@@ -3,42 +3,75 @@ pragma solidity ^0.8.20;
33
44import "./ProposerManager.sol " ;
55import "./Nightfall.sol " ;
6+ import "./X509/Certified.sol " ;
7+
8+
69/// @title Proposers
710/// @notice An Round-Robin implementation for choosing proposers
811
9- contract RoundRobin is ProposerManager {
12+ contract RoundRobin is ProposerManager , Certified {
13+
1014 mapping (address => Proposer) public proposers;
1115 mapping (address => uint ) public pending_withdraws;
1216 mapping (string => bool ) public proposer_urls;
17+ // When a proposer voluntarily deregisters:
18+ // Record the block number. Enforce that they cannot reregister until a certain COOLDOWN_BLOCKS window has passed.
19+ mapping (address => uint ) public last_exit_block;
1320 Proposer private current;
1421 uint public start_l1_block;
1522 int public start_l2_block;
1623 uint public proposer_count;
1724 uint public immutable STAKE;
1825 uint public immutable DING;
26+ // When the current proposer voluntarily deregisters, a small but nontrivial penalty is deducted.
27+ uint public immutable EXIT_PENALTY;
28+ // This is the number of blocks that must pass before a proposer can reregister after exiting
29+ uint public immutable COOLDOWN_BLOCKS;
1930 uint public immutable ROTATION_BlOCKS;
2031 uint public escrow = 0 ;
2132 Nightfall private nightfall;
22- address private owner;
33+
34+ // instance of the certified contract
35+ Certified private certified;
2336
2437 modifier only_owner () {
2538 require (msg .sender == owner, "Only the owner can call this function " );
2639 _;
2740 }
2841
2942 constructor (
43+ address x509_address ,
44+ address sanctionsListAddress ,
3045 address default_proposer_address ,
3146 string memory default_proposer_url ,
3247 uint stake ,
3348 uint ding ,
49+ uint exit_penalty ,
50+ uint cooling_blocks ,
3451 uint rotation_blocks
35- ) payable {
52+ )
53+ Certified (
54+ X509Interface (x509_address),
55+ SanctionsListInterface (sanctionsListAddress)
56+ )
57+ payable
58+ {
3659 STAKE = stake;
3760 DING = ding;
61+ EXIT_PENALTY = exit_penalty;
62+ COOLDOWN_BLOCKS = cooling_blocks;
63+ require (
64+ cooling_blocks > 0 ,
65+ "Cooling blocks must be greater than zero "
66+ );
67+ require (
68+ stake >= exit_penalty,
69+ "Stake must be greater than exit penalty "
70+ );
3871 ROTATION_BlOCKS = rotation_blocks;
3972 require (
4073 msg .value == STAKE,
41- "You have not paid the correct staking amount "
74+ "You have not paid the correct staking amount during deployment "
4275 );
4376 current = Proposer ({
4477 stake: STAKE,
@@ -47,10 +80,20 @@ contract RoundRobin is ProposerManager {
4780 next_addr: default_proposer_address,
4881 previous_addr: default_proposer_address
4982 });
83+ escrow += STAKE;
5084 proposers[default_proposer_address] = current;
5185 proposer_urls[default_proposer_url] = true ;
5286 proposer_count = 1 ;
53- owner = msg .sender ;
87+ }
88+
89+ function set_x509_address (address x509_address ) external onlyOwner {
90+ x509 = X509 (x509_address);
91+ }
92+
93+ function set_sanctions_list (
94+ address sanctionsListAddress
95+ ) external onlyOwner {
96+ sanctionsList = SanctionsListInterface (sanctionsListAddress);
5497 }
5598
5699 // we set the nightfall contract address later because we probably don't know it at the time of deployment
@@ -69,10 +112,14 @@ contract RoundRobin is ProposerManager {
69112
70113 function add_proposer (
71114 string calldata proposer_url
72- ) external payable override {
115+ ) external payable override onlyCertified{
116+ // Enforce cooldown only if they have previously exited
117+ if (last_exit_block[msg .sender ] != 0 ) {
118+ require (block .number > last_exit_block[msg .sender ] + COOLDOWN_BLOCKS, "Cooldown period not met " );
119+ }
73120 require (
74121 msg .value == STAKE,
75- "You have not paid the correct staking amount "
122+ "You have not paid the correct staking amount during registration "
76123 );
77124 require (
78125 proposers[msg .sender ].addr == address (0 ),
@@ -83,6 +130,7 @@ contract RoundRobin is ProposerManager {
83130 "This proposer URL is already in use "
84131 );
85132 escrow += STAKE;
133+
86134 // we add the new proposer behind the current proposer, so it will be the last to be called for
87135 // first, insert its address in the linked list
88136 address current_address = current.addr;
@@ -126,10 +174,26 @@ contract RoundRobin is ProposerManager {
126174 proposer_address != address (0 ),
127175 "The proposer address cannot be zero "
128176 );
129- require (
130- proposer_address != current.addr,
131- "You cannot remove the current proposer "
177+ if (msg .sender == current.addr) {
178+ require (
179+ proposer_count > 1 ,
180+ "Cannot deregister the only active proposer "
181+ );
182+ require (
183+ proposers[proposer_address].stake >= EXIT_PENALTY,
184+ "Insufficient stake for exit "
132185 );
186+
187+ proposers[proposer_address].stake -= EXIT_PENALTY;
188+ escrow -= EXIT_PENALTY;
189+
190+ // Rotate to the next proposer before removing
191+ current = proposers[current.next_addr];
192+ start_l1_block = block .number ;
193+ start_l2_block = nightfall.layer2_block_number ();
194+
195+ last_exit_block[proposer_address] = block .number ;
196+ }
133197 Proposer storage this_proposer = proposers[proposer_address]; // don't forget these only create references
134198 Proposer storage next_proposer = proposers[this_proposer.next_addr];
135199 Proposer storage previous_proposer = proposers[
@@ -195,5 +259,6 @@ contract RoundRobin is ProposerManager {
195259 return ;
196260 }
197261 proposer.stake = uint (new_stake);
262+ escrow -= DING;
198263 }
199264}
0 commit comments