Skip to content

Added upgradable contraintLimits #798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2a87697
simplify contract
Oct 7, 2020
685675e
tests
Oct 7, 2020
6b91201
test approval
Oct 7, 2020
feae9ee
check spender is white listed
Oct 7, 2020
1a6cd03
clean
Oct 7, 2020
e06fe11
poc
Oct 8, 2020
8872446
Add SchemeConstraints interface
Oct 8, 2020
9c9b4ab
add eth constraint as an example for DxDaoSchemeConstraint
Oct 8, 2020
e7cb197
+
Oct 8, 2020
519f7cb
more ..
Oct 11, 2020
221721f
fix dxdao constraints
Oct 11, 2020
8917c30
check schemeConstraint exist
Oct 11, 2020
aad9d93
+ comments
Oct 12, 2020
912657e
Merge branch 'master' into schemeconstraint
orenyodfat Oct 13, 2020
8a0bd6b
coverage
Oct 13, 2020
09c040e
Added upgradable contraintLimits
nicoelzer Oct 13, 2020
0231a5f
updated genericschmemulticall tests
nicoelzer Oct 13, 2020
d6809a2
Updated DXdaoSchemeConstraints
nicoelzer Oct 15, 2020
ad54756
Added contraint events and smaller updates
nicoelzer Oct 15, 2020
990ca26
Added additional tests for DxDaoSchemeConstraints
nicoelzer Oct 15, 2020
bd5a72b
rebase from arc/master
nicoelzer Oct 15, 2020
aad8929
Updated DxDaoSchemeContraints events & general cleanup
nicoelzer Oct 15, 2020
1b319f6
solhint cleanup
nicoelzer Oct 15, 2020
20469c7
Merge branch 'master' into schemeConstraintUpgradable
nicoelzer Oct 15, 2020
32c8403
cleanup
nicoelzer Oct 15, 2020
9aa64d2
Merge branch 'schemeConstraintUpgradable' of https://github.com/nicoe…
nicoelzer Oct 15, 2020
58b64b8
Fixed test issues
nicoelzer Oct 15, 2020
75a38d1
updated DxDaoSchemeConstraints.sol
nicoelzer Oct 15, 2020
d1d85ce
only genericSchemeMultiCall can call isAllowToCall
Oct 15, 2020
924f713
comments
Oct 15, 2020
3f6c82a
Added additional test for GenericSchemeMultiCall
nicoelzer Oct 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions contracts/schemes/DxDaoSchemeConstraints.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
pragma solidity 0.5.17;
pragma experimental ABIEncoderV2;

import "./SchemeConstraints.sol";


