-
Notifications
You must be signed in to change notification settings - Fork 598
feat: lock to propose #9430
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
feat: lock to propose #9430
Changes from all commits
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 |
|---|---|---|
|
|
@@ -37,6 +37,10 @@ contract Apella is IApella { | |
| gerousia = _gerousia; | ||
|
|
||
| configuration = DataStructures.Configuration({ | ||
| proposeConfig: DataStructures.ProposeConfiguration({ | ||
| lockDelay: Timestamp.wrap(3600), | ||
| lockAmount: 1000e18 | ||
| }), | ||
| votingDelay: Timestamp.wrap(3600), | ||
| votingDuration: Timestamp.wrap(3600), | ||
| executionDelay: Timestamp.wrap(3600), | ||
|
|
@@ -81,21 +85,7 @@ contract Apella is IApella { | |
| override(IApella) | ||
| returns (uint256) | ||
| { | ||
| users[msg.sender].sub(_amount); | ||
| total.sub(_amount); | ||
|
|
||
| uint256 withdrawalId = withdrawalCount++; | ||
|
|
||
| withdrawals[withdrawalId] = DataStructures.Withdrawal({ | ||
| amount: _amount, | ||
| unlocksAt: Timestamp.wrap(block.timestamp) + configuration.lockDelay(), | ||
| recipient: _to, | ||
| claimed: false | ||
| }); | ||
|
|
||
| emit WithdrawInitiated(withdrawalId, _to, _amount); | ||
|
|
||
| return withdrawalId; | ||
| return _initiateWithdraw(_to, _amount, configuration.withdrawalDelay()); | ||
| } | ||
|
|
||
| function finaliseWithdraw(uint256 _withdrawalId) external override(IApella) { | ||
|
|
@@ -114,21 +104,37 @@ contract Apella is IApella { | |
|
|
||
| function propose(IPayload _proposal) external override(IApella) returns (bool) { | ||
| require(msg.sender == gerousia, Errors.Apella__CallerNotGerousia(msg.sender, gerousia)); | ||
| return _propose(_proposal); | ||
| } | ||
|
|
||
| uint256 proposalId = proposalCount++; | ||
|
|
||
| proposals[proposalId] = DataStructures.Proposal({ | ||
| config: configuration, | ||
| state: DataStructures.ProposalState.Pending, | ||
| payload: _proposal, | ||
| creator: msg.sender, | ||
| creation: Timestamp.wrap(block.timestamp), | ||
| summedBallot: DataStructures.Ballot({yea: 0, nea: 0}) | ||
| }); | ||
| /** | ||
| * @notice Propose a new proposal by locking up a bunch of power | ||
| * | ||
| * Beware that if the gerousia changes these proposals will also be dropped | ||
| * This is to ensure consistency around way proposals are made, and they should | ||
| * really be using the proposal logic in Gerousia, which might have a similar | ||
| * mechanism in place as well. | ||
| * It is here for emergency purposes. | ||
| * Using the lock should be a last resort if the Gerousia is broken. | ||
|
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. Maybe mention that gerousia is broken if no proposals are made through it, its not immediately clear that this means validators are no longer able to make proposals
Contributor
Author
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. The "Gerousia" can also be seen as broken if it is not agreeing with what token holders want, so it is also a way to bypass it 🤷. |
||
| * | ||
| * @param _proposal The proposal to propose | ||
| * @param _to The address to send the lock to | ||
| * @return True if the proposal was proposed | ||
| */ | ||
| function proposeWithLock(IPayload _proposal, address _to) | ||
| external | ||
| override(IApella) | ||
| returns (bool) | ||
| { | ||
| uint256 availablePower = users[msg.sender].powerNow(); | ||
| uint256 amount = configuration.proposeConfig.lockAmount; | ||
|
|
||
| emit Proposed(proposalId, address(_proposal)); | ||
| require( | ||
| amount <= availablePower, Errors.Apella__InsufficientPower(msg.sender, availablePower, amount) | ||
| ); | ||
|
|
||
| return true; | ||
| _initiateWithdraw(_to, amount, configuration.proposeConfig.lockDelay); | ||
|
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. Perhaps we can make it more explicit that this delay is longer than
Contributor
Author
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. It is not the same delay as any other withdrawal, have renamed it to make it more clear as I could see the confusion. |
||
| return _propose(_proposal); | ||
| } | ||
|
|
||
| function vote(uint256 _proposalId, uint256 _amount, bool _support) | ||
|
|
@@ -264,7 +270,7 @@ contract Apella is IApella { | |
| } | ||
|
|
||
| // If the gerousia have changed we mark is as dropped | ||
| if (gerousia != self.creator) { | ||
| if (gerousia != self.gerousia) { | ||
| return DataStructures.ProposalState.Dropped; | ||
| } | ||
|
|
||
|
|
@@ -294,4 +300,42 @@ contract Apella is IApella { | |
|
|
||
| return DataStructures.ProposalState.Expired; | ||
| } | ||
|
|
||
| function _initiateWithdraw(address _to, uint256 _amount, Timestamp _delay) | ||
| internal | ||
| returns (uint256) | ||
| { | ||
| users[msg.sender].sub(_amount); | ||
| total.sub(_amount); | ||
|
|
||
| uint256 withdrawalId = withdrawalCount++; | ||
|
|
||
| withdrawals[withdrawalId] = DataStructures.Withdrawal({ | ||
| amount: _amount, | ||
| unlocksAt: Timestamp.wrap(block.timestamp) + _delay, | ||
| recipient: _to, | ||
| claimed: false | ||
| }); | ||
|
|
||
| emit WithdrawInitiated(withdrawalId, _to, _amount); | ||
|
|
||
| return withdrawalId; | ||
| } | ||
|
|
||
| function _propose(IPayload _proposal) internal returns (bool) { | ||
| uint256 proposalId = proposalCount++; | ||
|
|
||
| proposals[proposalId] = DataStructures.Proposal({ | ||
| config: configuration, | ||
| state: DataStructures.ProposalState.Pending, | ||
| payload: _proposal, | ||
| gerousia: gerousia, | ||
| creation: Timestamp.wrap(block.timestamp), | ||
| summedBallot: DataStructures.Ballot({yea: 0, nea: 0}) | ||
| }); | ||
|
|
||
| emit Proposed(proposalId, address(_proposal)); | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity >=0.8.27; | ||
|
|
||
| import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; | ||
| import {ApellaBase} from "./base.t.sol"; | ||
| import {IApella} from "@aztec/governance/interfaces/IApella.sol"; | ||
| import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; | ||
| import {Errors} from "@aztec/governance/libraries/Errors.sol"; | ||
| import {DataStructures} from "@aztec/governance/libraries/DataStructures.sol"; | ||
|
|
||
| contract ProposeWithLockTest is ApellaBase { | ||
| function test_WhenCallerHasInsufficientPower() external { | ||
| // it revert | ||
| DataStructures.Configuration memory config = apella.getConfiguration(); | ||
| vm.expectRevert( | ||
| abi.encodeWithSelector( | ||
| Errors.Apella__InsufficientPower.selector, address(this), 0, config.proposeConfig.lockAmount | ||
| ) | ||
| ); | ||
| apella.proposeWithLock(IPayload(address(0)), address(this)); | ||
| } | ||
|
|
||
| function test_WhenCallerHasSufficientPower(address _proposal) external { | ||
| // it creates a withdrawal with the lock amount and delay | ||
| // it creates a new proposal with current config | ||
| // it emits a {ProposalCreated} event | ||
| // it returns true | ||
| DataStructures.Configuration memory config = apella.getConfiguration(); | ||
| token.mint(address(this), config.proposeConfig.lockAmount); | ||
|
|
||
| token.approve(address(apella), config.proposeConfig.lockAmount); | ||
| apella.deposit(address(this), config.proposeConfig.lockAmount); | ||
|
|
||
| proposalId = apella.proposalCount(); | ||
|
|
||
| vm.expectEmit(true, true, true, true, address(apella)); | ||
| emit IApella.Proposed(proposalId, _proposal); | ||
|
|
||
| assertTrue(apella.proposeWithLock(IPayload(_proposal), address(this))); | ||
|
|
||
| DataStructures.Proposal memory proposal = apella.getProposal(proposalId); | ||
| assertEq(proposal.config.executionDelay, config.executionDelay); | ||
| assertEq(proposal.config.gracePeriod, config.gracePeriod); | ||
| assertEq(proposal.config.minimumVotes, config.minimumVotes); | ||
| assertEq(proposal.config.quorum, config.quorum); | ||
| assertEq(proposal.config.voteDifferential, config.voteDifferential); | ||
| assertEq(proposal.config.votingDelay, config.votingDelay); | ||
| assertEq(proposal.config.votingDuration, config.votingDuration); | ||
| assertEq(proposal.creation, Timestamp.wrap(block.timestamp)); | ||
| assertEq(proposal.gerousia, address(gerousia)); | ||
| assertEq(proposal.summedBallot.nea, 0); | ||
| assertEq(proposal.summedBallot.yea, 0); | ||
| assertTrue(proposal.state == DataStructures.ProposalState.Pending); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| ProposeWithLockTest | ||
| ├── when caller has insufficient power | ||
| │ └── it revert | ||
| └── when caller has sufficient power | ||
| ├── it creates a withdrawal with the lock amount and delay | ||
| ├── it creates a new proposal with current config | ||
| ├── it emits a {ProposalCreated} event | ||
| └── it returns true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make an issue to update the delays here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#9750