Skip to content

Commit

Permalink
Redeemer for GSMC (#809)
Browse files Browse the repository at this point in the history
* Redeemer for GSMC

* cov

* Check proposal passed before executing

* Update Redeemer.sol

* Genericscheme redeemer

* bump fee
  • Loading branch information
ben-kaufman authored Dec 13, 2020
1 parent 1b15ff2 commit 5bce4a0
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 2 deletions.
94 changes: 94 additions & 0 deletions contracts/utils/Redeemer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ pragma solidity 0.5.17;

import "../universalSchemes/ContributionReward.sol";
import "../schemes/ContributionRewardExt.sol";
import "../schemes/GenericSchemeMultiCall.sol";
import "../schemes/GenericScheme.sol";
import "@daostack/infra/contracts/votingMachines/GenesisProtocol.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";

Expand Down Expand Up @@ -121,6 +123,98 @@ contract Redeemer {
}
}

/**
* @dev helper to redeem proposal and execute the multi-call
* It calls execute on the proposal if it is not yet executed.
* It tries to redeem reputation and stake from the GenesisProtocol.
* It tries to execute multi call to contracts from the GenericSchemeMultiCall
* This function does not emit events.
* A client should listen to GenesisProtocol and GenericSchemeMultiCall events
* to monitor redemption and execution operations.
* @param _genericSchemeMultiCall GenericSchemeMultiCall scheme
* @param _genesisProtocol genesisProtocol
* @param _proposalId the ID of the voting in the voting machine
* @param _beneficiary beneficiary
* @return gpRewards array
* gpRewards[0] - stakerTokenAmount
* gpRewards[1] - voterReputationAmount
* gpRewards[2] - proposerReputationAmount
* @return gpDaoBountyReward array
* gpDaoBountyReward[0] - staker dao bounty reward -
* will be zero for the case there is not enough tokens in avatar for the reward.
* gpDaoBountyReward[1] - staker potential dao bounty reward.
* @return executed bool true or false
* @return winningVote
* 1 - executed or closed and the winning vote is YES
* 2 - executed or closed and the winning vote is NO
*/
function redeemGenericSchemeMultiCall(GenericSchemeMultiCall _genericSchemeMultiCall,
GenesisProtocol _genesisProtocol,
bytes32 _proposalId,
address _beneficiary)
external
returns(uint[3] memory gpRewards,
uint[2] memory gpDaoBountyReward,
bool executed,
uint256 winningVote
)
{
bool callGenericSchemeMultiCall;
(gpRewards, gpDaoBountyReward, executed, winningVote, callGenericSchemeMultiCall) =
genesisProtocolRedeem(_genesisProtocol, _proposalId, _beneficiary);
bool passedAndNotExecuted;
(,passedAndNotExecuted) = _genericSchemeMultiCall.proposals(_proposalId);
if (callGenericSchemeMultiCall && passedAndNotExecuted) {
_genericSchemeMultiCall.execute(_proposalId);
}
}

/**
* @dev helper to redeem proposal and execute the call
* It calls execute on the proposal if it is not yet executed.
* It tries to redeem reputation and stake from the GenesisProtocol.
* It tries to execute call to contract from the GenericScheme
* This function does not emit events.
* A client should listen to GenesisProtocol and GenericScheme events
* to monitor redemption and execution operations.
* @param _genericScheme GenericScheme scheme
* @param _genesisProtocol genesisProtocol
* @param _proposalId the ID of the voting in the voting machine
* @param _beneficiary beneficiary
* @return gpRewards array
* gpRewards[0] - stakerTokenAmount
* gpRewards[1] - voterReputationAmount
* gpRewards[2] - proposerReputationAmount
* @return gpDaoBountyReward array
* gpDaoBountyReward[0] - staker dao bounty reward -
* will be zero for the case there is not enough tokens in avatar for the reward.
* gpDaoBountyReward[1] - staker potential dao bounty reward.
* @return executed bool true or false
* @return winningVote
* 1 - executed or closed and the winning vote is YES
* 2 - executed or closed and the winning vote is NO
*/
function redeemGenericScheme(GenericScheme _genericScheme,
GenesisProtocol _genesisProtocol,
bytes32 _proposalId,
address _beneficiary)
external
returns(uint[3] memory gpRewards,
uint[2] memory gpDaoBountyReward,
bool executed,
uint256 winningVote
)
{
bool callGenericScheme;
(gpRewards, gpDaoBountyReward, executed, winningVote, callGenericScheme) =
genesisProtocolRedeem(_genesisProtocol, _proposalId, _beneficiary);
bool passedAndNotExecuted;
(,,,passedAndNotExecuted) = _genericScheme.organizationProposals(_proposalId);
if (callGenericScheme && passedAndNotExecuted) {
_genericScheme.execute(_proposalId);
}
}