contract DxDaoSchemeConstraints is SchemeConstraints {
using SafeMath for uint256;

address public avatar;
uint256 public initialTimestamp;
uint256 public periodSize;
uint256 public periodLimitWei;

mapping(uint256=>uint256) public periodSpendingTokens;
mapping(address=>uint256) public periodLimitToken;
mapping (uint256 => mapping(address => uint256)) public periodSpendingToken;
mapping(uint256=>uint256) public periodSpendingWei;
mapping(address=>bool) public contractsWhiteListMap;
bytes4 private constant APPROVE_SIGNATURE = 0x095ea7b3;//approve(address,uint256)

/* @dev initialize
* @param avatar the DAOs avatar address
* @param _periodSize the time period to limit the tokens and eth spending
* @param _periodLimitWei the limit of eth which can be sent per period
* @param _periodLimitTokensAddresses tokens to limit
* @param _periodLimitTokensAmounts the limit of token which can be sent per period
* @param _contractsWhiteList the contracts the scheme is allowed to interact with
*/
function initialize(
address _avatar,
uint256 _periodSize,
uint256 _periodLimitWei,
address[] calldata _periodLimitTokensAddresses,
uint256[] calldata _periodLimitTokensAmounts,
address[] calldata _contractsWhiteList
)
external {
require(initialTimestamp == 0, "cannot initialize twice");
require(_periodSize > 0, "preriod size should be greater than 0");
require(_periodLimitTokensAddresses.length == _periodLimitTokensAmounts.length,
"invalid length _periodLimitTokensAddresses");
periodSize = _periodSize;
periodLimitWei = _periodLimitWei;
avatar = _avatar;
// solhint-disable-next-line not-rely-on-time
initialTimestamp = block.timestamp;
for (uint i = 0; i < _contractsWhiteList.length; i++) {
contractsWhiteListMap[_contractsWhiteList[i]] = true;
}
for (uint i = 0; i < _periodLimitTokensAmounts.length; i++) {
periodLimitToken[_periodLimitTokensAddresses[i]] = _periodLimitTokensAmounts[i];
}
contractsWhiteList = _contractsWhiteList;
}

/*
* @dev updateContractWhitelist used to let the DAO update whitelisted contracts.
* @param _contractsAddresses - The contract that should be update
* @param _contractsWhitelisted – true adds a contract to the whitelist, false removes it.
*/
function updateContractWhitelist(
address[] calldata _contractsAddresses,
bool[] calldata _contractsWhitelisted
)
external {
require(msg.sender == avatar, "caller must be avatar");
require(_contractsAddresses.length == _contractsWhitelisted.length,
"invalid length _periodLimitTokensAddresses");
for (uint i = 0; i < _contractsAddresses.length; i++) {
contractsWhiteListMap[_contractsAddresses[i]] = _contractsWhitelisted[i];
}
}

/*
* @dev updatePeriodLimitTokens lets the dao update limits to token limits.
* @param _tokensAddresses - The token that should be updated
* @param _tokensPeriodLimits – The amount that will be set as a spending limit
*/
function updatePeriodLimitTokens(
address[] calldata _tokensAddresses,
uint256[] calldata _tokensPeriodLimits
)
external {
require(msg.sender == avatar, "caller must be avatar");
require(_tokensAddresses.length == _tokensPeriodLimits.length,
"invalid length _tokensPeriodLimits");
for (uint i = 0; i < _tokensAddresses.length; i++) {
periodLimitToken[_tokensAddresses[i]] = _tokensPeriodLimits[i];
}
}

/*
* @dev updatePeriodLimitWei lets the dao update limits to ETH spending limit.
* @param _periodLimitWei - The new spending limit in WEI that should be set.
*/
function updatePeriodLimitWei(uint256 _periodLimitWei) external {
require(msg.sender == avatar, "caller must be avatar");
periodLimitWei = _periodLimitWei;
}

/*
* @dev isAllowedToCall should be called upon a proposal execution.
* - check that the total spending of tokens within a 'periodSize' does not exceed the periodLimit per token
* - check that the total sending of eth within a 'periodSize' does not exceed the periodLimit
* @param _contractsToCall the contracts to be called
* @param _callsData - The abi encode data for the calls
* @param _values value(ETH) to transfer with the calls
* @param _avatar avatar
* @return bool value true-allowed false not allowed
*/
function isAllowedToCall(
address[] calldata _contractsToCall,
bytes[] calldata _callsData,
uint256[] calldata _values,
Avatar
)
external
returns(bool)
{

uint256 observervationIndex = observationIndex();
uint256 totalPeriodSpendingInWei;
for (uint i = 0; i < _contractsToCall.length; i++) {
// constraint eth transfer
totalPeriodSpendingInWei = totalPeriodSpendingInWei.add(_values[i]);
bytes memory callData = _callsData[i];
// constraint approve calls
if (callData[0] == APPROVE_SIGNATURE[0] &&
callData[1] == APPROVE_SIGNATURE[1] &&
callData[2] == APPROVE_SIGNATURE[2] &&
callData[3] == APPROVE_SIGNATURE[3]) {
uint256 amount;
address contractToCall = _contractsToCall[i];
// solhint-disable-next-line no-inline-assembly
assembly {
amount := mload(add(callData, 68))
}
periodSpendingToken[observervationIndex][contractToCall] =
periodSpendingToken[observervationIndex][contractToCall].add(amount);
require(
periodSpendingToken[observervationIndex][contractToCall] <= periodLimitToken[contractToCall],
"periodSpendingTokensExceeded");
}

}
periodSpendingWei[observervationIndex] =
periodSpendingWei[observervationIndex].add(totalPeriodSpendingInWei);
require(periodSpendingWei[observervationIndex] <= periodLimitWei, "periodSpendingWeiExceeded");
return true;
}

/*
* @dev isAllowedToPropose should be called upon a proposal submition.
* allow only whitelisted target contracts or 'approve' calls which the 'spender' is whitelisted
* @param _contractsToCall the contracts to be called
* @param _callsData - The abi encode data for the calls
* @param _values value(ETH) to transfer with the calls
* @param _avatar avatar
* @return bool value true-allowed false not allowed
*/
function isAllowedToPropose(
address[] calldata _contractsToCall,
bytes[] calldata _callsData,
uint256[] calldata,
Avatar)
external
returns(bool)
{
for (uint i = 0; i < _contractsToCall.length; i++) {
if (!contractsWhiteListMap[_contractsToCall[i]]) {
address spender;
bytes memory callData = _callsData[i];
require(
callData[0] == APPROVE_SIGNATURE[0] &&
callData[1] == APPROVE_SIGNATURE[1] &&
callData[2] == APPROVE_SIGNATURE[2] &&
callData[3] == APPROVE_SIGNATURE[3],
"allow only approve call for none whitelistedContracts");
//in solidity > 6 this can be replaced by:
//(spender,) = abi.descode(callData[4:], (address, uint));
// see https://github.com/ethereum/solidity/issues/9439
// solhint-disable-next-line no-inline-assembly
assembly {
spender := mload(add(callData, 36))
}
require(contractsWhiteListMap[spender], "spender contract not whitelisted");
}
}
return true;
}

function observationIndex() public view returns (uint256) {
// solhint-disable-next-line not-rely-on-time
return uint8((block.timestamp - initialTimestamp) / periodSize);
}

}
72 changes: 25 additions & 47 deletions contracts/schemes/GenericSchemeMultiCall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma experimental ABIEncoderV2;
import "@daostack/infra/contracts/votingMachines/IntVoteInterface.sol";
import "@daostack/infra/contracts/votingMachines/ProposalExecuteInterface.sol";
import "../votingMachines/VotingMachineCallbacks.sol";
import "./SchemeConstraints.sol";


