From 5de357257911bf832d32251c19c21ccd84a5a381 Mon Sep 17 00:00:00 2001 From: Nick Freyaldenhoven Date: Thu, 7 Nov 2019 09:53:01 -0600 Subject: [PATCH 1/5] fixing lint issues --- packages/kosu-solidity-tests/test/treasury.ts | 4 ++-- packages/kosu-solidity-tests/test/voting.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kosu-solidity-tests/test/treasury.ts b/packages/kosu-solidity-tests/test/treasury.ts index 8ab24583..ae7fddb8 100644 --- a/packages/kosu-solidity-tests/test/treasury.ts +++ b/packages/kosu-solidity-tests/test/treasury.ts @@ -502,8 +502,8 @@ describe("Treasury", async () => { const salt = new BigNumber("42"); const vote1 = new BigNumber("1"); const vote2 = new BigNumber("2"); - const secret1 = soliditySha3({ t: "uint", v: new BigNumber("1") }, { t: "uint", v: salt }); - const secret2 = soliditySha3({ t: "uint", v: new BigNumber("2") }, { t: "uint", v: salt }); + const secret1 = soliditySha3({ t: "uint", v: "1" }, { t: "uint", v: salt.toString() }); + const secret2 = soliditySha3({ t: "uint", v: "2" }, { t: "uint", v: salt.toString() }); it("should lock a validator after exit", async () => { await testHelpers.prepareListing("0x010203", { diff --git a/packages/kosu-solidity-tests/test/voting.ts b/packages/kosu-solidity-tests/test/voting.ts index 10f28fe5..86a6f582 100644 --- a/packages/kosu-solidity-tests/test/voting.ts +++ b/packages/kosu-solidity-tests/test/voting.ts @@ -31,8 +31,8 @@ describe("Voting", () => { const vote2 = new BigNumber("2"); const block1 = vote1; const block2 = vote2; - const secret1 = soliditySha3({ t: "uint", v: new BigNumber("1") }, { t: "uint", v: salt }); - const secret2 = soliditySha3({ t: "uint", v: new BigNumber("2") }, { t: "uint", v: salt }); + const secret1 = soliditySha3({ t: "uint", v: "1" }, { t: "uint", v: salt.toString() }); + const secret2 = soliditySha3({ t: "uint", v: "2" }, { t: "uint", v: salt.toString() }); before(async () => { voting = contracts.voting; From 50d18ac112fa29cab0de78e18719359b25b0bd02 Mon Sep 17 00:00:00 2001 From: Nick Freyaldenhoven Date: Thu, 7 Nov 2019 09:53:27 -0600 Subject: [PATCH 2/5] Adding vote proxy changes and tests. --- packages/kosu-solidity-tests/test/treasury.ts | 10 ++ packages/kosu-solidity-tests/test/voting.ts | 59 +++++++++ .../contracts/treasury/Treasury.sol | 107 +++++++++++----- .../contracts/voting/Voting.sol | 114 ++++++++++++------ 4 files changed, 227 insertions(+), 63 deletions(-) diff --git a/packages/kosu-solidity-tests/test/treasury.ts b/packages/kosu-solidity-tests/test/treasury.ts index ae7fddb8..d918f289 100644 --- a/packages/kosu-solidity-tests/test/treasury.ts +++ b/packages/kosu-solidity-tests/test/treasury.ts @@ -595,4 +595,14 @@ describe("Treasury", async () => { .should.eventually.eq((blockNumber + 4 + 10).toString()); }); }); + + describe("authorization", () => { + it("should authorize a proxy", async () => { + await treasury.isProxyFor.callAsync(accounts[0], accounts[1]).should.eventually.be.false; + await treasury.authorizeProxy.awaitTransactionSuccessAsync(accounts[1]); + await treasury.isProxyFor.callAsync(accounts[0], accounts[1]).should.eventually.be.true; + await treasury.deauthorizeProxy.awaitTransactionSuccessAsync(accounts[1]); + await treasury.isProxyFor.callAsync(accounts[0], accounts[1]).should.eventually.be.false; + }); + }); }); diff --git a/packages/kosu-solidity-tests/test/voting.ts b/packages/kosu-solidity-tests/test/voting.ts index 86a6f582..5ef743be 100644 --- a/packages/kosu-solidity-tests/test/voting.ts +++ b/packages/kosu-solidity-tests/test/voting.ts @@ -112,6 +112,23 @@ describe("Voting", () => { }); }); + describe("commitProxyVote", () => { + let pollId; + beforeEach(async () => { + await treasury.authorizeProxy.awaitTransactionSuccessAsync(accounts[1]); + pollId = await shortPoll(); + }); + + afterEach(async () => { + await treasury.deauthorizeProxy.awaitTransactionSuccessAsync(accounts[1]); + }); + + it("should allow a user to commit a proxy vote", async () => { + await voting.commitProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], secret1, TestValues.fiveEther, { from: accounts[1] }).should + .eventually.be.fulfilled; + }); + }); + describe("revealVote", () => { let pollId; beforeEach(async () => { @@ -164,6 +181,24 @@ describe("Voting", () => { }); }); + describe("revealProxyVote", () => { + let pollId; + beforeEach(async () => { + await treasury.authorizeProxy.awaitTransactionSuccessAsync(accounts[1]); + pollId = await shortPoll(); + }); + + afterEach(async () => { + await treasury.deauthorizeProxy.awaitTransactionSuccessAsync(accounts[1]); + }); + + it("should allow a user to reveal a proxy vote", async () => { + await voting.commitProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], secret1, TestValues.fiveEther, { from: accounts[1] }).should + .eventually.be.fulfilled; + await voting.revealProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], vote1, salt, { from: accounts[1] }).should.eventually.be.fulfilled; + }); + }); + describe("winningOption", () => { it("should report the correct winningOption", async () => { await kosuToken.transfer.awaitTransactionSuccessAsync(accounts[1], TestValues.fiveEther); @@ -186,6 +221,30 @@ describe("Voting", () => { .should.eventually.eq("2"); }); + it("should report the correct winningOption for proxy votes", async () => { + await treasury.authorizeProxy.awaitTransactionSuccessAsync(accounts[1]); + await kosuToken.transfer.awaitTransactionSuccessAsync(accounts[1], TestValues.fiveEther); + await testHelpers.prepareTokens(accounts[0], TestValues.fiveEther); + await testHelpers.prepareTokens(accounts[1], TestValues.fiveEther); + const { pollId } = await testHelpers.variablePoll(2, 2); + await voting.commitProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], secret1, TestValues.oneEther, { from: accounts[1] }).should.eventually + .be.fulfilled; + await voting.commitVote.awaitTransactionSuccessAsync(pollId, secret2, TestValues.fiveEther, { + from: accounts[1], + }).should.eventually.be.fulfilled; + await voting.revealProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], vote1, salt, { from: accounts[1] }).should.eventually.be.fulfilled; + await voting.revealVote.awaitTransactionSuccessAsync(pollId, vote2, salt, { from: accounts[1] }).should + .eventually.be.fulfilled; + + await testHelpers.skipBlocks(new BigNumber(1)); + await voting.winningOption + .callAsync(pollId) + .then(x => x.toString()) + .should.eventually.eq("2"); + await treasury.deauthorizeProxy.awaitTransactionSuccessAsync(accounts[1]); + + }); + it("should report the first winning option in a tie", async () => { await kosuToken.transfer.awaitTransactionSuccessAsync(accounts[1], TestValues.fiveEther); await testHelpers.prepareTokens(accounts[0], TestValues.fiveEther); diff --git a/packages/kosu-system-contracts/contracts/treasury/Treasury.sol b/packages/kosu-system-contracts/contracts/treasury/Treasury.sol index 3854f60e..bd873b45 100644 --- a/packages/kosu-system-contracts/contracts/treasury/Treasury.sol +++ b/packages/kosu-system-contracts/contracts/treasury/Treasury.sol @@ -12,6 +12,7 @@ contract Treasury is Authorizable { using SafeMath for uint; IVoting voting; + KosuToken public kosuToken; struct TokenLock { uint value; @@ -19,11 +20,15 @@ contract Treasury is Authorizable { uint tokenLockEnd; uint pollEarlyUnlock; } + struct Account { + uint currentBalance; + uint systemBalance; + TokenLock[] tokenLocks; + mapping(address => bool) proxies; + } + + mapping(address => Account) private accounts; - KosuToken public kosuToken; - mapping(address => uint) private currentBalances; - mapping(address => uint) private systemBalances; - mapping(address => TokenLock[]) private addressTokenLocks; /** @dev Initializes the treasury with the kosuToken and authorizedAddresses contracts. @notice Initializes the treasury with the kosuToken and authorizedAddresses contracts. @@ -160,8 +165,8 @@ contract Treasury is Authorizable { setSystemBalance(account, getSystemBalance(account).sub(amount)); } - /** @dev Allows voting contract to register a poll to ensure tokens aren't removed. - @notice Allows voting contract to register a poll to ensure tokens aren't removed. + /** @dev Allows voting contract to register a vote in a poll to ensure tokens aren't removed. + @notice Allows voting contract to register a vote in a poll to ensure tokens aren't removed. @param account The account voting. @param pollId The poll the account is voting on. @param amount Number of tokens contributed. @@ -169,11 +174,31 @@ contract Treasury is Authorizable { @param losingEndBlock Block number vote token lock should expire if vote was in support of a losing option. */ function registerVote(address account, uint pollId, uint amount, uint endBlock, uint losingEndBlock) isAuthorized public returns (bool) { - if(systemBalances[account] < amount) { + if(getSystemBalance(account) < amount) { + return false; + } + + accounts[account].tokenLocks.push(TokenLock(amount, pollId, endBlock, losingEndBlock)); + + return true; + } + + /** @dev Allows voting contract to register a vote in a poll from a proxy to ensure tokens aren't removed. + @notice Allows voting contract to register a vote in a poll from a proxy to ensure tokens aren't removed. + @param account The account voting. + @param pollId The poll the account is voting on. + @param amount Number of tokens contributed. + @param endBlock Block number vote token lock should expire. + @param losingEndBlock Block number vote token lock should expire if vote was in support of a losing option. + */ + function registerProxyVote(address proxy, address account, uint pollId, uint amount, uint endBlock, uint losingEndBlock) isAuthorized public returns (bool) { + if(getSystemBalance(account) < amount) { return false; } - addressTokenLocks[account].push(TokenLock(amount, pollId, endBlock, losingEndBlock)); + require(isProxyFor(account, proxy), "not valid proxy"); + + accounts[account].tokenLocks.push(TokenLock(amount, pollId, endBlock, losingEndBlock)); return true; } @@ -185,7 +210,7 @@ contract Treasury is Authorizable { @param endBlock The end of the lock. */ function validatorLock(address account, uint amount, uint endBlock) isAuthorized public { - addressTokenLocks[account].push(TokenLock( + accounts[account].tokenLocks.push(TokenLock( amount, 0, endBlock, @@ -200,9 +225,9 @@ contract Treasury is Authorizable { */ function tokenLocksExpire(address account) public returns (uint lastBlock) { _removeInactiveTokenLocks(msg.sender); - for(uint i = addressTokenLocks[account].length; i > 0; i--) { - if(addressTokenLocks[account][i - 1].tokenLockEnd > lastBlock) { - lastBlock = addressTokenLocks[account][i - 1].tokenLockEnd; + for(uint i = accounts[account].tokenLocks.length; i > 0; i--) { + if(accounts[account].tokenLocks[i - 1].tokenLockEnd > lastBlock) { + lastBlock = accounts[account].tokenLocks[i - 1].tokenLockEnd; } } return lastBlock; @@ -226,6 +251,34 @@ contract Treasury is Authorizable { return getCurrentBalance(account); } + /** @dev Authorize voting proxy for treasury balances. + @notice Authorize voting proxy for treasury balances. + @param proxy Address to allow to vote in proxy. + + */ + function authorizeProxy(address proxy) public { + accounts[msg.sender].proxies[proxy] = true; + } + + /** @dev Remove authorization of voting proxy for treasury balance. + @notice Remove authorization of voting proxy for treasury balance. + @param proxy Address to remove permission to vote in proxy. + + */ + function deauthorizeProxy(address proxy) public { + accounts[msg.sender].proxies[proxy] = false; + } + + /** @dev Check proxy authorization for account and proxy addresses. + @notice Check proxy authorization for account and proxy addresses. + @param account User account with tokens. + @param proxy Address to check proxy status. + + */ + function isProxyFor(address account, address proxy) public view returns (bool) { + return accounts[account].proxies[proxy]; + } + // INTERNAL /** @dev Bonds tokens by passing the transaction value to the bonding functions of the KosuToken then provides the added balance to the provided account address. */ @@ -262,20 +315,20 @@ contract Treasury is Authorizable { /** @dev Removes expired locks. */ function _removeInactiveTokenLocks(address account) internal { - for(uint i = addressTokenLocks[account].length; i > 0; i--) { - TokenLock storage v = addressTokenLocks[account][i - 1]; + for(uint i = accounts[account].tokenLocks.length; i > 0; i--) { + TokenLock storage v = accounts[account].tokenLocks[i - 1]; (bool finished, uint winningTokens) = voting.userWinningTokens(v.pollId, msg.sender); if(v.pollId > 0 && finished && winningTokens == 0) { v.tokenLockEnd = v.pollEarlyUnlock; } if(v.tokenLockEnd < block.number) { - if(i == addressTokenLocks[account].length) { - delete addressTokenLocks[account][i - 1]; - addressTokenLocks[account].length = addressTokenLocks[account].length - 1; + if(i == accounts[account].tokenLocks.length) { + delete accounts[account].tokenLocks[i - 1]; + accounts[account].tokenLocks.length = accounts[account].tokenLocks.length - 1; } else { - addressTokenLocks[account][i - 1] = addressTokenLocks[account][addressTokenLocks[account].length - 1]; - delete addressTokenLocks[account][addressTokenLocks[account].length - 1]; - addressTokenLocks[account].length = addressTokenLocks[account].length - 1; + accounts[account].tokenLocks[i - 1] = accounts[account].tokenLocks[accounts[account].tokenLocks.length - 1]; + delete accounts[account].tokenLocks[accounts[account].tokenLocks.length - 1]; + accounts[account].tokenLocks.length = accounts[account].tokenLocks.length - 1; } } } @@ -289,10 +342,10 @@ contract Treasury is Authorizable { //Updates the systemBalance for the account uint totalLocked; uint maxVoteLock; - for(uint i = 0; i < addressTokenLocks[account].length; i++) { - TokenLock storage lock = addressTokenLocks[account][i]; + for(uint i = 0; i < accounts[account].tokenLocks.length; i++) { + TokenLock storage lock = accounts[account].tokenLocks[i]; if(lock.pollId == 0) { - totalLocked = totalLocked.add(addressTokenLocks[account][i].value); + totalLocked = totalLocked.add(accounts[account].tokenLocks[i].value); } else { if (lock.value > maxVoteLock) { maxVoteLock = lock.value; @@ -305,26 +358,26 @@ contract Treasury is Authorizable { */ function getSystemBalance(address account) internal view returns (uint) { //Reports the systemBalance for the account - return systemBalances[account]; + return accounts[account].systemBalance; } /** @dev Sets the accounts system balance. */ function setSystemBalance(address account, uint amount) internal { - systemBalances[account] = amount; + accounts[account].systemBalance = amount; } /** @dev Reads the accounts current balance. */ function getCurrentBalance(address account) internal view returns (uint) { //Reports the held balance for the account - return currentBalances[account]; + return accounts[account].currentBalance; } /** @dev Sets the accounts current balance. */ function setCurrentBalance(address account, uint amount) internal { //Updates the held balance for the account - currentBalances[account] = amount; + accounts[account].currentBalance = amount; } } diff --git a/packages/kosu-system-contracts/contracts/voting/Voting.sol b/packages/kosu-system-contracts/contracts/voting/Voting.sol index 96c1f13e..8c7553dc 100644 --- a/packages/kosu-system-contracts/contracts/voting/Voting.sol +++ b/packages/kosu-system-contracts/contracts/voting/Voting.sol @@ -93,22 +93,23 @@ contract Voting is IVoting { @param _tokensToCommit Number of tokens to commit to vote. */ function commitVote(uint _pollId, bytes32 _vote, uint _tokensToCommit) public { - //load Poll and Vote Poll storage p = polls[_pollId]; - Vote storage v = p.votes[msg.sender]; - - //Ensure commit phase hasn't ended, the user has not committed and has adequate balance in the treasury - require(block.number <= p.commitEndBlock, "commit has ended"); - require(!p.didCommit[msg.sender], "address committed"); require(treasury.registerVote(msg.sender, _pollId, _tokensToCommit, p.winnerLockEnd, p.loserLockEnd), "insufficient tokens"); - require(_tokensToCommit > 0, "must commit tokens to lock"); + _commitVote(_pollId, msg.sender, _vote, _tokensToCommit); + } - //Set the tokens committed hidden vote data - v.tokensCommitted = _tokensToCommit; - v.hiddenVote = _vote; - //Track voter address and set did commit. - p.didCommit[msg.sender] = true; + /** @dev Commit a vote in a poll for another address to be later revealed. The salt and option must be retained for a successful reveal. + @notice Commit a vote in a poll for another address to be later revealed. The salt and option must be retained for a successful reveal. + @param _pollId Poll id to commit vote to. + @param _tokenHolder Address to submit commit in proxy of. + @param _vote Hash encoded vote option with salt. + @param _tokensToCommit Number of tokens to commit to vote. + */ + function commitProxyVote(uint _pollId, address _tokenHolder, bytes32 _vote, uint _tokensToCommit) public { + Poll storage p = polls[_pollId]; + require(treasury.registerProxyVote(msg.sender, _tokenHolder, _pollId, _tokensToCommit, p.winnerLockEnd, p.loserLockEnd), "insufficient tokens"); + _commitVote(_pollId, _tokenHolder, _vote, _tokensToCommit); } /** @dev Reveal a previously committed vote by providing the vote option and salt used to generate the vote hash. @@ -118,32 +119,20 @@ contract Voting is IVoting { @param _voteSalt Salt used to generate vote hash. */ function revealVote(uint _pollId, uint _voteOption, uint _voteSalt) public { - Poll storage p = polls[_pollId]; - Vote storage v = p.votes[msg.sender]; - - // Ensure commit phase has passed, reveal phase has not. User has commited but not revealed. User has adequate balance in the treasury. - require(block.number > p.commitEndBlock, "commit hasn't ended"); - require(block.number <= p.revealEndBlock, "reveal has ended"); - require(p.didCommit[msg.sender], "address hasn't committed"); - require(!p.didReveal[msg.sender], "address has revealed"); - - // Calculate and compare the commited vote - bytes32 exposedVote = keccak256(abi.encodePacked(_voteOption, _voteSalt)); - require(v.hiddenVote == exposedVote, "vote doesn't match"); - - // Store info from a valid revealed vote. Remove the pending vote. - v.salt = _voteSalt; - v.voteOption = _voteOption; - p.didReveal[msg.sender] = true; - p.voteValues[_voteOption] = p.voteValues[_voteOption].add(v.tokensCommitted); - p.totalRevealedTokens = p.totalRevealedTokens.add(v.tokensCommitted); + _revealVote(_pollId, msg.sender, _voteOption, _voteSalt); + } - // Update winner and tracking - if(p.currentLeadingOption != _voteOption && p.voteValues[_voteOption] > p.leadingTokens) { - p.currentLeadingOption = _voteOption; - } + /** @dev Reveal a previously committed vote for another address by providing the vote option and salt used to generate the vote hash. + @notice Reveal a previously committed vote for another address by providing the vote option and salt used to generate the vote hash. + @param _pollId Poll id to commit vote to. + @param _tokenHolder Address to submit reveal in proxy of. + @param _voteOption Vote option used to generate vote hash. + @param _voteSalt Salt used to generate vote hash. + */ + function revealProxyVote(uint _pollId, address _tokenHolder, uint _voteOption, uint _voteSalt) public { + require(treasury.isProxyFor(_tokenHolder, msg.sender), "not valid proxy"); - p.leadingTokens = p.voteValues[p.currentLeadingOption]; + _revealVote(_pollId, _tokenHolder, _voteOption, _voteSalt); } /** @dev Retrieve the winning option for a finalized poll. @@ -195,4 +184,57 @@ contract Voting is IVoting { return (true, tokens); } + + // INTERNAL + + /** @dev Commit votes + */ + function _commitVote(uint _pollId, address _tokenHolder, bytes32 _vote, uint _tokensToCommit) internal { + //load Poll and Vote + Poll storage p = polls[_pollId]; + Vote storage v = p.votes[_tokenHolder]; + + //Ensure commit phase hasn't ended, the user has not committed and has adequate balance in the treasury + require(block.number <= p.commitEndBlock, "commit has ended"); + require(!p.didCommit[_tokenHolder], "address committed"); + require(_tokensToCommit > 0, "must commit tokens to lock"); + + //Set the tokens committed hidden vote data + v.tokensCommitted = _tokensToCommit; + v.hiddenVote = _vote; + + //Track voter address and set did commit. + p.didCommit[_tokenHolder] = true; + } + + /** @dev Reveal vote + */ + function _revealVote(uint _pollId, address _tokenHolder, uint _voteOption, uint _voteSalt) internal { + Poll storage p = polls[_pollId]; + Vote storage v = p.votes[_tokenHolder]; + + // Ensure commit phase has passed, reveal phase has not. User has commited but not revealed. User has adequate balance in the treasury. + require(block.number > p.commitEndBlock, "commit hasn't ended"); + require(block.number <= p.revealEndBlock, "reveal has ended"); + require(p.didCommit[_tokenHolder], "address hasn't committed"); + require(!p.didReveal[_tokenHolder], "address has revealed"); + + // Calculate and compare the commited vote + bytes32 exposedVote = keccak256(abi.encodePacked(_voteOption, _voteSalt)); + require(v.hiddenVote == exposedVote, "vote doesn't match"); + + // Store info from a valid revealed vote. Remove the pending vote. + v.salt = _voteSalt; + v.voteOption = _voteOption; + p.didReveal[_tokenHolder] = true; + p.voteValues[_voteOption] = p.voteValues[_voteOption].add(v.tokensCommitted); + p.totalRevealedTokens = p.totalRevealedTokens.add(v.tokensCommitted); + + // Update winner and tracking + if(p.currentLeadingOption != _voteOption && p.voteValues[_voteOption] > p.leadingTokens) { + p.currentLeadingOption = _voteOption; + } + + p.leadingTokens = p.voteValues[p.currentLeadingOption]; + } } From b325479e2963a7116179581ea1208f965aed757d Mon Sep 17 00:00:00 2001 From: Nick Freyaldenhoven Date: Thu, 7 Nov 2019 10:01:11 -0600 Subject: [PATCH 3/5] running prettier --- packages/kosu-solidity-tests/test/voting.ts | 36 +++++++++++++++------ 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/kosu-solidity-tests/test/voting.ts b/packages/kosu-solidity-tests/test/voting.ts index 5ef743be..2155d859 100644 --- a/packages/kosu-solidity-tests/test/voting.ts +++ b/packages/kosu-solidity-tests/test/voting.ts @@ -124,8 +124,13 @@ describe("Voting", () => { }); it("should allow a user to commit a proxy vote", async () => { - await voting.commitProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], secret1, TestValues.fiveEther, { from: accounts[1] }).should - .eventually.be.fulfilled; + await voting.commitProxyVote.awaitTransactionSuccessAsync( + pollId, + accounts[0], + secret1, + TestValues.fiveEther, + { from: accounts[1] }, + ).should.eventually.be.fulfilled; }); }); @@ -193,9 +198,16 @@ describe("Voting", () => { }); it("should allow a user to reveal a proxy vote", async () => { - await voting.commitProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], secret1, TestValues.fiveEther, { from: accounts[1] }).should - .eventually.be.fulfilled; - await voting.revealProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], vote1, salt, { from: accounts[1] }).should.eventually.be.fulfilled; + await voting.commitProxyVote.awaitTransactionSuccessAsync( + pollId, + accounts[0], + secret1, + TestValues.fiveEther, + { from: accounts[1] }, + ).should.eventually.be.fulfilled; + await voting.revealProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], vote1, salt, { + from: accounts[1], + }).should.eventually.be.fulfilled; }); }); @@ -227,12 +239,19 @@ describe("Voting", () => { await testHelpers.prepareTokens(accounts[0], TestValues.fiveEther); await testHelpers.prepareTokens(accounts[1], TestValues.fiveEther); const { pollId } = await testHelpers.variablePoll(2, 2); - await voting.commitProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], secret1, TestValues.oneEther, { from: accounts[1] }).should.eventually - .be.fulfilled; + await voting.commitProxyVote.awaitTransactionSuccessAsync( + pollId, + accounts[0], + secret1, + TestValues.oneEther, + { from: accounts[1] }, + ).should.eventually.be.fulfilled; await voting.commitVote.awaitTransactionSuccessAsync(pollId, secret2, TestValues.fiveEther, { from: accounts[1], }).should.eventually.be.fulfilled; - await voting.revealProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], vote1, salt, { from: accounts[1] }).should.eventually.be.fulfilled; + await voting.revealProxyVote.awaitTransactionSuccessAsync(pollId, accounts[0], vote1, salt, { + from: accounts[1], + }).should.eventually.be.fulfilled; await voting.revealVote.awaitTransactionSuccessAsync(pollId, vote2, salt, { from: accounts[1] }).should .eventually.be.fulfilled; @@ -242,7 +261,6 @@ describe("Voting", () => { .then(x => x.toString()) .should.eventually.eq("2"); await treasury.deauthorizeProxy.awaitTransactionSuccessAsync(accounts[1]); - }); it("should report the first winning option in a tie", async () => { From 734cf8521e93c8d5f3c6b5c6327cd88c0ef36126 Mon Sep 17 00:00:00 2001 From: Nick Freyaldenhoven Date: Thu, 7 Nov 2019 15:41:44 -0600 Subject: [PATCH 4/5] Adding wrappers updates for proxy votes. --- .../kosu-wrapper-enhancements/src/Treasury.ts | 34 ++++++++++++ .../kosu-wrapper-enhancements/src/Voting.ts | 54 +++++++++++++++++++ .../test/treasury_test.ts | 10 ++++ .../test/voting_test.ts | 39 ++++++++++++++ 4 files changed, 137 insertions(+) diff --git a/packages/kosu-wrapper-enhancements/src/Treasury.ts b/packages/kosu-wrapper-enhancements/src/Treasury.ts index bfd54a25..7f8d252e 100644 --- a/packages/kosu-wrapper-enhancements/src/Treasury.ts +++ b/packages/kosu-wrapper-enhancements/src/Treasury.ts @@ -126,6 +126,40 @@ export class Treasury { return contract.withdraw.awaitTransactionSuccessAsync(new BigNumber(value.toString())); } + /** + * Authorize an address to vote in proxy + * + * @param _proxy The address to be added as a proxy + * @returns The decoded transaction receipt, after the TX is mined in a block. + */ + public async authorizeProxy(_proxy: string): Promise { + const contract = await this.getContract(); + return contract.authorizeProxy.awaitTransactionSuccessAsync(_proxy); + } + + /** + * Remove an address's authorization to vote in proxy + * + * @param _proxy The address to be removed as a proxy + * @returns The decoded transaction receipt, after the TX is mined in a block. + */ + public async deauthorizeProxy(_proxy: string): Promise { + const contract = await this.getContract(); + return contract.deauthorizeProxy.awaitTransactionSuccessAsync(_proxy); + } + + /** + * Checks if an address is a valid proxy for another address + * + * @param _account The balance holder + * @param _proxy The possible voting proxy + * @returns The boolean status of proxy voting permission + */ + public async isProxyFor(_account: string, _proxy: string): Promise { + const contract = await this.getContract(); + return contract.isProxyFor.callAsync(_account, _proxy); + } + /** * Read the total system balance of KOSU for a provided `address` string. * diff --git a/packages/kosu-wrapper-enhancements/src/Voting.ts b/packages/kosu-wrapper-enhancements/src/Voting.ts index 3d231e04..77b9acd8 100644 --- a/packages/kosu-wrapper-enhancements/src/Voting.ts +++ b/packages/kosu-wrapper-enhancements/src/Voting.ts @@ -82,6 +82,37 @@ export class Voting { ); } + /** + * Commits proxy vote to voting contract + * + * @param _pollId uint poll index + * @param _account ethereum address vote will be commited for + * @param _vote encoded vote option + * @param _tokensToCommit uint number of tokens to be commited to vote + */ + public async commitProxyVote( + _pollId: BigNumber, + _account: string, + _vote: string, + _tokensToCommit: BigNumber, + ): Promise { + const contract = await this.getContract(); + + const systemBalance = await this.treasury.systemBalance(_account); + if (systemBalance.lt(_tokensToCommit)) { + throw new Error(`${_account} doesn't have sufficient tokens for a proxy vote.`); + } + + // tslint:disable-next-line: no-console + console.log(`Committing proxy vote for ${_account} of ${_vote} with ${_tokensToCommit} DIGM tokens`); + return contract.commitProxyVote.awaitTransactionSuccessAsync( + new BigNumber(_pollId.toString()), + _account, + _vote, + new BigNumber(_tokensToCommit.toString()), + ); + } + /** * Reveals vote on voting contract * @@ -102,6 +133,29 @@ export class Voting { ); } + /** + * Reveals vote on voting contract + * + * @param _pollId uint poll index + * @param _account ethereum address vote will be revealed for + * @param _voteOption uint representation of vote position + * @param _voteSalt uint salt used to encode vote option + */ + public async revealProxyVote( + _pollId: BigNumber, + _account: string, + _voteOption: BigNumber, + _voteSalt: BigNumber, + ): Promise { + const contract = await this.getContract(); + return contract.revealProxyVote.awaitTransactionSuccessAsync( + new BigNumber(_pollId.toString()), + _account, + new BigNumber(_voteOption.toString()), + new BigNumber(_voteSalt.toString()), + ); + } + /** * Reads the winning option for poll * diff --git a/packages/kosu-wrapper-enhancements/test/treasury_test.ts b/packages/kosu-wrapper-enhancements/test/treasury_test.ts index 1217a8c0..e05a4cb7 100644 --- a/packages/kosu-wrapper-enhancements/test/treasury_test.ts +++ b/packages/kosu-wrapper-enhancements/test/treasury_test.ts @@ -21,4 +21,14 @@ describe("Treasury", () => { await kosuToken.releaseTokens(difference); }); }); + + describe("proxy authorization", () => { + it("should authorize and deauthorize", async () => { + await treasury.isProxyFor(accounts[0], accounts[1]).should.eventually.eq(false); + await treasury.authorizeProxy(accounts[1]); + await treasury.isProxyFor(accounts[0], accounts[1]).should.eventually.eq(true); + await treasury.deauthorizeProxy(accounts[1]); + await treasury.isProxyFor(accounts[0], accounts[1]).should.eventually.eq(false); + }) + }); }); diff --git a/packages/kosu-wrapper-enhancements/test/voting_test.ts b/packages/kosu-wrapper-enhancements/test/voting_test.ts index 517b5d1b..a9dd1219 100644 --- a/packages/kosu-wrapper-enhancements/test/voting_test.ts +++ b/packages/kosu-wrapper-enhancements/test/voting_test.ts @@ -1,6 +1,7 @@ import { encodeVote } from "@kosu/utils"; import { KosuToken, Treasury, Voting } from "../src"; +import {BigNumber} from "bignumber.js"; describe("Voting", () => { let kosuToken: KosuToken; @@ -42,4 +43,42 @@ describe("Voting", () => { .then(x => x.toString()) .should.eventually.eq(voteValue.toString()); }); + + it("should interact with a Voting poll as proxy", async () => { + const voteValue = TestValues.oneWei; + const voteSalt = TestValues.fiveEther; + const encodedVote = encodeVote(voteValue, voteSalt); + await kosuToken.transfer(accounts[1], TestValues.oneEther); + const t = await treasury.getContract(); + const kt = await kosuToken.getContract(); + await kt.approve.awaitTransactionSuccessAsync(t.address, TestValues.oneEther, { from: accounts[1] }); + await t.deposit.awaitTransactionSuccessAsync(TestValues.oneEther, { from: accounts[1] }); + await t.authorizeProxy.sendTransactionAsync(accounts[0], { from: accounts[1] }); + + const { blockNumber, pollId } = await testHelpers.variablePoll(10, 10); + const commitEnd = blockNumber + 10; + const revealEnd = blockNumber + 21; + await voting.commitProxyVote(pollId, accounts[1], encodedVote, TestValues.oneWei).should.be.fulfilled; + await testHelpers.skipTo(commitEnd); + await voting.revealProxyVote(pollId, accounts[1], voteValue, voteSalt); + await testHelpers.skipTo(revealEnd); + await voting + .winningOption(pollId) + .then(x => x.toString()) + .should.eventually.eq(voteValue.toString()); + await voting + .totalWinningTokens(pollId) + .then(x => x.toString()) + .should.eventually.eq(voteValue.toString()); + await voting + .totalRevealedTokens(pollId) + .then(x => x.toString()) + .should.eventually.eq(voteValue.toString()); + await voting + .userWinningTokens(pollId, accounts[1]) + .then(x => x.toString()) + .should.eventually.eq(voteValue.toString()); + + await t.deauthorizeProxy.callAsync(accounts[0], { from: accounts[1] }); + }); }); From e833423108a38f3dffbdeeb71620afc21ae6171b Mon Sep 17 00:00:00 2001 From: Nick Freyaldenhoven Date: Thu, 7 Nov 2019 15:53:21 -0600 Subject: [PATCH 5/5] Running prettier. --- packages/kosu-wrapper-enhancements/test/treasury_test.ts | 2 +- packages/kosu-wrapper-enhancements/test/voting_test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kosu-wrapper-enhancements/test/treasury_test.ts b/packages/kosu-wrapper-enhancements/test/treasury_test.ts index e05a4cb7..598a8c65 100644 --- a/packages/kosu-wrapper-enhancements/test/treasury_test.ts +++ b/packages/kosu-wrapper-enhancements/test/treasury_test.ts @@ -29,6 +29,6 @@ describe("Treasury", () => { await treasury.isProxyFor(accounts[0], accounts[1]).should.eventually.eq(true); await treasury.deauthorizeProxy(accounts[1]); await treasury.isProxyFor(accounts[0], accounts[1]).should.eventually.eq(false); - }) + }); }); }); diff --git a/packages/kosu-wrapper-enhancements/test/voting_test.ts b/packages/kosu-wrapper-enhancements/test/voting_test.ts index a9dd1219..0f2f27b8 100644 --- a/packages/kosu-wrapper-enhancements/test/voting_test.ts +++ b/packages/kosu-wrapper-enhancements/test/voting_test.ts @@ -1,7 +1,7 @@ import { encodeVote } from "@kosu/utils"; import { KosuToken, Treasury, Voting } from "../src"; -import {BigNumber} from "bignumber.js"; +import { BigNumber } from "bignumber.js"; describe("Voting", () => { let kosuToken: KosuToken;