-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
918 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.27; | ||
|
||
import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; | ||
import {IApella} from "@aztec/governance/interfaces/IApella.sol"; | ||
import {IGerousia} from "@aztec/governance/interfaces/IGerousia.sol"; | ||
import {Errors} from "@aztec/governance/libraries/Errors.sol"; | ||
|
||
import {Slot, SlotLib} from "@aztec/core/libraries/TimeMath.sol"; | ||
import {ILeonidas} from "@aztec/core/interfaces/ILeonidas.sol"; | ||
|
||
/** | ||
* @notice A Gerousia implementation following the empire model | ||
* Beware that while governance generally do not care about the implementation | ||
* this implementation will since it is dependent on the sequencer selection. | ||
* This also means that the implementation here will need to be "updated" if | ||
* the interfaces of the sequencer selection changes, for exampel going optimistic. | ||
*/ | ||
contract Gerousia is IGerousia { | ||
using SlotLib for Slot; | ||
|
||
struct RoundAccounting { | ||
Slot lastVote; | ||
address leader; | ||
bool executed; | ||
mapping(address proposal => uint256 count) yeaCount; | ||
} | ||
|
||
uint256 public constant LIFETIME_IN_ROUNDS = 5; | ||
|
||
IApella public immutable APELLA; | ||
IRegistry public immutable REGISTRY; | ||
uint256 public immutable N; | ||
uint256 public immutable M; | ||
|
||
mapping(address instance => mapping(uint256 roundNumber => RoundAccounting)) public rounds; | ||
|
||
constructor(IApella _apella, IRegistry _registry, uint256 _n, uint256 _m) { | ||
APELLA = _apella; | ||
REGISTRY = _registry; | ||
N = _n; | ||
M = _m; | ||
|
||
require(N > M / 2, Errors.Gerousia__InvalidNAndMValues(N, M)); | ||
require(N <= M, Errors.Gerousia__NCannotBeLargerTHanM(N, M)); | ||
} | ||
|
||
// Note that this one is heavily realying on the fact that this contract | ||
// could be updated at the same time as another upgrade is made. | ||
|
||
/** | ||
* @notice Cast a vote on a proposal | ||
* Note that this is assuming that the canonical rollup will cast it as | ||
* part of block production, we will perform it here | ||
* | ||
* @param _proposal - The proposal to cast a vote on | ||
* | ||
* @return True if executed successfully, false otherwise | ||
*/ | ||
function vote(address _proposal) external override(IGerousia) returns (bool) { | ||
require(_proposal.code.length > 0, Errors.Gerousia__ProposalHaveNoCode(_proposal)); | ||
|
||
address instance = REGISTRY.getRollup(); | ||
require(instance.code.length > 0, Errors.Gerousia__InstanceHaveNoCode(instance)); | ||
|
||
ILeonidas selection = ILeonidas(instance); | ||
Slot currentSlot = selection.getCurrentSlot(); | ||
|
||
uint256 roundNumber = computeRound(currentSlot); | ||
|
||
RoundAccounting storage round = rounds[instance][roundNumber]; | ||
|
||
require(currentSlot > round.lastVote, Errors.Gerousia__VoteAlreadyCastForSlot(currentSlot)); | ||
|
||
address proposer = selection.getCurrentProposer(); | ||
require(msg.sender == proposer, Errors.Gerousia__OnlyProposerCanVote(msg.sender, proposer)); | ||
|
||
round.yeaCount[_proposal] += 1; | ||
round.lastVote = currentSlot; | ||
|
||
// @todo We can optimise here for gas by storing some of it packed with the leader. | ||
if (round.leader != _proposal && round.yeaCount[_proposal] > round.yeaCount[round.leader]) { | ||
round.leader = _proposal; | ||
} | ||
|
||
emit VoteCast(_proposal, roundNumber, msg.sender); | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* @notice Push the proposal to the appela | ||
* | ||
* @param _roundNumber - The round number to execute | ||
* | ||
* @return True if executed successfully, false otherwise | ||
*/ | ||
function pushProposal(uint256 _roundNumber) external override(IGerousia) returns (bool) { | ||
// Need to ensure that the round is not active. | ||
address instance = REGISTRY.getRollup(); | ||
require(instance.code.length > 0, Errors.Gerousia__InstanceHaveNoCode(instance)); | ||
|
||
ILeonidas selection = ILeonidas(instance); | ||
Slot currentSlot = selection.getCurrentSlot(); | ||
|
||
uint256 currentRound = computeRound(currentSlot); | ||
require(_roundNumber < currentRound, Errors.Gerousia__CanOnlyPushProposalInPast()); | ||
require( | ||
_roundNumber + LIFETIME_IN_ROUNDS >= currentRound, | ||
Errors.Gerousia__ProposalTooOld(_roundNumber) | ||
); | ||
|
||
RoundAccounting storage round = rounds[instance][_roundNumber]; | ||
require(!round.executed, Errors.Gerousia__ProposalAlreadyExecuted(_roundNumber)); | ||
require(round.leader != address(0), Errors.Gerousia__ProposalCannotBeAddressZero()); | ||
require(round.yeaCount[round.leader] >= N, Errors.Gerousia__InsufficientVotes()); | ||
|
||
round.executed = true; | ||
|
||
emit ProposalPushed(round.leader, _roundNumber); | ||
|
||
require(APELLA.propose(round.leader), Errors.Gerousia__FailedToPropose(round.leader)); | ||
return true; | ||
} | ||
|
||
/** | ||
* @notice Fetch the yea count for a specific proposal in a specific round on a specific instance | ||
* | ||
* @param _instance - The address of the instance | ||
* @param _round - The round to lookup | ||
* @param _proposal - The address of the proposal | ||
* | ||
* @return The number of yea votes | ||
*/ | ||
function yeaCount(address _instance, uint256 _round, address _proposal) | ||
external | ||
view | ||
override(IGerousia) | ||
returns (uint256) | ||
{ | ||
return rounds[_instance][_round].yeaCount[_proposal]; | ||
} | ||
|
||
/** | ||
* @notice Computes the round at the given slot | ||
* | ||
* @param _slot - The slot to compute round for | ||
* | ||
* @return The round number | ||
*/ | ||
function computeRound(Slot _slot) public view override(IGerousia) returns (uint256) { | ||
return _slot.unwrap() / M; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.27; | ||
|
||
interface IApella { | ||
function propose(address _proposal) external returns (bool); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.27; | ||
|
||
import {Slot} from "@aztec/core/libraries/TimeMath.sol"; | ||
|
||
interface IGerousia { | ||
event VoteCast(address indexed proposal, uint256 indexed round, address indexed voter); | ||
event ProposalPushed(address indexed proposal, uint256 indexed round); | ||
|
||
function vote(address _proposa) external returns (bool); | ||
function pushProposal(uint256 _roundNumber) external returns (bool); | ||
function yeaCount(address _instance, uint256 _round, address _proposal) | ||
external | ||
view | ||
returns (uint256); | ||
function computeRound(Slot _slot) external view returns (uint256); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.27; | ||
|
||
import {Test} from "forge-std/Test.sol"; | ||
|
||
import {Registry} from "@aztec/governance/Registry.sol"; | ||
import {Gerousia} from "@aztec/governance/Gerousia.sol"; | ||
|
||
import {IApella} from "@aztec/governance/interfaces/IApella.sol"; | ||
|
||
contract FakeApella is IApella { | ||
address public gerousia; | ||
|
||
mapping(address => bool) public proposals; | ||
|
||
function setGerousia(address _gerousia) external { | ||
gerousia = _gerousia; | ||
} | ||
|
||
function propose(address _proposal) external override(IApella) returns (bool) { | ||
proposals[_proposal] = true; | ||
return true; | ||
} | ||
} | ||
|
||
contract GerousiaBase is Test { | ||
Registry internal registry; | ||
FakeApella internal apella; | ||
Gerousia internal gerousia; | ||
|
||
function setUp() public virtual { | ||
registry = new Registry(address(this)); | ||
apella = new FakeApella(); | ||
|
||
gerousia = new Gerousia(apella, registry, 667, 1000); | ||
|
||
apella.setGerousia(address(gerousia)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.27; | ||
|
||
import {Test} from "forge-std/Test.sol"; | ||
import {Gerousia} from "@aztec/governance/Gerousia.sol"; | ||
import {Errors} from "@aztec/governance/libraries/Errors.sol"; | ||
import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; | ||
import {IApella} from "@aztec/governance/interfaces/IApella.sol"; | ||
|
||
contract ConstructorTest is Test { | ||
IApella internal constant APELLA = IApella(address(0x01)); | ||
IRegistry internal constant REGISTRY = IRegistry(address(0x02)); | ||
|
||
function test_WhenNIsLessThanOrEqualHalfOfM(uint256 _n, uint256 _m) external { | ||
// it revert | ||
|
||
uint256 n = bound(_n, 0, _m / 2); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(Errors.Gerousia__InvalidNAndMValues.selector, n, _m)); | ||
new Gerousia(APELLA, REGISTRY, n, _m); | ||
} | ||
|
||
function test_WhenNLargerThanM(uint256 _n, uint256 _m) external { | ||
// it revert | ||
uint256 m = bound(_m, 0, type(uint256).max - 1); | ||
uint256 n = bound(_n, m + 1, type(uint256).max); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(Errors.Gerousia__NCannotBeLargerTHanM.selector, n, m)); | ||
new Gerousia(APELLA, REGISTRY, n, m); | ||
} | ||
|
||
function test_WhenNIsGreatherThanHalfOfM(uint256 _n, uint256 _m) external { | ||
// it deploys | ||
|
||
uint256 m = bound(_m, 1, type(uint256).max); | ||
uint256 n = bound(_n, m / 2 + 1, m); | ||
|
||
Gerousia g = new Gerousia(APELLA, REGISTRY, n, m); | ||
|
||
assertEq(address(g.APELLA()), address(APELLA)); | ||
assertEq(address(g.REGISTRY()), address(REGISTRY)); | ||
assertEq(g.N(), n); | ||
assertEq(g.M(), m); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
ConstructorTest | ||
├── when N is less than or equal half of M | ||
│ └── it revert | ||
├── when N larger than M | ||
│ └── it revert | ||
└── when N is greather than half of M | ||
└── it deploys |
10 changes: 10 additions & 0 deletions
10
l1-contracts/test/governance/gerousia/mocks/FalsyApella.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.27; | ||
|
||
import {IApella} from "@aztec/governance/interfaces/IApella.sol"; | ||
|
||
contract FalsyApella is IApella { | ||
function propose(address) external pure override(IApella) returns (bool) { | ||
return false; | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
l1-contracts/test/governance/gerousia/mocks/FaultyApella.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.27; | ||
|
||
import {IApella} from "@aztec/governance/interfaces/IApella.sol"; | ||
|
||
contract FaultyApella is IApella { | ||
error Faulty(); | ||
|
||
function propose(address) external pure override(IApella) returns (bool) { | ||
require(false, Faulty()); | ||
return true; | ||
} | ||
} |
Oops, something went wrong.