/**
Expand All @@ -24,12 +25,10 @@ contract GenericSchemeMultiCall is VotingMachineCallbacks, ProposalExecuteInterf
}

mapping(bytes32=>MultiCallProposal) public proposals;

IntVoteInterface public votingMachine;
bytes32 public voteParams;
mapping(address=>bool) internal contractWhitelist;
address[] public whitelistedContracts;
Avatar public avatar;
SchemeConstraints public schemeConstraints;

event NewMultiCallProposal(
address indexed _avatar,
Expand Down Expand Up @@ -61,37 +60,26 @@ contract GenericSchemeMultiCall is VotingMachineCallbacks, ProposalExecuteInterf

event ProposalDeleted(address indexed _avatar, bytes32 indexed _proposalId);

/**
* @dev initialize
/* @dev initialize
* @param _avatar the avatar to mint reputation from
* @param _votingMachine the voting machines address to
* @param _voteParams voting machine parameters.
* @param _contractWhitelist the contracts the scheme is allowed to interact with
*
* @param _schemeConstraints the schemeConstraints contracts.
*/
function initialize(
Avatar _avatar,
IntVoteInterface _votingMachine,
bytes32 _voteParams,
address[] calldata _contractWhitelist
SchemeConstraints _schemeConstraints
)
external
{
require(avatar == Avatar(0), "can be called only one time");
require(_avatar != Avatar(0), "avatar cannot be zero");
require(_contractWhitelist.length > 0, "contractWhitelist cannot be empty");
avatar = _avatar;
votingMachine = _votingMachine;
voteParams = _voteParams;
/* Whitelist controller by default*/
Controller controller = Controller(_avatar.owner());
whitelistedContracts.push(address(controller));
contractWhitelist[address(controller)] = true;

for (uint i = 0; i < _contractWhitelist.length; i++) {
contractWhitelist[_contractWhitelist[i]] = true;
whitelistedContracts.push(_contractWhitelist[i]);
}
schemeConstraints = _schemeConstraints;
}

