Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Voting Proxy #372

Merged
merged 7 commits into from
Nov 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions packages/kosu-solidity-tests/test/treasury.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand Down Expand Up @@ -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
Expand Up @@ -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;
Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
107 changes: 80 additions & 27 deletions packages/kosu-system-contracts/contracts/treasury/Treasury.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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;
}
}
}
Expand All @@ -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;
Expand All @@ -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