From 8ead8964e303867ca082bec44053e22f758435a5 Mon Sep 17 00:00:00 2001 From: Nico Elzer <> Date: Mon, 31 Aug 2020 17:16:42 +0200 Subject: [PATCH 1/8] added GenericSchemeMultiCall --- contracts/schemes/GenericSchemeMultiCall.sol | 167 +++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 contracts/schemes/GenericSchemeMultiCall.sol diff --git a/contracts/schemes/GenericSchemeMultiCall.sol b/contracts/schemes/GenericSchemeMultiCall.sol new file mode 100644 index 00000000..38127e4e --- /dev/null +++ b/contracts/schemes/GenericSchemeMultiCall.sol @@ -0,0 +1,167 @@ +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 GenericScheme. + * @dev A scheme for proposing and executing calls to multiple arbitrary function + * on whitelisted contract on behalf of the organization avatar. + */ +contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterface { + event NewMultiCallProposal( + address indexed _avatar, + bytes32 indexed _proposalId, + bytes[] _callData, + uint[] _value, + string _descriptionHash, + address[] _contractToCall + ); + + event ProposalExecuted( + address indexed _avatar, + bytes32 indexed _proposalId + ); + + event ProposalCallExecuted( + address indexed _avatar, + bytes32 indexed _proposalId, + address _contractToCall, + bytes _callDataReturnValue + ); + + event ProposalExecutedByVotingMachine( + address indexed _avatar, + bytes32 indexed _proposalId, + int256 _param + ); + + event ProposalDeleted(address indexed _avatar, bytes32 indexed _proposalId); + + // Details of a voting proposal: + struct MultiCallProposal { + address[] contractToCall; + bytes[] callData; + uint256[] value; + bool exist; + bool passed; + } + + mapping(bytes32=>MultiCallProposal) public organizationProposals; + + IntVoteInterface public votingMachine; + bytes32 public voteParams; + Avatar public avatar; + + /** + * @dev initialize + * @param _avatar the avatar to mint reputation from + * @param _votingMachine the voting machines address to + * @param _voteParams voting machine parameters. + */ + function initialize( + Avatar _avatar, + IntVoteInterface _votingMachine, + bytes32 _voteParams + ) + external + { + require(avatar == Avatar(0), "can be called only one time"); + require(_avatar != Avatar(0), "avatar cannot be zero"); + avatar = _avatar; + votingMachine = _votingMachine; + voteParams = _voteParams; + } + + /** + * @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 = organizationProposals[_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 organizationProposals[_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 = organizationProposals[_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(avatar.owner()); + + for (uint i = 0; i < proposal.contractToCall.length; i ++) { + (success, genericCallReturnValue) = + controller.genericCall(proposal.contractToCall[i], proposal.callData[i], avatar, proposal.value[i]); + require(success, "Proposal call failed"); + emit ProposalCallExecuted(address(avatar), _proposalId, proposal.contractToCall[i], genericCallReturnValue); + } + + delete organizationProposals[_proposalId]; + emit ProposalDeleted(address(avatar), _proposalId); + emit ProposalExecuted(address(avatar), _proposalId); + + } + + /** + * @dev propose to call on behalf of the _avatar + * The function trigger NewCallProposal event + * @param _contractToCall the contract to be called + * @param _callData - The abi encode data for the call + * @param _value value(ETH) to transfer with the call + * @param _descriptionHash proposal description hash + * @return an id which represents the proposal + */ + + function proposeCall(address[] memory _contractToCall, bytes[] memory _callData, uint256[] memory _value, string memory _descriptionHash) + public + returns(bytes32) + { + require( + (_contractToCall.length == _callData.length) && (_contractToCall.length == _value.length), + "Wrong length of _contractToCall, _callData or _value arrays" + ); + bytes32 proposalId = votingMachine.propose(2, voteParams, msg.sender, address(avatar)); + + organizationProposals[proposalId] = MultiCallProposal({ + contractToCall: _contractToCall, + callData: _callData, + value: _value, + exist: true, + passed: false + }); + proposalsInfo[address(votingMachine)][proposalId] = ProposalInfo({ + blockNumber:block.number, + avatar:avatar + }); + + emit NewMultiCallProposal(address(avatar), proposalId, _callData, _value, _descriptionHash, _contractToCall); + return proposalId; + + } + +} \ No newline at end of file From fe016bf844a639a02d8e73aa231d1d19771a64fb Mon Sep 17 00:00:00 2001 From: Nico Elzer Date: Tue, 1 Sep 2020 07:23:17 +0200 Subject: [PATCH 2/8] Cleanup GenericSchemeMultiCall --- contracts/schemes/GenericSchemeMultiCall.sol | 53 ++++++++++---------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/contracts/schemes/GenericSchemeMultiCall.sol b/contracts/schemes/GenericSchemeMultiCall.sol index 38127e4e..4758747b 100644 --- a/contracts/schemes/GenericSchemeMultiCall.sol +++ b/contracts/schemes/GenericSchemeMultiCall.sol @@ -6,18 +6,18 @@ import "@daostack/infra/contracts/votingMachines/ProposalExecuteInterface.sol"; import "../votingMachines/VotingMachineCallbacks.sol"; /** - * @title GenericScheme. - * @dev A scheme for proposing and executing calls to multiple arbitrary function - * on whitelisted contract on behalf of the organization avatar. + * @title GenericSchemeMulticall. + * @dev A scheme for proposing and executing one/multiple calls to or or multiple arbitrary functions + * on contracts on behalf of the organization avatar. */ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterface { event NewMultiCallProposal( address indexed _avatar, bytes32 indexed _proposalId, bytes[] _callData, - uint[] _value, + uint256[] _value, string _descriptionHash, - address[] _contractToCall + address[] _contractsToCall ); event ProposalExecuted( @@ -42,14 +42,14 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf // Details of a voting proposal: struct MultiCallProposal { - address[] contractToCall; + address[] contractsToCall; bytes[] callData; uint256[] value; bool exist; bool passed; } - mapping(bytes32=>MultiCallProposal) public organizationProposals; + mapping(bytes32=>MultiCallProposal) public proposals; IntVoteInterface public votingMachine; bytes32 public voteParams; @@ -85,7 +85,7 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf external onlyVotingMachine(_proposalId) returns(bool) { - MultiCallProposal storage proposal = organizationProposals[_proposalId]; + MultiCallProposal storage proposal = proposals[_proposalId]; require(proposal.exist, "must be a live proposal"); require(proposal.passed == false, "cannot execute twice"); @@ -93,7 +93,7 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf proposal.passed = true; execute(_proposalId); } else { - delete organizationProposals[_proposalId]; + delete proposals[_proposalId]; emit ProposalDeleted(address(avatar), _proposalId); } @@ -106,7 +106,7 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf * @param _proposalId the ID of the voting in the voting machine */ function execute(bytes32 _proposalId) public { - MultiCallProposal storage proposal = organizationProposals[_proposalId]; + 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; @@ -114,41 +114,42 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf bool success; Controller controller = Controller(avatar.owner()); - for (uint i = 0; i < proposal.contractToCall.length; i ++) { + for (uint i = 0; i < proposal.contractsToCall.length; i ++) { (success, genericCallReturnValue) = - controller.genericCall(proposal.contractToCall[i], proposal.callData[i], avatar, proposal.value[i]); + controller.genericCall(proposal.contractsToCall[i], proposal.callData[i], avatar, proposal.value[i]); + /* All transactions must be successfull otherwise the whole proposal transaction will be reverted*/ require(success, "Proposal call failed"); - emit ProposalCallExecuted(address(avatar), _proposalId, proposal.contractToCall[i], genericCallReturnValue); + emit ProposalCallExecuted(address(avatar), _proposalId, proposal.contractsToCall[i], genericCallReturnValue); } - delete organizationProposals[_proposalId]; + delete proposals[_proposalId]; emit ProposalDeleted(address(avatar), _proposalId); emit ProposalExecuted(address(avatar), _proposalId); } /** - * @dev propose to call on behalf of the _avatar - * The function trigger NewCallProposal event - * @param _contractToCall the contract to be called - * @param _callData - The abi encode data for the call - * @param _value value(ETH) to transfer with the call + * @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 _callData - The abi encode data for the calls + * @param _value value(ETH) to transfer with the calls * @param _descriptionHash proposal description hash * @return an id which represents the proposal */ - function proposeCall(address[] memory _contractToCall, bytes[] memory _callData, uint256[] memory _value, string memory _descriptionHash) + function proposeCalls(address[] memory _contractsToCall, bytes[] memory _callData, uint256[] memory _value, string memory _descriptionHash) public - returns(bytes32) + returns(bytes32 proposalId) { require( - (_contractToCall.length == _callData.length) && (_contractToCall.length == _value.length), - "Wrong length of _contractToCall, _callData or _value arrays" + (_contractsToCall.length == _callData.length) && (_contractsToCall.length == _value.length), + "Wrong length of _contractsToCall, _callData or _value arrays" ); bytes32 proposalId = votingMachine.propose(2, voteParams, msg.sender, address(avatar)); - organizationProposals[proposalId] = MultiCallProposal({ - contractToCall: _contractToCall, + proposals[proposalId] = MultiCallProposal({ + contractsToCall: _contractsToCall, callData: _callData, value: _value, exist: true, @@ -159,7 +160,7 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf avatar:avatar }); - emit NewMultiCallProposal(address(avatar), proposalId, _callData, _value, _descriptionHash, _contractToCall); + emit NewMultiCallProposal(address(avatar), proposalId, _callData, _value, _descriptionHash, _contractsToCall); return proposalId; } From b452b21681570f85f639f92d8d6130f40b45fa71 Mon Sep 17 00:00:00 2001 From: Nico Elzer Date: Tue, 1 Sep 2020 07:25:12 +0200 Subject: [PATCH 3/8] Cleanup GenericSchemeMultiCall --- contracts/schemes/GenericSchemeMultiCall.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/schemes/GenericSchemeMultiCall.sol b/contracts/schemes/GenericSchemeMultiCall.sol index 4758747b..66d17034 100644 --- a/contracts/schemes/GenericSchemeMultiCall.sol +++ b/contracts/schemes/GenericSchemeMultiCall.sol @@ -161,7 +161,6 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf }); emit NewMultiCallProposal(address(avatar), proposalId, _callData, _value, _descriptionHash, _contractsToCall); - return proposalId; } From af96862d5fd00627c6817e47802e795882fd4374 Mon Sep 17 00:00:00 2001 From: Nico Elzer Date: Tue, 1 Sep 2020 07:26:14 +0200 Subject: [PATCH 4/8] Cleanup GenericSchemeMultiCall --- contracts/schemes/GenericSchemeMultiCall.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/schemes/GenericSchemeMultiCall.sol b/contracts/schemes/GenericSchemeMultiCall.sol index 66d17034..9846943e 100644 --- a/contracts/schemes/GenericSchemeMultiCall.sol +++ b/contracts/schemes/GenericSchemeMultiCall.sol @@ -146,7 +146,7 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf (_contractsToCall.length == _callData.length) && (_contractsToCall.length == _value.length), "Wrong length of _contractsToCall, _callData or _value arrays" ); - bytes32 proposalId = votingMachine.propose(2, voteParams, msg.sender, address(avatar)); + proposalId = votingMachine.propose(2, voteParams, msg.sender, address(avatar)); proposals[proposalId] = MultiCallProposal({ contractsToCall: _contractsToCall, From cda645c879d7501a24f728db52aee196cf235d2b Mon Sep 17 00:00:00 2001 From: Nico Elzer Date: Tue, 1 Sep 2020 19:35:49 +0200 Subject: [PATCH 5/8] Added contract whitelist & tokenApproval --- contracts/schemes/GenericSchemeMultiCall.sol | 42 ++- test/genericschememulticall.js | 269 +++++++++++++++++++ 2 files changed, 303 insertions(+), 8 deletions(-) create mode 100644 test/genericschememulticall.js diff --git a/contracts/schemes/GenericSchemeMultiCall.sol b/contracts/schemes/GenericSchemeMultiCall.sol index 9846943e..f8ed362f 100644 --- a/contracts/schemes/GenericSchemeMultiCall.sol +++ b/contracts/schemes/GenericSchemeMultiCall.sol @@ -7,10 +7,11 @@ import "../votingMachines/VotingMachineCallbacks.sol"; /** * @title GenericSchemeMulticall. - * @dev A scheme for proposing and executing one/multiple calls to or or multiple arbitrary functions + * @dev A scheme for proposing and executing one/multiple calls to or or multiple arbitrary functions * on contracts on behalf of the organization avatar. */ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterface { + event NewMultiCallProposal( address indexed _avatar, bytes32 indexed _proposalId, @@ -53,6 +54,8 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf IntVoteInterface public votingMachine; bytes32 public voteParams; + mapping(address=>bool) internal contractWhitelist; + address[] public whitelistedContracts; Avatar public avatar; /** @@ -60,19 +63,27 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf * @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 + 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; + for(uint i = 0; i < _contractWhitelist.length; i ++) { + contractWhitelist[_contractWhitelist[i]] = true; + whitelistedContracts.push(_contractWhitelist[i]); + } } /** @@ -115,17 +126,32 @@ contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterf Controller controller = Controller(avatar.owner()); for (uint i = 0; i < proposal.contractsToCall.length; i ++) { - (success, genericCallReturnValue) = - controller.genericCall(proposal.contractsToCall[i], proposal.callData[i], avatar, proposal.value[i]); - /* All transactions must be successfull otherwise the whole proposal transaction will be reverted*/ - require(success, "Proposal call failed"); - emit ProposalCallExecuted(address(avatar), _proposalId, proposal.contractsToCall[i], genericCallReturnValue); + if (proposal.contractsToCall[i] == address(controller)) { + + (IERC20 extToken, + address spender, + uint256 valueToSpend + ) = + /* solhint-disable */ + abi.decode( + proposal.callData[i], + (IERC20, address, uint256) + ); + controller.externalTokenApproval(extToken,spender,valueToSpend,avatar); + } else { + require(contractWhitelist[proposal.contractsToCall[i]], "contractToCall is not whitelisted"); + (success, genericCallReturnValue) = + controller.genericCall(proposal.contractsToCall[i], proposal.callData[i], avatar, proposal.value[i]); + /* All transactions must be successfull otherwise the whole proposal transaction will be reverted*/ + require(success, "Proposal call failed"); + } + + emit ProposalCallExecuted(address(avatar), _proposalId, proposal.contractsToCall[i], genericCallReturnValue); } delete proposals[_proposalId]; emit ProposalDeleted(address(avatar), _proposalId); emit ProposalExecuted(address(avatar), _proposalId); - } /** diff --git a/test/genericschememulticall.js b/test/genericschememulticall.js new file mode 100644 index 00000000..d2f5bb9a --- /dev/null +++ b/test/genericschememulticall.js @@ -0,0 +1,269 @@ +import * as helpers from './helpers'; +const constants = require('./constants'); +const GenericSchemeMultiCall = artifacts.require('./GenericSchemeMultiCall.sol'); +const DaoCreator = artifacts.require("./DaoCreator.sol"); +const ControllerCreator = artifacts.require("./ControllerCreator.sol"); +const DAOTracker = artifacts.require("./DAOTracker.sol"); +const ERC20Mock = artifacts.require("./ERC20Mock.sol"); +const ActionMock = artifacts.require("./ActionMock.sol"); +const Wallet = artifacts.require("./Wallet.sol"); + +export class GenericSchemeMultiCallParams { + constructor() { + } +} + +const setupGenericSchemeParams = async function( + genericScheme, + accounts, + contractToCall, + genesisProtocol = false, + tokenAddress = 0, + avatar + ) { + var genericSchemeParams = new GenericSchemeParams(); + if (genesisProtocol === true){ + genericSchemeParams.votingMachine = await helpers.setupGenesisProtocol(accounts,tokenAddress,0,helpers.NULL_ADDRESS); + await genericScheme.initialize( + avatar.address, + genericSchemeParams.votingMachine.genesisProtocol.address, + genericSchemeParams.votingMachine.params, + contractToCall); + } + else { + genericSchemeParams.votingMachine = await helpers.setupAbsoluteVote(helpers.NULL_ADDRESS,50,genericScheme.address); + await genericScheme.initialize( + avatar.address, + genericSchemeParams.votingMachine.absoluteVote.address, + genericSchemeParams.votingMachine.params, + contractToCall); + } + return genericSchemeParams; +}; + +const setup = async function (accounts,contractToCall = 0,reputationAccount=0,genesisProtocol = false,tokenAddress=0) { + var testSetup = new helpers.TestSetup(); + testSetup.standardTokenMock = await ERC20Mock.new(accounts[1],100); + testSetup.genericScheme = await GenericScheme.new(); + var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT}); + var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT}); + testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,daoTracker.address,{gas:constants.ARC_GAS_LIMIT}); + testSetup.reputationArray = [20,10,70]; + + if (reputationAccount === 0) { + testSetup.org = await helpers.setupOrganizationWithArrays(testSetup.daoCreator,[accounts[0],accounts[1],accounts[2]],[1000,1000,1000],testSetup.reputationArray); + } else { + testSetup.org = await helpers.setupOrganizationWithArrays(testSetup.daoCreator,[accounts[0],accounts[1],reputationAccount],[1000,1000,1000],testSetup.reputationArray); + } + testSetup.genericSchemeParams= await setupGenericSchemeParams(testSetup.genericScheme,accounts,contractToCall,genesisProtocol,tokenAddress,testSetup.org.avatar); + var permissions = "0x00000010"; + + + await testSetup.daoCreator.setSchemes(testSetup.org.avatar.address, + [testSetup.genericScheme.address], + [helpers.NULL_HASH],[permissions],"metaData"); + + return testSetup; +}; + +const createCallToActionMock = async function(_avatar,_actionMock) { + return await new web3.eth.Contract(_actionMock.abi).methods.test2(_avatar).encodeABI(); +}; + +contract('GenericScheme', function(accounts) { + before(function() { + helpers.etherForEveryone(accounts); + }); + + it("proposeCall log", async function() { + + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,actionMock.address); + var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + + var tx = await testSetup.genericScheme.proposeCall( + callData,0,helpers.NULL_HASH); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "NewCallProposal"); + }); + + it("execute proposeCall -no decision - proposal data delete", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,actionMock.address); + var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + var tx = await testSetup.genericScheme.proposeCall(callData,0,helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + await testSetup.genericSchemeParams.votingMachine.absoluteVote.vote(proposalId,0,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + //check organizationsProposals after execution + var organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); + assert.equal(organizationProposal.passed,false); + assert.equal(organizationProposal.callData,null); + }); + + it("execute proposeVote -positive decision - proposal data delete", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,actionMock.address); + var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + var tx = await testSetup.genericScheme.proposeCall(callData,0,helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + var organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); + assert.equal(organizationProposal[0],callData,helpers.NULL_HASH); + await testSetup.genericSchemeParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + //check organizationsProposals after execution + organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); + assert.equal(organizationProposal.callData,null);//new contract address + }); + + it("execute proposeVote -positive decision - destination reverts", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,actionMock.address); + var callData = await createCallToActionMock(helpers.NULL_ADDRESS,actionMock); + var tx = await testSetup.genericScheme.proposeCall(callData,0,helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + + await testSetup.genericSchemeParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + //actionMock revert because msg.sender is not the _addr param at actionMock thpugh the generic scheme not . + var organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); + assert.equal(organizationProposal.exist,true);//new contract address + assert.equal(organizationProposal.passed,true);//new contract address + //can call execute + await testSetup.genericScheme.execute( proposalId); + }); + + + it("execute proposeVote -positive decision - destination reverts and then active", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,actionMock.address); + var activationTime = (await web3.eth.getBlock("latest")).timestamp + 1000; + await actionMock.setActivationTime(activationTime); + var callData = await new web3.eth.Contract(actionMock.abi).methods.test3().encodeABI(); + var tx = await testSetup.genericScheme.proposeCall(callData,0,helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + + await testSetup.genericSchemeParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + //actionMock revert because msg.sender is not the _addr param at actionMock thpugh the generic scheme not . + var organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); + assert.equal(organizationProposal.exist,true);//new contract address + assert.equal(organizationProposal.passed,true);//new contract address + //can call execute + await testSetup.genericScheme.execute( proposalId); + await helpers.increaseTime(1001); + await testSetup.genericScheme.execute( proposalId); + + organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); + assert.equal(organizationProposal.exist,false);//new contract address + assert.equal(organizationProposal.passed,false);//new contract address + try { + await testSetup.genericScheme.execute( proposalId); + assert(false, "cannot call execute after it been executed"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + + + it("execute proposeVote without return value-positive decision - check action", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,actionMock.address); + const encodeABI = await new web3.eth.Contract(actionMock.abi).methods.withoutReturnValue(testSetup.org.avatar.address).encodeABI(); + var tx = await testSetup.genericScheme.proposeCall(encodeABI,0,helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + + await testSetup.genericSchemeParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + + }); + + it("execute should fail if not executed from votingMachine", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,actionMock.address); + const encodeABI = await new web3.eth.Contract(actionMock.abi).methods.withoutReturnValue(testSetup.org.avatar.address).encodeABI(); + var tx = await testSetup.genericScheme.proposeCall(encodeABI,0,helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + + try { + await testSetup.genericScheme.execute( proposalId); + assert(false, "execute should fail if not executed from votingMachine"); + } catch(error) { + helpers.assertVMException(error); + } + + }); + + it("execute proposeVote -positive decision - check action - with GenesisProtocol", async function() { + var actionMock =await ActionMock.new(); + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,actionMock.address,0,true,standardTokenMock.address); + var value = 123; + var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + var tx = await testSetup.genericScheme.proposeCall(callData,value,helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + //transfer some eth to avatar + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value: web3.utils.toWei('1', "ether")}); + assert.equal(await web3.eth.getBalance(actionMock.address),0); + tx = await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.genericScheme.getPastEvents('ProposalExecutedByVotingMachine', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"ProposalExecutedByVotingMachine"); + assert.equal(events[0].args._param,1); + }); + assert.equal(await web3.eth.getBalance(actionMock.address),value); + }); + + it("execute proposeVote -negative decision - check action - with GenesisProtocol", async function() { + var actionMock =await ActionMock.new(); + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,actionMock.address,0,true,standardTokenMock.address); + + var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + var tx = await testSetup.genericScheme.proposeCall(callData,0,helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + tx = await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,2,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.genericScheme.getPastEvents('ProposalExecutedByVotingMachine', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"ProposalExecutedByVotingMachine"); + assert.equal(events[0].args._param,2); + }); + }); + + it("Wallet - execute proposeVote -positive decision - check action - with GenesisProtocol", async function() { + var wallet =await Wallet.new(); + await web3.eth.sendTransaction({from:accounts[0],to:wallet.address, value: web3.utils.toWei('1', "ether")}); + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,wallet.address,0,true,standardTokenMock.address); + var callData = await new web3.eth.Contract(wallet.abi).methods.pay(accounts[1]).encodeABI(); + var tx = await testSetup.genericScheme.proposeCall(callData,0,helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + assert.equal(await web3.eth.getBalance(wallet.address),web3.utils.toWei('1', "ether")); + await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + assert.equal(await web3.eth.getBalance(wallet.address),web3.utils.toWei('1', "ether")); + await wallet.transferOwnership(testSetup.org.avatar.address); + await testSetup.genericScheme.execute( proposalId); + assert.equal(await web3.eth.getBalance(wallet.address),0); + }); + + it("cannot init twice", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,actionMock.address); + + try { + await testSetup.genericScheme.initialize( + testSetup.org.avatar.address, + accounts[0], + helpers.SOME_HASH, + accounts[0] + ); + assert(false, "cannot init twice"); + } catch(error) { + helpers.assertVMException(error); + } + + }); + +}); From 471a775e3e09e86743267215acdc3c5859c96e69 Mon Sep 17 00:00:00 2001 From: Nico Elzer Date: Thu, 3 Sep 2020 08:25:47 +0200 Subject: [PATCH 6/8] GenericSchemeMultiCall tests --- test/genericschememulticall.js | 79 +++++++++++++++++----------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/test/genericschememulticall.js b/test/genericschememulticall.js index d2f5bb9a..71ddd7bd 100644 --- a/test/genericschememulticall.js +++ b/test/genericschememulticall.js @@ -8,7 +8,7 @@ const ERC20Mock = artifacts.require("./ERC20Mock.sol"); const ActionMock = artifacts.require("./ActionMock.sol"); const Wallet = artifacts.require("./Wallet.sol"); -export class GenericSchemeMultiCallParams { +export class GenericSchemeParams { constructor() { } } @@ -16,7 +16,7 @@ export class GenericSchemeMultiCallParams { const setupGenericSchemeParams = async function( genericScheme, accounts, - contractToCall, + contractWhitelist, genesisProtocol = false, tokenAddress = 0, avatar @@ -28,7 +28,7 @@ const setupGenericSchemeParams = async function( avatar.address, genericSchemeParams.votingMachine.genesisProtocol.address, genericSchemeParams.votingMachine.params, - contractToCall); + contractWhitelist); } else { genericSchemeParams.votingMachine = await helpers.setupAbsoluteVote(helpers.NULL_ADDRESS,50,genericScheme.address); @@ -36,31 +36,30 @@ const setupGenericSchemeParams = async function( avatar.address, genericSchemeParams.votingMachine.absoluteVote.address, genericSchemeParams.votingMachine.params, - contractToCall); + contractWhitelist); } return genericSchemeParams; }; -const setup = async function (accounts,contractToCall = 0,reputationAccount=0,genesisProtocol = false,tokenAddress=0) { +const setup = async function (accounts,contractsWhitelist,reputationAccount=0,genesisProtocol = false,tokenAddress=0) { var testSetup = new helpers.TestSetup(); testSetup.standardTokenMock = await ERC20Mock.new(accounts[1],100); - testSetup.genericScheme = await GenericScheme.new(); + testSetup.GenericSchemeMultiCall = await GenericSchemeMultiCall.new(); var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT}); var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT}); testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,daoTracker.address,{gas:constants.ARC_GAS_LIMIT}); testSetup.reputationArray = [20,10,70]; - if (reputationAccount === 0) { testSetup.org = await helpers.setupOrganizationWithArrays(testSetup.daoCreator,[accounts[0],accounts[1],accounts[2]],[1000,1000,1000],testSetup.reputationArray); } else { testSetup.org = await helpers.setupOrganizationWithArrays(testSetup.daoCreator,[accounts[0],accounts[1],reputationAccount],[1000,1000,1000],testSetup.reputationArray); } - testSetup.genericSchemeParams= await setupGenericSchemeParams(testSetup.genericScheme,accounts,contractToCall,genesisProtocol,tokenAddress,testSetup.org.avatar); + testSetup.genericSchemeParams= await setupGenericSchemeParams(testSetup.GenericSchemeMultiCall,accounts,contractsWhitelist,genesisProtocol,tokenAddress,testSetup.org.avatar); var permissions = "0x00000010"; await testSetup.daoCreator.setSchemes(testSetup.org.avatar.address, - [testSetup.genericScheme.address], + [testSetup.GenericSchemeMultiCall.address], [helpers.NULL_HASH],[permissions],"metaData"); return testSetup; @@ -70,64 +69,64 @@ const createCallToActionMock = async function(_avatar,_actionMock) { return await new web3.eth.Contract(_actionMock.abi).methods.test2(_avatar).encodeABI(); }; -contract('GenericScheme', function(accounts) { +contract('GenericSchemeMultiCall', function(accounts) { before(function() { helpers.etherForEveryone(accounts); }); - it("proposeCall log", async function() { - - var actionMock =await ActionMock.new(); - var testSetup = await setup(accounts,actionMock.address); - var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); - - var tx = await testSetup.genericScheme.proposeCall( - callData,0,helpers.NULL_HASH); - assert.equal(tx.logs.length, 1); - assert.equal(tx.logs[0].event, "NewCallProposal"); + it.only("proposeCall log", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,[actionMock.address]); + var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + var tx = await testSetup.GenericSchemeMultiCall.proposeCalls( + [actionMock.address],[callData],[0],helpers.NULL_HASH); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "NewMultiCallProposal"); }); - it("execute proposeCall -no decision - proposal data delete", async function() { + it.only("execute proposeCall -no decision - proposal data delete", async function() { var actionMock =await ActionMock.new(); - var testSetup = await setup(accounts,actionMock.address); + var testSetup = await setup(accounts,[actionMock.address]); var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); - var tx = await testSetup.genericScheme.proposeCall(callData,0,helpers.NULL_HASH); + var tx = await testSetup.GenericSchemeMultiCall.proposeCalls( + [actionMock.address],[callData],[0],helpers.NULL_HASH); var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); await testSetup.genericSchemeParams.votingMachine.absoluteVote.vote(proposalId,0,0,helpers.NULL_ADDRESS,{from:accounts[2]}); //check organizationsProposals after execution - var organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); - assert.equal(organizationProposal.passed,false); - assert.equal(organizationProposal.callData,null); + var proposal = await testSetup.GenericSchemeMultiCall.proposals(proposalId); + assert.equal(proposal.passed,false); + assert.equal(proposal.callData,null); }); - it("execute proposeVote -positive decision - proposal data delete", async function() { + it.only("execute proposeVote -positive decision - proposal data delete", async function() { var actionMock =await ActionMock.new(); - var testSetup = await setup(accounts,actionMock.address); + var testSetup = await setup(accounts,[actionMock.address]); var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); - var tx = await testSetup.genericScheme.proposeCall(callData,0,helpers.NULL_HASH); + var tx = await testSetup.GenericSchemeMultiCall.proposeCalls( + [actionMock.address],[callData],[0],helpers.NULL_HASH); var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); - var organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); - assert.equal(organizationProposal[0],callData,helpers.NULL_HASH); + var proposal = await testSetup.GenericSchemeMultiCall.proposals(proposalId); await testSetup.genericSchemeParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); //check organizationsProposals after execution - organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); - assert.equal(organizationProposal.callData,null);//new contract address + proposal = await testSetup.GenericSchemeMultiCall.proposals(proposalId); + assert.equal(proposal.callData,null);//new contract address }); - it("execute proposeVote -positive decision - destination reverts", async function() { + it.only("execute proposeVote -positive decision - destination reverts", async function() { var actionMock =await ActionMock.new(); - var testSetup = await setup(accounts,actionMock.address); + var testSetup = await setup(accounts,[actionMock.address]); var callData = await createCallToActionMock(helpers.NULL_ADDRESS,actionMock); - var tx = await testSetup.genericScheme.proposeCall(callData,0,helpers.NULL_HASH); + var tx = await testSetup.GenericSchemeMultiCall.proposeCalls( + [actionMock.address],[callData],[0],helpers.NULL_HASH); var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); await testSetup.genericSchemeParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); //actionMock revert because msg.sender is not the _addr param at actionMock thpugh the generic scheme not . - var organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId); - assert.equal(organizationProposal.exist,true);//new contract address - assert.equal(organizationProposal.passed,true);//new contract address + var proposal = await testSetup.GenericSchemeMultiCall.proposals(proposalId); + assert.equal(proposal.exist,true);//new contract address + assert.equal(proposal.passed,true);//new contract address //can call execute - await testSetup.genericScheme.execute( proposalId); + await testSetup.GenericSchemeMultiCall.execute( proposalId); }); From 1a46a755d0263c8115afe129cdb48e6ebccba8ee Mon Sep 17 00:00:00 2001 From: Nico Elzer <> Date: Thu, 3 Sep 2020 11:23:38 +0200 Subject: [PATCH 7/8] . --- contracts/schemes/GenericSchemeMultiCall.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/schemes/GenericSchemeMultiCall.sol b/contracts/schemes/GenericSchemeMultiCall.sol index 38127e4e..35536bb4 100644 --- a/contracts/schemes/GenericSchemeMultiCall.sol +++ b/contracts/schemes/GenericSchemeMultiCall.sol @@ -6,11 +6,11 @@ import "@daostack/infra/contracts/votingMachines/ProposalExecuteInterface.sol"; import "../votingMachines/VotingMachineCallbacks.sol"; /** - * @title GenericScheme. + * @title GenericSchemeMultiCall. * @dev A scheme for proposing and executing calls to multiple arbitrary function - * on whitelisted contract on behalf of the organization avatar. + * on one or multiple contracts on behalf of the organization avatar. */ -contract GenericSchemeMulticall is VotingMachineCallbacks, ProposalExecuteInterface { +contract GenericSchemeMultiCall is VotingMachineCallbacks, ProposalExecuteInterface { event NewMultiCallProposal( address indexed _avatar, bytes32 indexed _proposalId, From f8e3aafdea567f9bd7d2449fb1f21257bf18f5b0 Mon Sep 17 00:00:00 2001 From: Nico Elzer <> Date: Thu, 3 Sep 2020 16:19:52 +0200 Subject: [PATCH 8/8] Added GenericSchemeMultiCall tests --- contracts/schemes/GenericSchemeMultiCall.sol | 7 +- test/genericschememulticall.js | 239 +++++++++---------- 2 files changed, 120 insertions(+), 126 deletions(-) diff --git a/contracts/schemes/GenericSchemeMultiCall.sol b/contracts/schemes/GenericSchemeMultiCall.sol index 97b35aa7..e4b7da2d 100644 --- a/contracts/schemes/GenericSchemeMultiCall.sol +++ b/contracts/schemes/GenericSchemeMultiCall.sol @@ -140,7 +140,6 @@ contract GenericSchemeMultiCall is VotingMachineCallbacks, ProposalExecuteInterf ); controller.externalTokenApproval(extToken,spender,valueToSpend,avatar); } else { - require(contractWhitelist[proposal.contractsToCall[i]], "contractToCall is not whitelisted"); (success, genericCallReturnValue) = controller.genericCall(proposal.contractsToCall[i], proposal.callData[i], avatar, proposal.value[i]); } @@ -171,6 +170,12 @@ contract GenericSchemeMultiCall is VotingMachineCallbacks, ProposalExecuteInterf (_contractsToCall.length == _callData.length) && (_contractsToCall.length == _value.length), "Wrong length of _contractsToCall, _callData or _value arrays" ); + for (uint i = 0; i < _contractsToCall.length; i ++) { + require( + contractWhitelist[_contractsToCall[i]] || _contractsToCall[i] == avatar.owner(), + "contractToCall is not whitelisted" + ); + } proposalId = votingMachine.propose(2, voteParams, msg.sender, address(avatar)); proposals[proposalId] = MultiCallProposal({ diff --git a/test/genericschememulticall.js b/test/genericschememulticall.js index 64fc524a..0d961952 100644 --- a/test/genericschememulticall.js +++ b/test/genericschememulticall.js @@ -161,19 +161,13 @@ contract('GenericSchemeMultiCall', function(accounts) { var actionMock =await ActionMock.new(); var testSetup = await setup(accounts,[accounts[1]]); var callData = await createCallToActionMock(helpers.NULL_ADDRESS,actionMock); - var tx = await testSetup.GenericSchemeMultiCall.proposeCalls( - [actionMock.address],[callData],[0],helpers.NULL_HASH); - var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); - //actionMock revert because msg.sender is not the _addr param at actionMock though the whole proposal execution will fail. try { - await testSetup.genericSchemeParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.GenericSchemeMultiCall.proposeCalls( + [actionMock.address],[callData],[0],helpers.NULL_HASH); assert(false, "contractToCall is not whitelisted"); } catch(error) { helpers.assertVMException(error); } - var proposal = await testSetup.GenericSchemeMultiCall.proposals(proposalId); - assert.equal(proposal.exist,true); - assert.equal(proposal.passed,false); }); it("execute proposeVote without return value-positive decision - check action", async function() { @@ -191,7 +185,6 @@ contract('GenericSchemeMultiCall', function(accounts) { const encodeABI = await new web3.eth.Contract(actionMock.abi).methods.withoutReturnValue(testSetup.org.avatar.address).encodeABI(); var tx = await testSetup.GenericSchemeMultiCall.proposeCalls([actionMock.address],[encodeABI],[0],helpers.NULL_HASH); var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); - try { await testSetup.GenericSchemeMultiCall.execute( proposalId); assert(false, "execute should fail if not executed from votingMachine"); @@ -243,131 +236,127 @@ contract('GenericSchemeMultiCall', function(accounts) { }); }); - it("execute proposeVote with multiple calls -positive decision - check action - with GenesisProtocol", async function() { - var actionMock =await ActionMock.new(); - var actionMock2 =await ActionMock.new(); - var standardTokenMock = await ERC20Mock.new(accounts[0],1000); - var testSetup = await setup(accounts,[actionMock.address,actionMock2.address],0,true,standardTokenMock.address); - - var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); - var callData2 = await createCallToActionMock(testSetup.org.avatar.address,actionMock); - var tx = await testSetup.GenericSchemeMultiCall.proposeCalls([actionMock.address,actionMock2.address],[callData,callData2],[0,0],helpers.NULL_HASH); - var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); - tx = await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - await testSetup.GenericSchemeMultiCall.getPastEvents('ProposalExecutedByVotingMachine', { - fromBlock: tx.blockNumber, - toBlock: 'latest' - }) - .then(function(events){ - assert.equal(events[0].event,"ProposalExecutedByVotingMachine"); - assert.equal(events[0].args._param,1); - }); - }); + it("execute proposeVote with multiple calls -positive decision - check action - with GenesisProtocol", async function() { + var actionMock =await ActionMock.new(); + var actionMock2 =await ActionMock.new(); + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,[actionMock.address,actionMock2.address],0,true,standardTokenMock.address); - it("execute proposeVote with multiple calls -positive decision - one failed transaction", async function() { - var actionMock =await ActionMock.new(); - var actionMock2 =await ActionMock.new(); - var standardTokenMock = await ERC20Mock.new(accounts[0],1000); - var testSetup = await setup(accounts,[actionMock.address,actionMock2.address],0,true,standardTokenMock.address); - var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); - var callData2 = await createCallToActionMock(accounts[0],actionMock); - var tx = await testSetup.GenericSchemeMultiCall.proposeCalls([actionMock.address,actionMock2.address],[callData,callData2],[0,0],helpers.NULL_HASH); - var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); - var proposal = await testSetup.GenericSchemeMultiCall.proposals(proposalId); - assert.equal(proposal.exist,true); - assert.equal(proposal.passed,false); - await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - await testSetup.GenericSchemeMultiCall.getPastEvents('ProposalCallExecuted', { - fromBlock: tx.blockNumber, - toBlock: 'latest' - }) - .then(function(events){ - assert.equal(events[0].event,"ProposalCallExecuted"); - assert.equal(events[0].args._success,true); - assert.equal(events[1].event,"ProposalCallExecuted"); - assert.equal(events[1].args._success,false); - }); + var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + var callData2 = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + var tx = await testSetup.GenericSchemeMultiCall.proposeCalls([actionMock.address,actionMock2.address],[callData,callData2],[0,0],helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + tx = await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.GenericSchemeMultiCall.getPastEvents('ProposalExecutedByVotingMachine', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"ProposalExecutedByVotingMachine"); + assert.equal(events[0].args._param,1); }); + }); - it.skip("execute proposeVote with multiple calls with votingMachine -positive decision", async function() { - var actionMock =await ActionMock.new(); - var standardTokenMock = await ERC20Mock.new(accounts[0],1000); - var testSetup = await setup(accounts,[actionMock.address],0,true,standardTokenMock.address); - var avatarInst = await new web3.eth.Contract(testSetup.org.avatar.abi,testSetup.org.avatar.address); - var controllerAddr = await avatarInst.methods.owner().call() - var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); - var tx = await testSetup.GenericSchemeMultiCall.proposeCalls([actionMock.address],[callData],[0],helpers.NULL_HASH); - var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); - var proposal = await testSetup.GenericSchemeMultiCall.proposals(proposalId); - assert.equal(proposal.exist,true); - assert.equal(proposal.passed,false); - await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - /* - await testSetup.GenericSchemeMultiCall.getPastEvents('ProposalCallExecuted', { - fromBlock: tx.blockNumber, - toBlock: 'latest' - }) - .then(function(events){ - assert.equal(events[0].event,"ProposalCallExecuted"); - assert.equal(events[0].args._success,true); - assert.equal(events[1].event,"ProposalCallExecuted"); - assert.equal(events[1].args._success,false); - }); - */ + it("execute proposeVote with multiple calls -positive decision - one failed transaction", async function() { + var actionMock =await ActionMock.new(); + var actionMock2 =await ActionMock.new(); + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,[actionMock.address,actionMock2.address],0,true,standardTokenMock.address); + var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + var callData2 = await createCallToActionMock(accounts[0],actionMock); + var tx = await testSetup.GenericSchemeMultiCall.proposeCalls([actionMock.address,actionMock2.address],[callData,callData2],[0,0],helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + var proposal = await testSetup.GenericSchemeMultiCall.proposals(proposalId); + assert.equal(proposal.exist,true); + assert.equal(proposal.passed,false); + await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.GenericSchemeMultiCall.getPastEvents('ProposalCallExecuted', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"ProposalCallExecuted"); + assert.equal(events[0].args._success,true); + assert.equal(events[1].event,"ProposalCallExecuted"); + assert.equal(events[1].args._success,false); }); + }); - - it("cannot init without contract whitelist", async function() { - var actionMock =await ActionMock.new(); - var testSetup = await setup(accounts,[actionMock.address]); - var genericSchemeMultiCall =await GenericSchemeMultiCall.new(); - - try { - await genericSchemeMultiCall.initialize( - testSetup.org.avatar.address, - accounts[0], - helpers.SOME_HASH, - [] - ); - assert(false, "contractWhitelist cannot be empty"); - } catch(error) { - helpers.assertVMException(error); - } - + it("execute proposeVote with multiple calls with votingMachine -positive decision", async function() { + var actionMock =await ActionMock.new(); + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,[actionMock.address],0,true,standardTokenMock.address); + var avatarInst = await new web3.eth.Contract(testSetup.org.avatar.abi,testSetup.org.avatar.address); + var controllerAddr = await avatarInst.methods.owner().call() + var encodedTokenApproval= await web3.eth.abi.encodeParameters(['address','address', 'uint256'], [standardTokenMock.address, accounts[3], 1000]); + var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock); + var tx = await testSetup.GenericSchemeMultiCall.proposeCalls([actionMock.address,controllerAddr],[callData,encodedTokenApproval],[0,0],helpers.NULL_HASH); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId'); + var proposal = await testSetup.GenericSchemeMultiCall.proposals(proposalId); + assert.equal(proposal.exist,true); + assert.equal(proposal.passed,false); + await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.GenericSchemeMultiCall.getPastEvents('ProposalCallExecuted', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"ProposalCallExecuted"); + assert.equal(events[0].args._success,true); + assert.equal(events[1].event,"ProposalCallExecuted"); + assert.equal(events[1].args._success,true); }); + }); - it("cannot init twice", async function() { - var actionMock =await ActionMock.new(); - var testSetup = await setup(accounts,[actionMock.address]); - - try { - await testSetup.GenericSchemeMultiCall.initialize( - testSetup.org.avatar.address, - accounts[0], - helpers.SOME_HASH, - [accounts[0]] - ); - assert(false, "cannot init twice"); - } catch(error) { - helpers.assertVMException(error); - } + it("cannot init without contract whitelist", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,[actionMock.address]); + var genericSchemeMultiCall =await GenericSchemeMultiCall.new(); + + try { + await genericSchemeMultiCall.initialize( + testSetup.org.avatar.address, + accounts[0], + helpers.SOME_HASH, + [] + ); + assert(false, "contractWhitelist cannot be empty"); + } catch(error) { + helpers.assertVMException(error); + } - }); + }); - it("can init with multiple contracts on whitelist", async function() { - var actionMock =await ActionMock.new(); - var testSetup = await setup(accounts,[actionMock.address]); - var genericSchemeMultiCall =await GenericSchemeMultiCall.new(); - await genericSchemeMultiCall.initialize( - testSetup.org.avatar.address, - accounts[0], - helpers.SOME_HASH, - [accounts[0],accounts[1],accounts[2],accounts[3]] + it("cannot init twice", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,[actionMock.address]); + try { + await testSetup.GenericSchemeMultiCall.initialize( + testSetup.org.avatar.address, + accounts[0], + helpers.SOME_HASH, + [accounts[0]] ); - assert.equal(await genericSchemeMultiCall.whitelistedContracts(0),accounts[0]); - assert.equal(await genericSchemeMultiCall.whitelistedContracts(1),accounts[1]); - assert.equal(await genericSchemeMultiCall.whitelistedContracts(2),accounts[2]); - assert.equal(await genericSchemeMultiCall.whitelistedContracts(3),accounts[3]); - }); + assert(false, "cannot init twice"); + } catch(error) { + helpers.assertVMException(error); + } + }); + it("can init with multiple contracts on whitelist", async function() { + var actionMock =await ActionMock.new(); + var testSetup = await setup(accounts,[actionMock.address]); + var genericSchemeMultiCall =await GenericSchemeMultiCall.new(); + await genericSchemeMultiCall.initialize( + testSetup.org.avatar.address, + accounts[0], + helpers.SOME_HASH, + [accounts[0],accounts[1],accounts[2],accounts[3]] + ); + assert.equal(await genericSchemeMultiCall.whitelistedContracts(0),accounts[0]); + assert.equal(await genericSchemeMultiCall.whitelistedContracts(1),accounts[1]); + assert.equal(await genericSchemeMultiCall.whitelistedContracts(2),accounts[2]); + assert.equal(await genericSchemeMultiCall.whitelistedContracts(3),accounts[3]); + }); + });