function genesisProtocolRedeem(GenesisProtocol _genesisProtocol,
bytes32 _proposalId,
address _beneficiary)
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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.51",
"version": "0.0.1-rc.52",
"description": "A platform for building DAOs",
"files": [
"contracts/",
Expand Down
38 changes: 38 additions & 0 deletions test/genericscheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const DAOTracker = artifacts.require("./DAOTracker.sol");
const ERC20Mock = artifacts.require("./ERC20Mock.sol");
const ActionMock = artifacts.require("./ActionMock.sol");
const Wallet = artifacts.require("./Wallet.sol");
const Redeemer = artifacts.require("./Redeemer.sol");

class GenericSchemeParams {
constructor() {
Expand Down Expand Up @@ -162,6 +163,43 @@ contract('GenericScheme', function(accounts) {
});


it("execute proposeVote -positive decision - destination reverts and then active and executed with redeemer", 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 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.genesisProtocol.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 helpers.increaseTime(1001);
var redeemer = await Redeemer.new();
var redeemRewards = await redeemer.redeemGenericScheme.call(
testSetup.genericScheme.address,
testSetup.genericSchemeParams.votingMachine.genesisProtocol.address,
proposalId,
accounts[0]);
assert.equal(redeemRewards[0][1],0); //redeemRewards[0] gpRewards
assert.equal(redeemRewards[0][2],60);
assert.equal(redeemRewards.executed,false); // GP already executed by vote
assert.equal(redeemRewards.winningVote,1);
tx = await redeemer.redeemGenericScheme(
testSetup.genericScheme.address,
testSetup.genericSchemeParams.votingMachine.genesisProtocol.address,
proposalId,
accounts[0]);
organizationProposal = await testSetup.genericScheme.organizationProposals(proposalId);
assert.equal(organizationProposal.exist,false);//new contract address
assert.equal(organizationProposal.passed,false);//new contract address
});


it("execute proposeVote without return value-positive decision - check action", async function() {
var actionMock =await ActionMock.new();
Expand Down
71 changes: 71 additions & 0 deletions test/genericschememulticall.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const ERC20Mock = artifacts.require("./ERC20Mock.sol");
const ActionMock = artifacts.require("./ActionMock.sol");
const DxDaoSchemeConstraints = artifacts.require("./DxDaoSchemeConstraints.sol");
const SimpleSchemeConstraints = artifacts.require("./SimpleSchemeConstraints.sol");
const Redeemer = artifacts.require("./Redeemer.sol");

class GenericSchemeParams {
constructor() {
Expand Down Expand Up @@ -291,6 +292,76 @@ contract('GenericSchemeMultiCall', function(accounts) {
});
});

it("redeemer should fail if not executed from votingMachine", 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);
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');
var redeemer = await Redeemer.new();
var redeemRewards = await redeemer.redeemGenericSchemeMultiCall.call(
testSetup.genericSchemeMultiCall.address,
testSetup.genericSchemeParams.votingMachine.genesisProtocol.address,
proposalId,
accounts[0]);
assert.equal(redeemRewards[0][1],0); //redeemRewards[0] gpRewards
assert.equal(redeemRewards[0][2],0);
assert.equal(redeemRewards.executed,false);
assert.equal(redeemRewards.winningVote,0); // Cannot redeem, so will not get the winning vote
tx = await redeemer.redeemGenericSchemeMultiCall(
testSetup.genericSchemeMultiCall.address,
testSetup.genericSchemeParams.votingMachine.genesisProtocol.address,
proposalId,
accounts[0]);
await testSetup.genericSchemeMultiCall.getPastEvents('ProposalExecuted', {
fromBlock: tx.blockNumber,
toBlock: 'latest'
})
.then(function(events){
assert.equal(events.length,0);
});
});

it("execute proposeVote -positive decision - execute with redeemer", 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 = 50000;
var callData = await createCallToActionMock(testSetup.org.avatar.address,actionMock);
var tx = await testSetup.genericSchemeMultiCall.proposeCalls([actionMock.address,actionMock.address],[callData,callData],[value,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);
await testSetup.genericSchemeParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]});
var redeemer = await Redeemer.new();
var redeemRewards = await redeemer.redeemGenericSchemeMultiCall.call(
testSetup.genericSchemeMultiCall.address,
testSetup.genericSchemeParams.votingMachine.genesisProtocol.address,
proposalId,
accounts[0]);
assert.equal(redeemRewards[0][1],0); //redeemRewards[0] gpRewards
assert.equal(redeemRewards[0][2],60);
assert.equal(redeemRewards.executed,false); // GP already executed by vote
assert.equal(redeemRewards.winningVote,1);
tx = await redeemer.redeemGenericSchemeMultiCall(
testSetup.genericSchemeMultiCall.address,
testSetup.genericSchemeParams.votingMachine.genesisProtocol.address,
proposalId,
accounts[0]);
await testSetup.genericSchemeMultiCall.getPastEvents('ProposalExecuted', {
fromBlock: tx.blockNumber,
toBlock: 'latest'
})
.then(function(events){
assert.equal(events[0].event,"ProposalExecuted");
assert.equal(events[0].args._proposalId,proposalId);
});
assert.equal(await web3.eth.getBalance(actionMock.address),value*2);
});


it("schemeconstrains eth value exceed limit", async function() {
var actionMock =await ActionMock.new();
var standardTokenMock = await ERC20Mock.new(accounts[0],1000);
Expand Down

0 comments on commit 5bce4a0

Please sign in to comment.