Skip to content

Commit 3e5ea1e

Browse files
authored
Added GenericSchemeMultiCall (#786)
* 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 <>
1 parent 26f704a commit 3e5ea1e

File tree

3 files changed

+598
-1
lines changed

3 files changed

+598
-1
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
pragma solidity 0.5.17;
2+
pragma experimental ABIEncoderV2;
3+
4+
import "@daostack/infra/contracts/votingMachines/IntVoteInterface.sol";
5+
import "@daostack/infra/contracts/votingMachines/ProposalExecuteInterface.sol";
6+
import "../votingMachines/VotingMachineCallbacks.sol";
7+
8+
/**
9+
* @title GenericSchemeMultiCall.
10+
* @dev A scheme for proposing and executing calls to multiple arbitrary function
11+
* on one or multiple contracts on behalf of the organization avatar.
12+
*/
13+
contract GenericSchemeMultiCall is VotingMachineCallbacks, ProposalExecuteInterface {
14+
15+
// Details of a voting proposal:
16+
struct MultiCallProposal {
17+
address[] contractsToCall;
18+
bytes[] callsData;
19+
uint256[] values;
20+
bool exist;
21+
bool passed;
22+
}
23+
24+
mapping(bytes32=>MultiCallProposal) public proposals;
25+
26+
IntVoteInterface public votingMachine;
27+
bytes32 public voteParams;
28+
mapping(address=>bool) internal contractWhitelist;
29+
address[] public whitelistedContracts;
30+
Avatar public avatar;
31+
32+
event NewMultiCallProposal(
33+
address indexed _avatar,
34+
bytes32 indexed _proposalId,
35+
bytes[] _callsData,
36+
uint256[] _values,
37+
string _descriptionHash,
38+
address[] _contractsToCall
39+
);
40+
41+
event ProposalExecuted(
42+
address indexed _avatar,
43+
bytes32 indexed _proposalId
44+
);
45+
46+
event ProposalCallExecuted(
47+
address indexed _avatar,
48+
bytes32 indexed _proposalId,
49+
address _contractToCall,
50+
bool _success,
51+
bytes _callDataReturnValue
52+
);
53+
54+
event ProposalExecutedByVotingMachine(
55+
address indexed _avatar,
56+
bytes32 indexed _proposalId,
57+
int256 _param
58+
);
59+
60+
event ProposalDeleted(address indexed _avatar, bytes32 indexed _proposalId);
61+
62+
/**
63+
* @dev initialize
64+
* @param _avatar the avatar to mint reputation from
65+
* @param _votingMachine the voting machines address to
66+
* @param _voteParams voting machine parameters.
67+
* @param _contractWhitelist the contracts the scheme is allowed to interact with
68+
*
69+
*/
70+
function initialize(
71+
Avatar _avatar,
72+
IntVoteInterface _votingMachine,
73+
bytes32 _voteParams,
74+
address[] calldata _contractWhitelist
75+
)
76+
external
77+
{
78+
require(avatar == Avatar(0), "can be called only one time");
79+
require(_avatar != Avatar(0), "avatar cannot be zero");
80+
require(_contractWhitelist.length > 0, "contractWhitelist cannot be empty");
81+
avatar = _avatar;
82+
votingMachine = _votingMachine;
83+
voteParams = _voteParams;
84+
/* Whitelist controller by default*/
85+
Controller controller = Controller(_avatar.owner());
86+
whitelistedContracts.push(address(controller));
87+
contractWhitelist[address(controller)] = true;
88+
89+
for (uint i = 0; i < _contractWhitelist.length; i++) {
90+
contractWhitelist[_contractWhitelist[i]] = true;
91+
whitelistedContracts.push(_contractWhitelist[i]);
92+
}
93+
}
94+
95+
/**
96+
* @dev execution of proposals, can only be called by the voting machine in which the vote is held.
97+
* @param _proposalId the ID of the voting in the voting machine
98+
* @param _decision a parameter of the voting result, 1 yes and 2 is no.
99+
* @return bool success
100+
*/
101+
function executeProposal(bytes32 _proposalId, int256 _decision)
102+
external
103+
onlyVotingMachine(_proposalId)
104+
returns(bool) {
105+
MultiCallProposal storage proposal = proposals[_proposalId];
106+
require(proposal.exist, "must be a live proposal");
107+
require(proposal.passed == false, "cannot execute twice");
108+
109+
if (_decision == 1) {
110+
proposal.passed = true;
111+
execute(_proposalId);
112+
} else {
113+
delete proposals[_proposalId];
114+
emit ProposalDeleted(address(avatar), _proposalId);
115+
}
116+
117+
emit ProposalExecutedByVotingMachine(address(avatar), _proposalId, _decision);
118+
return true;
119+
}
120+
121+
/**
122+
* @dev execution of proposals after it has been decided by the voting machine
123+
* @param _proposalId the ID of the voting in the voting machine
124+
*/
125+
function execute(bytes32 _proposalId) public {
126+
MultiCallProposal storage proposal = proposals[_proposalId];
127+
require(proposal.exist, "must be a live proposal");
128+
require(proposal.passed, "proposal must passed by voting machine");
129+
proposal.exist = false;
130+
bytes memory genericCallReturnValue;
131+
bool success;
132+
Controller controller = Controller(whitelistedContracts[0]);
133+
134+
for (uint i = 0; i < proposal.contractsToCall.length; i++) {
135+
if (proposal.contractsToCall[i] == address(controller)) {
136+
(IERC20 extToken,
137+
address spender,
138+
uint256 valueToSpend
139+
) =
140+
abi.decode(
141+
proposal.callsData[i],
142+
(IERC20, address, uint256)
143+
);
144+
(success) = controller.externalTokenApproval(extToken, spender, valueToSpend, avatar);
145+
} else {
146+
(success, genericCallReturnValue) =
147+
controller.genericCall(proposal.contractsToCall[i], proposal.callsData[i], avatar, proposal.values[i]);
148+
}
149+
150+
emit ProposalCallExecuted(
151+
address(avatar),
152+
_proposalId,
153+
proposal.contractsToCall[i],
154+
success,
155+
genericCallReturnValue
156+
);
157+
}
158+
159+
delete proposals[_proposalId];
160+
emit ProposalDeleted(address(avatar), _proposalId);
161+
emit ProposalExecuted(address(avatar), _proposalId);
162+
}
163+
164+
/**
165+
* @dev propose to call one or multiple contracts on behalf of the _avatar
166+
* The function trigger NewMultiCallProposal event
167+
* @param _contractsToCall the contracts to be called
168+
* @param _callsData - The abi encode data for the calls
169+
* @param _values value(ETH) to transfer with the calls
170+
* @param _descriptionHash proposal description hash
171+
* @return an id which represents the proposal
172+
*/
173+
function proposeCalls(
174+
address[] memory _contractsToCall,
175+
bytes[] memory _callsData,
176+
uint256[] memory _values,
177+
string memory _descriptionHash
178+
)
179+
public
180+
returns(bytes32 proposalId)
181+
{
182+
require(
183+
(_contractsToCall.length == _callsData.length) && (_contractsToCall.length == _values.length),
184+
"Wrong length of _contractsToCall, _callsData or _values arrays"
185+
);
186+
Controller controller = Controller(whitelistedContracts[0]);
187+
for (uint i = 0; i < _contractsToCall.length; i++) {
188+
require(
189+
contractWhitelist[_contractsToCall[i]], "contractToCall is not whitelisted"
190+
);
191+
if (_contractsToCall[i] == address(controller)) {
192+
(IERC20 extToken,
193+
address spender,
194+
uint256 valueToSpend
195+
) =
196+
abi.decode(
197+
_callsData[i],
198+
(IERC20, address, uint256)
199+
);
200+
require(contractWhitelist[spender], "spender contract not whitelisted");
201+
}
202+
}
203+
proposalId = votingMachine.propose(2, voteParams, msg.sender, address(avatar));
204+
205+
proposals[proposalId] = MultiCallProposal({
206+
contractsToCall: _contractsToCall,
207+
callsData: _callsData,
208+
values: _values,
209+
exist: true,
210+
passed: false
211+
});
212+
proposalsInfo[address(votingMachine)][proposalId] = ProposalInfo({
213+
blockNumber:block.number,
214+
avatar:avatar
215+
});
216+
217+
emit NewMultiCallProposal(address(avatar), proposalId, _callsData, _values, _descriptionHash, _contractsToCall);
218+
219+
}
220+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@daostack/arc",
3-
"version": "0.0.1-rc.44",
3+
"version": "0.0.1-rc.45",
44
"description": "A platform for building DAOs",
55
"files": [
66
"contracts/",

0 commit comments

Comments
 (0)