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
+ }
0 commit comments