-
Notifications
You must be signed in to change notification settings - Fork 45
The next version of chief #7
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
jparklev marked this conversation as resolved.
Outdated
|
||
| 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)); | ||
|
jparklev marked this conversation as resolved.
Outdated
|
||
| 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)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TransferFrom should be Transfer. One could also consider merging
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually in |
||
| 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) } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider passing the returndata of the delegatecall to the caller of |
||
| } | ||
|
|
||
| hasFired[proposal] = true; | ||
| emit Executed(msg.sender, proposal, app, data); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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)); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.