From a72fa95e3f997c7e8e9c7c8f80b34679ff23edae Mon Sep 17 00:00:00 2001 From: Joshua Levine Date: Thu, 3 Jan 2019 20:51:20 -0800 Subject: [PATCH 1/4] init new approach to voting --- src/chief-v2.sol | 100 +++++++++++ src/chief-v2.t.sol | 180 ++++++++++++++++++++ src/chief.sol | 184 -------------------- src/chief.t.sol | 409 --------------------------------------------- 4 files changed, 280 insertions(+), 593 deletions(-) create mode 100644 src/chief-v2.sol create mode 100644 src/chief-v2.t.sol delete mode 100644 src/chief.sol delete mode 100644 src/chief.t.sol diff --git a/src/chief-v2.sol b/src/chief-v2.sol new file mode 100644 index 0000000..cdd88a8 --- /dev/null +++ b/src/chief-v2.sol @@ -0,0 +1,100 @@ +pragma solidity >=0.5.0; + +import "ds-note/note.sol"; + +contract TokenLike { + function transferFrom(address, address, uint) public returns (bool); +} + +contract Chief is DSNote { + // --- Init --- + constructor(address gov_) public { + gov = TokenLike(gov_); + threshold = ONE / 2; + } + + // --- Math --- + uint256 constant ONE = 10 ** 18; + function add(uint x, uint y) internal pure returns (uint z) { + z = x + y; + require(z >= x); + } + function sub(uint x, uint y) internal pure returns (uint z) { + z = x - y; + require(z <= x); + } + function wmul(uint x, uint y) internal pure returns (uint z) { + z = x * y; + require(y == 0 || z / y == x); + z = z / ONE; + } + + // --- Data --- + uint256 public threshold; // locked % needed to enact a proposal + TokenLike public gov; // governance token + uint256 public locked; // total locked gov + mapping(bytes32 => bool) public hasFired; // proposal => hasFired? + mapping(address => uint256) public balances; // guy => lockedGovHowMuch + mapping(address => bytes32) public picks; // guy => proposalHash + mapping(bytes32 => uint256) public votes; // proposalHash => votesHowMany + + // --- Events --- + event Voted( + bytes32 indexed proposalHash, + address indexed voter, + uint256 weight + ); + event Executed( + address caller, + bytes32 proposal, + address indexed app, + bytes data + ); + + // --- Voting Interface --- + function lock(uint256 wad) public note { + require(gov.transferFrom(msg.sender, address(this), wad)); + balances[msg.sender] = add(balances[msg.sender], wad); + locked = add(locked, wad); + + bytes32 currPick = picks[msg.sender]; + if (currPick != bytes32(0) && !hasFired[currPick]) + votes[currPick] = add(votes[currPick], wad); + } + function free(uint256 wad) public note { + balances[msg.sender] = sub(balances[msg.sender], wad); + require(gov.transferFrom(address(this), msg.sender, wad)); + locked = sub(locked, wad); + + bytes32 currPick = picks[msg.sender]; + if (currPick != bytes32(0) && !hasFired[currPick]) + votes[currPick] = sub(votes[currPick], wad); + } + function vote(bytes32 currPick) public { + require(!hasFired[currPick]); + + uint256 weight = balances[msg.sender]; + bytes32 prevPick = picks[msg.sender]; + + if (prevPick != bytes32(0) && !hasFired[prevPick]) + votes[prevPick] = sub(votes[prevPick], weight); + + votes[currPick] = add(votes[currPick], weight); + picks[msg.sender] = currPick; + + emit Voted(currPick, msg.sender, weight); + } + function exec(address app, bytes memory data) public { + bytes32 proposal = keccak256(abi.encode(app, data)); + require(!hasFired[proposal]); + require(votes[proposal] > wmul(locked, threshold)); + + assembly { + let ok := delegatecall(sub(gas, 5000), app, add(data, 0x20), mload(data), 0, 0) + if eq(ok, 0) { revert(0, 0) } + } + + hasFired[proposal] = true; + emit Executed(msg.sender, proposal, app, data); + } +} diff --git a/src/chief-v2.t.sol b/src/chief-v2.t.sol new file mode 100644 index 0000000..b1b5988 --- /dev/null +++ b/src/chief-v2.t.sol @@ -0,0 +1,180 @@ +pragma solidity >=0.5.0; + +import "ds-test/test.sol"; +import "ds-token/token.sol"; + +import "./Chief.sol"; + +contract Voter { + DSToken gov; + address chief; + + constructor(DSToken gov_, address chief_) public { + gov = gov_; + chief = chief_; + } + + function approveChief() public { gov.approve(chief); } + + function tryLock(uint256 wad) public returns (bool ok) { + (ok, ) = chief.call(abi.encodeWithSignature( + "lock(uint256)", wad + )); + } + + function tryFree(uint256 wad) public returns (bool ok) { + (ok, ) = chief.call(abi.encodeWithSignature( + "free(uint256)", wad + )); + } + + function tryVote(bytes32 pick) public returns (bool ok) { + (ok, ) = chief.call(abi.encodeWithSignature( + "vote(bytes32)", pick + )); + } + + function tryExec(address app, bytes memory data) public returns (bool ok) { + (ok, ) = chief.call(abi.encodeWithSignature( + "exec(address,bytes)", app, data + )); + } +} + +contract Cache { + uint256 public val; + function set(uint256 val_) public { val = val_; } +} + +contract CacheScript { + function setCache(address target, uint256 val) public { + Cache(target).set(val); + } +} + +contract ThresholdScript { + uint256 public threshold; + function updateThreshold(uint val_) public { + threshold = val_; + } +} + + +contract ChiefTest is DSTest { + Chief chief; + DSToken gov; + Cache cache; + + Voter ben; + Voter sam; + Voter ava; + + function setUp() public { + gov = new DSToken("gov"); + chief = new Chief(address(gov)); + cache = new Cache(); + + ben = new Voter(gov, address(chief)); + sam = new Voter(gov, address(chief)); + ava = new Voter(gov, address(chief)); + gov.mint(address(ben), 100 ether); + gov.mint(address(sam), 100 ether); + gov.mint(address(ava), 100 ether); + } + + function test_sanity_setup_check() public { + assertEq(chief.threshold(), 10 ** 18 / 2); + assertEq(chief.locked(), 0); + assertEq(address(chief.gov()), address(gov)); + assertEq(chief.balances(address(ben)), 0); + assertEq(chief.picks(address(ben)), bytes32(0)); + + assertEq(cache.val(), 0); + + assertEq(gov.balanceOf(address(ben)), 100 ether); + } + + function test_lock_free() public { + // ben gives chief unlimited approvals over his gov token + ben.approveChief(); + + // ben locks some gov in chief + assertTrue(ben.tryLock(10 ether)); + assertEq(chief.balances(address(ben)), 10 ether); + assertEq(chief.locked(), 10 ether); + assertTrue(ben.tryLock(1 ether)); + assertEq(chief.balances(address(ben)), 11 ether); + assertEq(chief.locked(), 11 ether); + + // ben frees the same amount of gov from chief + assertTrue(ben.tryFree(11 ether)); + assertEq(chief.balances(address(ben)), 0); + assertEq(chief.locked(), 0); + } + + function test_vote_exec() public { + ben.approveChief(); + assertTrue(ben.tryLock(10 ether)); + + // create a useful contract for chief to delegatecall + CacheScript cacheScript = new CacheScript(); + uint256 newVal = 123; + + // create a proposal + bytes memory data = abi.encodeWithSignature( + "setCache(address,uint256)", cache, newVal + ); + bytes32 proposal = keccak256(abi.encode(cacheScript, data)); + + // ben votes for the proposal + assertTrue(ben.tryVote(proposal)); + assertEq(chief.picks(address(ben)), proposal); + assertEq(chief.votes(proposal), 10 ether); + assertTrue(!chief.hasFired(proposal)); + + // ben executes the proposal + assertEq(cache.val(), 0); + assertTrue(ben.tryExec(address(cacheScript), data)); + + // the proposal was successfully executed + assertEq(cache.val(), newVal); + assertTrue(chief.hasFired(proposal)); + + // the proposal can only be executed once + assertTrue(!ben.tryExec(address(cacheScript), data)); + } + + function test_modify_threshold() public { + ben.approveChief(); + assertTrue(ben.tryLock(10 ether)); + + ThresholdScript thresholdScript = new ThresholdScript(); + uint256 newThreshold = 10 ** 18; + + uint256 oldThreshold = chief.threshold(); + assertTrue(newThreshold != oldThreshold); + + bytes memory data = abi.encodeWithSignature( + "updateThreshold(uint256)", newThreshold + ); + bytes32 proposal = keccak256(abi.encode(thresholdScript, data)); + + assertTrue(ben.tryVote(proposal)); + assertTrue(ben.tryExec(address(thresholdScript), data)); + assertEq(chief.threshold(), newThreshold); + } + + function test_fail_free_too_much() public { + ben.approveChief(); + assertTrue( ben.tryLock(10 ether)); + assertTrue(!ben.tryFree(11 ether)); + + sam.approveChief(); + assertTrue( sam.tryLock(10 ether)); + assertTrue( ben.tryFree(9 ether )); + assertTrue( ben.tryFree(1 ether )); + + assertTrue(!ben.tryFree(1 ether )); + assertTrue( sam.tryFree(10 ether)); + } +} diff --git a/src/chief.sol b/src/chief.sol deleted file mode 100644 index 36bf9bd..0000000 --- a/src/chief.sol +++ /dev/null @@ -1,184 +0,0 @@ -// chief.sol - select an authority by consensus - -// Copyright (C) 2017 DappHub, LLC - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma solidity >=0.4.23; - -import 'ds-token/token.sol'; -import 'ds-roles/roles.sol'; -import 'ds-thing/thing.sol'; - -// The right way to use this contract is probably to mix it with some kind -// of `DSAuthority`, like with `ds-roles`. -// SEE DSChief -contract DSChiefApprovals is DSThing { - mapping(bytes32=>address[]) public slates; - mapping(address=>bytes32) public votes; - mapping(address=>uint256) public approvals; - mapping(address=>uint256) public deposits; - DSToken public GOV; // voting token that gets locked up - DSToken public IOU; // non-voting representation of a token, for e.g. secondary voting mechanisms - address public hat; // the chieftain's hat - - uint256 public MAX_YAYS; - - event Etch(bytes32 indexed slate); - - // IOU constructed outside this contract reduces deployment costs significantly - // lock/free/vote are quite sensitive to token invariants. Caution is advised. - constructor(DSToken GOV_, DSToken IOU_, uint MAX_YAYS_) public - { - GOV = GOV_; - IOU = IOU_; - MAX_YAYS = MAX_YAYS_; - } - - function lock(uint wad) - public - note - { - GOV.pull(msg.sender, wad); - IOU.mint(msg.sender, wad); - deposits[msg.sender] = add(deposits[msg.sender], wad); - addWeight(wad, votes[msg.sender]); - } - - function free(uint wad) - public - note - { - deposits[msg.sender] = sub(deposits[msg.sender], wad); - subWeight(wad, votes[msg.sender]); - IOU.burn(msg.sender, wad); - GOV.push(msg.sender, wad); - } - - function etch(address[] memory yays) - public - note - returns (bytes32 slate) - { - require( yays.length <= MAX_YAYS ); - requireByteOrderedSet(yays); - - bytes32 hash = keccak256(abi.encodePacked(yays)); - slates[hash] = yays; - emit Etch(hash); - return hash; - } - - function vote(address[] memory yays) public returns (bytes32) - // note both sub-calls note - { - bytes32 slate = etch(yays); - vote(slate); - return slate; - } - - function vote(bytes32 slate) - public - note - { - uint weight = deposits[msg.sender]; - subWeight(weight, votes[msg.sender]); - votes[msg.sender] = slate; - addWeight(weight, votes[msg.sender]); - } - - // like `drop`/`swap` except simply "elect this address if it is higher than current hat" - function lift(address whom) - public - note - { - require(approvals[whom] > approvals[hat]); - hat = whom; - } - - function addWeight(uint weight, bytes32 slate) - internal - { - address[] storage yays = slates[slate]; - for( uint i = 0; i < yays.length; i++) { - approvals[yays[i]] = add(approvals[yays[i]], weight); - } - } - - function subWeight(uint weight, bytes32 slate) - internal - { - address[] storage yays = slates[slate]; - for( uint i = 0; i < yays.length; i++) { - approvals[yays[i]] = sub(approvals[yays[i]], weight); - } - } - - // Throws unless the array of addresses is a ordered set. - function requireByteOrderedSet(address[] memory yays) - internal - pure - { - if( yays.length == 0 || yays.length == 1 ) { - return; - } - for( uint i = 0; i < yays.length - 1; i++ ) { - // strict inequality ensures both ordering and uniqueness - require(uint(yays[i]) < uint(yays[i+1])); - } - } -} - - -// `hat` address is unique root user (has every role) and the -// unique owner of role 0 (typically 'sys' or 'internal') -contract DSChief is DSRoles, DSChiefApprovals { - - constructor(DSToken GOV, DSToken IOU, uint MAX_YAYS) - DSChiefApprovals (GOV, IOU, MAX_YAYS) - public - { - authority = this; - owner = address(0); - } - - function setOwner(address owner_) public { - owner_; - revert(); - } - - function setAuthority(DSAuthority authority_) public { - authority_; - revert(); - } - - function isUserRoot(address who) - public view - returns (bool) - { - return (who == hat); - } - function setRootUser(address who, bool enabled) public { - who; enabled; - revert(); - } -} - -contract DSChiefFab { - function newChief(DSToken gov, uint MAX_YAYS) public returns (DSChief chief) { - DSToken iou = new DSToken('IOU'); - chief = new DSChief(gov, iou, MAX_YAYS); - iou.setOwner(address(chief)); - } -} diff --git a/src/chief.t.sol b/src/chief.t.sol deleted file mode 100644 index a3b668c..0000000 --- a/src/chief.t.sol +++ /dev/null @@ -1,409 +0,0 @@ -// chief.t.sol - test for chief.sol - -// Copyright (C) 2017 DappHub, LLC - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma solidity >=0.4.23; - -import "ds-test/test.sol"; -import "ds-token/token.sol"; -import "ds-thing/thing.sol"; - -import "./chief.sol"; - -contract ChiefUser is DSThing { - DSChief chief; - - constructor(DSChief chief_) public { - chief = chief_; - } - - function doTransferFrom(DSToken token, address from, address to, - uint amount) - public - returns (bool) - { - return token.transferFrom(from, to, amount); - } - - function doTransfer(DSToken token, address to, uint amount) - public - returns (bool) - { - return token.transfer(to, amount); - } - - function doApprove(DSToken token, address recipient, uint amount) - public - returns (bool) - { - return token.approve(recipient, amount); - } - - function doAllowance(DSToken token, address owner, address spender) - public view - returns (uint) - { - return token.allowance(owner, spender); - } - - function doEtch(address[] memory guys) public returns (bytes32) { - return chief.etch(guys); - } - - function doVote(address[] memory guys) public returns (bytes32) { - return chief.vote(guys); - } - - function doVote(address[] memory guys, address lift_whom) public returns (bytes32) { - bytes32 slate = chief.vote(guys); - chief.lift(lift_whom); - return slate; - } - - function doVote(bytes32 id) public { - chief.vote(id); - } - - function doVote(bytes32 id, address lift_whom) public { - chief.vote(id); - chief.lift(lift_whom); - } - - function doLift(address to_lift) public { - chief.lift(to_lift); - } - - function doLock(uint amt) public { - chief.lock(amt); - } - - function doFree(uint amt) public { - chief.free(amt); - } - - function doSetUserRole(address who, uint8 role, bool enabled) public { - chief.setUserRole(who, role, enabled); - } - - function doSetRoleCapability(uint8 role, address code, bytes4 sig, bool enabled) public { - chief.setRoleCapability(role, code, sig, enabled); - } - - function doSetPublicCapability(address code, bytes4 sig, bool enabled) public { - chief.setPublicCapability(code, sig, enabled); - } - - function authedFn() public view auth returns (bool) { - return true; - } -} - -contract DSChiefTest is DSThing, DSTest { - uint256 constant electionSize = 3; - - // c prefix: candidate - address constant c1 = address(0x1); - address constant c2 = address(0x2); - address constant c3 = address(0x3); - address constant c4 = address(0x4); - address constant c5 = address(0x5); - address constant c6 = address(0x6); - address constant c7 = address(0x7); - address constant c8 = address(0x8); - address constant c9 = address(0x9); - uint256 constant initialBalance = 1000 ether; - uint256 constant uLargeInitialBalance = initialBalance / 3; - uint256 constant uMediumInitialBalance = initialBalance / 4; - uint256 constant uSmallInitialBalance = initialBalance / 5; - - DSChief chief; - DSToken gov; - DSToken iou; - - // u prefix: user - ChiefUser uLarge; - ChiefUser uMedium; - ChiefUser uSmall; - - function setUp() public { - gov = new DSToken("GOV"); - gov.mint(initialBalance); - - DSChiefFab fab = new DSChiefFab(); - chief = fab.newChief(gov, electionSize); - iou = chief.IOU(); - - uLarge = new ChiefUser(chief); - uMedium = new ChiefUser(chief); - uSmall = new ChiefUser(chief); - - assert(initialBalance > uLargeInitialBalance + uMediumInitialBalance + - uSmallInitialBalance); - assert(uLargeInitialBalance < uMediumInitialBalance + uSmallInitialBalance); - - gov.transfer(address(uLarge), uLargeInitialBalance); - gov.transfer(address(uMedium), uMediumInitialBalance); - gov.transfer(address(uSmall), uSmallInitialBalance); - } - - function test_basic_sanity() public pure { - assert(true); - } - - function testFail_basic_sanity() public pure { - assert(false); - } - - function test_etch_returns_same_id_for_same_sets() public { - address[] memory candidates = new address[](3); - candidates[0] = c1; - candidates[1] = c2; - candidates[2] = c3; - - bytes32 id = uSmall.doEtch(candidates); - assert(id != 0x0); - assertEq32(id, uMedium.doEtch(candidates)); - } - - function test_size_zero_slate() public { - address[] memory candidates = new address[](0); - bytes32 id = uSmall.doEtch(candidates); - uSmall.doVote(id); - } - function test_size_one_slate() public { - address[] memory candidates = new address[](1); - candidates[0] = c1; - bytes32 id = uSmall.doEtch(candidates); - uSmall.doVote(id); - } - - function testFail_etch_requires_ordered_sets() public { - address[] memory candidates = new address[](3); - candidates[0] = c2; - candidates[1] = c1; - candidates[2] = c3; - - uSmall.doEtch(candidates); - } - - function test_lock_debits_user() public { - assert(gov.balanceOf(address(uLarge)) == uLargeInitialBalance); - - uint lockedAmt = uLargeInitialBalance / 10; - uLarge.doApprove(gov, address(chief), lockedAmt); - uLarge.doLock(lockedAmt); - - assert(gov.balanceOf(address(uLarge)) == uLargeInitialBalance - lockedAmt); - } - - function test_changing_weight_after_voting() public { - uint uLargeLockedAmt = uLargeInitialBalance / 2; - uLarge.doApprove(iou, address(chief), uLargeLockedAmt); - uLarge.doApprove(gov, address(chief), uLargeLockedAmt); - uLarge.doLock(uLargeLockedAmt); - - address[] memory uLargeSlate = new address[](1); - uLargeSlate[0] = c1; - uLarge.doVote(uLargeSlate); - - assert(chief.approvals(c1) == uLargeLockedAmt); - - // Changing weight should update the weight of our candidate. - uLarge.doFree(uLargeLockedAmt); - assert(chief.approvals(c1) == 0); - - uLargeLockedAmt = uLargeInitialBalance / 4; - uLarge.doApprove(gov, address(chief), uLargeLockedAmt); - uLarge.doLock(uLargeLockedAmt); - - assert(chief.approvals(c1) == uLargeLockedAmt); - } - - function test_voting_and_reordering() public { - assert(gov.balanceOf(address(uLarge)) == uLargeInitialBalance); - - initial_vote(); - - // Upset the order. - uint uLargeLockedAmt = uLargeInitialBalance; - uLarge.doApprove(gov, address(chief), uLargeLockedAmt); - uLarge.doLock(uLargeLockedAmt); - - address[] memory uLargeSlate = new address[](1); - uLargeSlate[0] = c3; - uLarge.doVote(uLargeSlate); - } - - function testFail_lift_while_out_of_order() public { - initial_vote(); - - // Upset the order. - uSmall.doApprove(gov, address(chief), uSmallInitialBalance); - uSmall.doLock(uSmallInitialBalance); - - address[] memory uSmallSlate = new address[](1); - uSmallSlate[0] = c3; - uSmall.doVote(uSmallSlate); - - uMedium.doFree(uMediumInitialBalance); - - chief.lift(c3); - } - - function test_lift_half_approvals() public { - initial_vote(); - - // Upset the order. - uSmall.doApprove(gov, address(chief), uSmallInitialBalance); - uSmall.doLock(uSmallInitialBalance); - - address[] memory uSmallSlate = new address[](1); - uSmallSlate[0] = c3; - uSmall.doVote(uSmallSlate); - - uMedium.doApprove(iou, address(chief), uMediumInitialBalance); - uMedium.doFree(uMediumInitialBalance); - - chief.lift(c3); - - assert(!chief.isUserRoot(c1)); - assert(!chief.isUserRoot(c2)); - assert(chief.isUserRoot(c3)); - } - - function testFail_voting_and_reordering_without_weight() public { - assert(gov.balanceOf(address(uLarge)) == uLargeInitialBalance); - - initial_vote(); - - // Vote without weight. - address[] memory uLargeSlate = new address[](1); - uLargeSlate[0] = c3; - uLarge.doVote(uLargeSlate); - - // Attempt to update the elected set. - chief.lift(c3); - } - - function test_voting_by_slate_id() public { - assert(gov.balanceOf(address(uLarge)) == uLargeInitialBalance); - - bytes32 slateID = initial_vote(); - - // Upset the order. - uLarge.doApprove(gov, address(chief), uLargeInitialBalance); - uLarge.doLock(uLargeInitialBalance); - - address[] memory uLargeSlate = new address[](1); - uLargeSlate[0] = c4; - uLarge.doVote(uLargeSlate); - - // Update the elected set to reflect the new order. - chief.lift(c4); - - // Now restore the old order using a slate ID. - uSmall.doApprove(gov, address(chief), uSmallInitialBalance); - uSmall.doLock(uSmallInitialBalance); - uSmall.doVote(slateID); - - // Update the elected set to reflect the restored order. - chief.lift(c1); - } - - function testFail_non_hat_can_not_set_roles() public { - uSmall.doSetUserRole(address(uMedium), 1, true); - } - - function test_hat_can_set_roles() public { - address[] memory slate = new address[](1); - slate[0] = address(uSmall); - - // Upset the order. - uLarge.doApprove(gov, address(chief), uLargeInitialBalance); - uLarge.doLock(uLargeInitialBalance); - - uLarge.doVote(slate); - - // Update the elected set to reflect the new order. - chief.lift(address(uSmall)); - - uSmall.doSetUserRole(address(uMedium), 1, true); - } - - function testFail_non_hat_can_not_role_capability() public { - uSmall.doSetRoleCapability(1, address(uMedium), S("authedFn"), true); - } - - function test_hat_can_set_role_capability() public { - address[] memory slate = new address[](1); - slate[0] = address(uSmall); - - // Upset the order. - uLarge.doApprove(gov, address(chief), uLargeInitialBalance); - uLarge.doLock(uLargeInitialBalance); - - uLarge.doVote(slate); - - // Update the elected set to reflect the new order. - chief.lift(address(uSmall)); - - uSmall.doSetRoleCapability(1, address(uLarge), S("authedFn()"), true); - uSmall.doSetUserRole(address(this), 1, true); - - uLarge.setAuthority(chief); - uLarge.setOwner(address(0)); - uLarge.authedFn(); - } - - function test_hat_can_set_public_capability() public { - address[] memory slate = new address[](1); - slate[0] = address(uSmall); - - // Upset the order. - uLarge.doApprove(gov, address(chief), uLargeInitialBalance); - uLarge.doLock(uLargeInitialBalance); - - uLarge.doVote(slate); - - // Update the elected set to reflect the new order. - chief.lift(address(uSmall)); - - uSmall.doSetPublicCapability(address(uLarge), S("authedFn()"), true); - - uLarge.setAuthority(chief); - uLarge.setOwner(address(0)); - uLarge.authedFn(); - } - - function test_chief_no_owner() public { - assertEq(chief.owner(), address(0)); - } - - function initial_vote() internal returns (bytes32 slateID) { - uint uMediumLockedAmt = uMediumInitialBalance; - uMedium.doApprove(gov, address(chief), uMediumLockedAmt); - uMedium.doLock(uMediumLockedAmt); - - address[] memory uMediumSlate = new address[](3); - uMediumSlate[0] = c1; - uMediumSlate[1] = c2; - uMediumSlate[2] = c3; - slateID = uMedium.doVote(uMediumSlate); - - // Lift the chief. - chief.lift(c1); - } -} From 3b33020b95b44675d3e43ce06b008726d00fa74c Mon Sep 17 00:00:00 2001 From: Joshua Levine Date: Mon, 11 Feb 2019 18:22:49 -0800 Subject: [PATCH 2/4] dapp install ds-math --- .gitmodules | 3 +++ lib/ds-math | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/ds-math diff --git a/.gitmodules b/.gitmodules index 0af557d..97bd8fb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/ds-thing"] path = lib/ds-thing url = https://github.com/dapphub/ds-thing +[submodule "lib/ds-math"] + path = lib/ds-math + url = https://github.com/dapphub/ds-math diff --git a/lib/ds-math b/lib/ds-math new file mode 160000 index 0000000..784079b --- /dev/null +++ b/lib/ds-math @@ -0,0 +1 @@ +Subproject commit 784079b72c4d782b022b3e893a7c5659aa35971a From d653426ea9288db4e8951708a31e421a3d2d7c5a Mon Sep 17 00:00:00 2001 From: Joshua Levine Date: Mon, 11 Feb 2019 21:20:51 -0800 Subject: [PATCH 3/4] add error messages to require statements * use ds-math * gov -> governanceToken --- src/chief-v2.sol | 40 +++++++++++++--------------------------- src/chief-v2.t.sol | 4 ++-- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/chief-v2.sol b/src/chief-v2.sol index cdd88a8..f8f14c6 100644 --- a/src/chief-v2.sol +++ b/src/chief-v2.sol @@ -1,38 +1,23 @@ pragma solidity >=0.5.0; import "ds-note/note.sol"; +import "ds-math/math.sol"; contract TokenLike { function transferFrom(address, address, uint) public returns (bool); } -contract Chief is DSNote { +contract Chief is DSNote, DSMath { // --- Init --- - constructor(address gov_) public { - gov = TokenLike(gov_); - threshold = ONE / 2; - } - - // --- Math --- - uint256 constant ONE = 10 ** 18; - function add(uint x, uint y) internal pure returns (uint z) { - z = x + y; - require(z >= x); - } - function sub(uint x, uint y) internal pure returns (uint z) { - z = x - y; - require(z <= x); - } - function wmul(uint x, uint y) internal pure returns (uint z) { - z = x * y; - require(y == 0 || z / y == x); - z = z / ONE; + constructor(address governanceToken_) public { + governanceToken = TokenLike(governanceToken_); + threshold = WAD / 2; } // --- Data --- uint256 public threshold; // locked % needed to enact a proposal - TokenLike public gov; // governance token - uint256 public locked; // total locked gov + TokenLike public governanceToken; + uint256 public locked; // total locked governanceToken mapping(bytes32 => bool) public hasFired; // proposal => hasFired? mapping(address => uint256) public balances; // guy => lockedGovHowMuch mapping(address => bytes32) public picks; // guy => proposalHash @@ -53,7 +38,7 @@ contract Chief is DSNote { // --- Voting Interface --- function lock(uint256 wad) public note { - require(gov.transferFrom(msg.sender, address(this), wad)); + require(governanceToken.transferFrom(msg.sender, address(this), wad), "ds-chief-transfer-failed"); balances[msg.sender] = add(balances[msg.sender], wad); locked = add(locked, wad); @@ -63,15 +48,16 @@ contract Chief is DSNote { } function free(uint256 wad) public note { balances[msg.sender] = sub(balances[msg.sender], wad); - require(gov.transferFrom(address(this), msg.sender, wad)); + require(governanceToken.transferFrom( address(this), msg.sender, wad), "ds-chief-transfer-failed"); locked = sub(locked, wad); bytes32 currPick = picks[msg.sender]; if (currPick != bytes32(0) && !hasFired[currPick]) votes[currPick] = sub(votes[currPick], wad); } + function vote(bytes32 currPick) public { - require(!hasFired[currPick]); + require(!hasFired[currPick], "ds-chief-propposal-has-already-been-enacted"); uint256 weight = balances[msg.sender]; bytes32 prevPick = picks[msg.sender]; @@ -86,8 +72,8 @@ contract Chief is DSNote { } function exec(address app, bytes memory data) public { bytes32 proposal = keccak256(abi.encode(app, data)); - require(!hasFired[proposal]); - require(votes[proposal] > wmul(locked, threshold)); + require(!hasFired[proposal], "ds-chief-propposal-has-already-been-enacted"); + require(votes[proposal] > wmul(locked, threshold), "ds-chief-proposal-does-not-pass-threshold"); assembly { let ok := delegatecall(sub(gas, 5000), app, add(data, 0x20), mload(data), 0, 0) diff --git a/src/chief-v2.t.sol b/src/chief-v2.t.sol index b1b5988..153fa4e 100644 --- a/src/chief-v2.t.sol +++ b/src/chief-v2.t.sol @@ -3,7 +3,7 @@ pragma solidity >=0.5.0; import "ds-test/test.sol"; import "ds-token/token.sol"; -import "./Chief.sol"; +import "./chief-v2.sol"; contract Voter { DSToken gov; @@ -85,7 +85,7 @@ contract ChiefTest is DSTest { function test_sanity_setup_check() public { assertEq(chief.threshold(), 10 ** 18 / 2); assertEq(chief.locked(), 0); - assertEq(address(chief.gov()), address(gov)); + assertEq(address(chief.governanceToken()), address(gov)); assertEq(chief.balances(address(ben)), 0); assertEq(chief.picks(address(ben)), bytes32(0)); From bbfc5f2c0d82ba4d9f3750d4de131b46096e4682 Mon Sep 17 00:00:00 2001 From: Joshua Levine Date: Mon, 11 Feb 2019 21:23:22 -0800 Subject: [PATCH 4/4] fix typo propposal -> proposal --- src/chief-v2.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chief-v2.sol b/src/chief-v2.sol index f8f14c6..a22866e 100644 --- a/src/chief-v2.sol +++ b/src/chief-v2.sol @@ -48,7 +48,7 @@ contract Chief is DSNote, DSMath { } function free(uint256 wad) public note { balances[msg.sender] = sub(balances[msg.sender], wad); - require(governanceToken.transferFrom( address(this), msg.sender, wad), "ds-chief-transfer-failed"); + require(governanceToken.transferFrom(address(this), msg.sender, wad), "ds-chief-transfer-failed"); locked = sub(locked, wad); bytes32 currPick = picks[msg.sender]; @@ -57,7 +57,7 @@ contract Chief is DSNote, DSMath { } function vote(bytes32 currPick) public { - require(!hasFired[currPick], "ds-chief-propposal-has-already-been-enacted"); + require(!hasFired[currPick], "ds-chief-proposal-has-already-been-enacted"); uint256 weight = balances[msg.sender]; bytes32 prevPick = picks[msg.sender]; @@ -72,7 +72,7 @@ contract Chief is DSNote, DSMath { } function exec(address app, bytes memory data) public { bytes32 proposal = keccak256(abi.encode(app, data)); - require(!hasFired[proposal], "ds-chief-propposal-has-already-been-enacted"); + require(!hasFired[proposal], "ds-chief-proposal-has-already-been-enacted"); require(votes[proposal] > wmul(locked, threshold), "ds-chief-proposal-does-not-pass-threshold"); assembly {