/**
Expand Down Expand Up @@ -127,28 +115,23 @@ contract GenericSchemeMultiCall is VotingMachineCallbacks, ProposalExecuteInterf
MultiCallProposal storage proposal = proposals[_proposalId];
require(proposal.exist, "must be a live proposal");
require(proposal.passed, "proposal must passed by voting machine");
if (schemeConstraints != SchemeConstraints(0)) {
require(
schemeConstraints.isAllowedToCall(
proposal.contractsToCall,
proposal.callsData,
proposal.values,
avatar),
"call is not allowed");
}
proposal.exist = false;
bytes memory genericCallReturnValue;
bool success;
Controller controller = Controller(whitelistedContracts[0]);

Controller controller = Controller(avatar.owner());
for (uint i = 0; i < proposal.contractsToCall.length; i++) {
bytes memory callData = proposal.callsData[i];
if (proposal.contractsToCall[i] == address(controller)) {
(IERC20 extToken,
address spender,
uint256 valueToSpend
) =
abi.decode(
callData,
(IERC20, address, uint256)
);
success = controller.externalTokenApproval(extToken, spender, valueToSpend, avatar);
} else {
(success, genericCallReturnValue) =
controller.genericCall(proposal.contractsToCall[i], callData, avatar, proposal.values[i]);
}

(success, genericCallReturnValue) =
controller.genericCall(proposal.contractsToCall[i], callData, avatar, proposal.values[i]);
/* Whole transaction will be reverted if at least one call fails*/
require(success, "Proposal call failed");
emit ProposalCallExecuted(
Expand Down Expand Up @@ -190,19 +173,14 @@ contract GenericSchemeMultiCall is VotingMachineCallbacks, ProposalExecuteInterf
(_contractsToCall.length == _callsData.length) && (_contractsToCall.length == _values.length),
"Wrong length of _contractsToCall, _callsDataLens or _values arrays"
);
for (uint i = 0; i < _contractsToCall.length; i++) {
if (schemeConstraints != SchemeConstraints(0)) {
require(
contractWhitelist[_contractsToCall[i]], "contractToCall is not whitelisted"
);
if (_contractsToCall[i] == whitelistedContracts[0]) {

(, address spender,) =
abi.decode(
_callsData[i],
(IERC20, address, uint256)
);
require(contractWhitelist[spender], "spender contract not whitelisted");
}
schemeConstraints.isAllowedToPropose(
_contractsToCall,
_callsData,
_values,
avatar),
"propose is not allowed");
}

proposalId = votingMachine.propose(2, voteParams, msg.sender, address(avatar));
Expand Down
44 changes: 44 additions & 0 deletions contracts/schemes/SchemeConstraints.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
pragma solidity 0.5.17;
pragma experimental ABIEncoderV2;
import "../controller/Avatar.sol";


contract SchemeConstraints {

address[] public contractsWhiteList;

/*
* @dev isAllowedToCall should be called upon a proposal execution.
* @param _contractsToCall the contracts to be called
* @param _callsData - The abi encode data for the calls
* @param _values value(ETH) to transfer with the calls
* @param _avatar avatar
* @return bool value true-allowed false not allowed
*/
function isAllowedToCall(
address[] calldata _contractsToCall,
bytes[] calldata _callsData,
uint256[] calldata _values,
Avatar _avatar)
external returns(bool);

/*
* @dev isAllowedToPropose should be called upon a proposal submition.
* @param _contractsToCall the contracts to be called
* @param _callsData - The abi encode data for the calls
* @param _values value(ETH) to transfer with the calls
* @param _avatar avatar
* @return bool value true-allowed false not allowed
*/
function isAllowedToPropose(
address[] calldata _contractsToCall,
bytes[] calldata _callsData,
uint256[] calldata _values,
Avatar _avatar)
external returns(bool);

function getContractsWhiteList() external view returns(address[] memory) {
return contractsWhiteList;
}

}
Loading