Skip to content

Commit

Permalink
Merge pull request #372 from ParadigmFoundation/refactor/adding-voter…
Browse files Browse the repository at this point in the history
…-proxy

Voting Proxy
  • Loading branch information
Freydal authored Nov 8, 2019

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents e7374a5 + ac48ed5 commit d7532db
Showing 8 changed files with 386 additions and 67 deletions.
14 changes: 12 additions & 2 deletions packages/kosu-solidity-tests/test/treasury.ts
Original file line number Diff line number Diff line change
@@ -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", {
@@ -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;
});
});
});
81 changes: 79 additions & 2 deletions packages/kosu-solidity-tests/test/voting.ts
Original file line number Diff line number Diff line change
@@ -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;
@@ -112,6 +112,28 @@ 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 +186,31 @@ 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 +233,36 @@ 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);
107 changes: 80 additions & 27 deletions packages/kosu-system-contracts/contracts/treasury/Treasury.sol
Original file line number Diff line number Diff line change
@@ -12,18 +12,23 @@ contract Treasury is Authorizable {
using SafeMath for uint;

IVoting voting;
KosuToken public kosuToken;

struct TokenLock {
uint value;
uint pollId;
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,20 +165,40 @@ 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.
@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 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;
}
}
Loading

0 comments on commit d7532db

Please sign in to comment.