Skip to content

Commit

Permalink
Added GenericSchemeMultiCall (#786)
Browse files Browse the repository at this point in the history
* added GenericSchemeMultiCall

* Cleanup GenericSchemeMultiCall

* Cleanup GenericSchemeMultiCall

* Cleanup GenericSchemeMultiCall

* Added contract whitelist & tokenApproval

* GenericSchemeMultiCall tests

* .

* Added GenericSchemeMultiCall tests

* Added GenericSchemeMultiCall tests

* added approval spender restriction

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* GenericSchemeMultiCall v.0.0.1

* GenericSchemeMultiCall

* updated .gitignore

* Cleanup

* cleanup

* Update package.json

* Update .gitignore

* Update package-lock.json

* Update 2_deploy_organization.js

* Change version to 0.0.1-rc.45

Co-authored-by: Nico Elzer <>
  • Loading branch information
nicoelzer authored Sep 10, 2020
1 parent 26f704a commit 3e5ea1e
Show file tree
Hide file tree
Showing 3 changed files with 598 additions and 1 deletion.
220 changes: 220 additions & 0 deletions contracts/schemes/GenericSchemeMultiCall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
pragma solidity 0.5.17;
pragma experimental ABIEncoderV2;

import "@daostack/infra/contracts/votingMachines/IntVoteInterface.sol";
import "@daostack/infra/contracts/votingMachines/ProposalExecuteInterface.sol";
import "../votingMachines/VotingMachineCallbacks.sol";

/**
* @title GenericSchemeMultiCall.
* @dev A scheme for proposing and executing calls to multiple arbitrary function
* on one or multiple contracts on behalf of the organization avatar.
*/
contract GenericSchemeMultiCall is VotingMachineCallbacks, ProposalExecuteInterface {

// Details of a voting proposal:
struct MultiCallProposal {
address[] contractsToCall;
bytes[] callsData;
uint256[] values;
bool exist;
bool passed;
}

mapping(bytes32=>MultiCallProposal) public proposals;

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

event NewMultiCallProposal(
address indexed _avatar,
bytes32 indexed _proposalId,
bytes[] _callsData,
uint256[] _values,
string _descriptionHash,
address[] _contractsToCall
);

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

event ProposalCallExecuted(
address indexed _avatar,
bytes32 indexed _proposalId,
address _contractToCall,
bool _success,
bytes _callDataReturnValue
);

event ProposalExecutedByVotingMachine(
address indexed _avatar,
bytes32 indexed _proposalId,
int256 _param
);

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

/**
* @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
*
*/
function initialize(
Avatar _avatar,
IntVoteInterface _votingMachine,
bytes32 _voteParams,
address[] calldata _contractWhitelist
)
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]);
}
}

/**
* @dev execution of proposals, can only be called by the voting machine in which the vote is held.
* @param _proposalId the ID of the voting in the voting machine
* @param _decision a parameter of the voting result, 1 yes and 2 is no.
* @return bool success
*/
function executeProposal(bytes32 _proposalId, int256 _decision)
external
onlyVotingMachine(_proposalId)
returns(bool) {
MultiCallProposal storage proposal = proposals[_proposalId];
require(proposal.exist, "must be a live proposal");
require(proposal.passed == false, "cannot execute twice");

if (_decision == 1) {
proposal.passed = true;
execute(_proposalId);
} else {
delete proposals[_proposalId];
emit ProposalDeleted(address(avatar), _proposalId);
}

emit ProposalExecutedByVotingMachine(address(avatar), _proposalId, _decision);
return true;
}

/**
* @dev execution of proposals after it has been decided by the voting machine
* @param _proposalId the ID of the voting in the voting machine
*/
function execute(bytes32 _proposalId) public {
MultiCallProposal storage proposal = proposals[_proposalId];
require(proposal.exist, "must be a live proposal");
require(proposal.passed, "proposal must passed by voting machine");
proposal.exist = false;
bytes memory genericCallReturnValue;
bool success;
Controller controller = Controller(whitelistedContracts[0]);

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

emit ProposalCallExecuted(
address(avatar),
_proposalId,
proposal.contractsToCall[i],
success,
genericCallReturnValue
);
}

delete proposals[_proposalId];
emit ProposalDeleted(address(avatar), _proposalId);
emit ProposalExecuted(address(avatar), _proposalId);
}

/**
* @dev propose to call one or multiple contracts on behalf of the _avatar
* The function trigger NewMultiCallProposal event
* @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 _descriptionHash proposal description hash
* @return an id which represents the proposal
*/
function proposeCalls(
address[] memory _contractsToCall,
bytes[] memory _callsData,
uint256[] memory _values,
string memory _descriptionHash
)
public
returns(bytes32 proposalId)
{
require(
(_contractsToCall.length == _callsData.length) && (_contractsToCall.length == _values.length),
"Wrong length of _contractsToCall, _callsData or _values arrays"
);
Controller controller = Controller(whitelistedContracts[0]);
for (uint i = 0; i < _contractsToCall.length; i++) {
require(
contractWhitelist[_contractsToCall[i]], "contractToCall is not whitelisted"
);
if (_contractsToCall[i] == address(controller)) {
(IERC20 extToken,
address spender,
uint256 valueToSpend
) =
abi.decode(
_callsData[i],
(IERC20, address, uint256)
);
require(contractWhitelist[spender], "spender contract not whitelisted");
}
}
proposalId = votingMachine.propose(2, voteParams, msg.sender, address(avatar));

proposals[proposalId] = MultiCallProposal({
contractsToCall: _contractsToCall,
callsData: _callsData,
values: _values,
exist: true,
passed: false
});
proposalsInfo[address(votingMachine)][proposalId] = ProposalInfo({
blockNumber:block.number,
avatar:avatar
});

emit NewMultiCallProposal(address(avatar), proposalId, _callsData, _values, _descriptionHash, _contractsToCall);

}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@daostack/arc",
"version": "0.0.1-rc.44",
"version": "0.0.1-rc.45",
"description": "A platform for building DAOs",
"files": [
"contracts/",
Expand Down
Loading

0 comments on commit 3e5ea1e

Please sign in to comment.