@@ -11,13 +11,15 @@ contract NODLMigration {
1111 struct Proposal {
1212 address target;
1313 uint256 amount;
14+ uint256 lastVote;
1415 uint8 totalVotes;
1516 bool executed;
1617 }
1718
1819 NODL public nodl;
1920 mapping (address => bool ) public isOracle;
2021 uint8 public threshold;
22+ uint256 public delay;
2123
2224 // We track votes in a seperate mapping to avoid having to write helper functions to
2325 // expose the votes for each proposal.
@@ -28,16 +30,19 @@ contract NODLMigration {
2830 error AlreadyExecuted (bytes32 proposal );
2931 error ParametersChanged (bytes32 proposal );
3032 error NotAnOracle (address user );
33+ error NotYetWithdrawable (bytes32 proposal );
34+ error NotEnoughVotes (bytes32 proposal );
3135
3236 event VoteStarted (bytes32 indexed proposal , address oracle , address indexed user , uint256 amount );
3337 event Voted (bytes32 indexed proposal , address oracle );
34- event Bridged (bytes32 indexed proposal , address indexed user , uint256 amount );
38+ event Withdrawn (bytes32 indexed proposal , address indexed user , uint256 amount );
3539
3640 /// @param bridgeOracles Array of oracle accounts that will be able to bridge the tokens.
3741 /// @param token Contract address of the NODL token.
3842 /// @param minVotes Minimum number of votes required to bridge the tokens. This needs to be
3943 /// less than or equal to the number of oracles and is expected to be above 1.
40- constructor (address [] memory bridgeOracles , NODL token , uint8 minVotes ) {
44+ /// @param minDelay Minimum delay in blocks before bridged tokens can be minted.
45+ constructor (address [] memory bridgeOracles , NODL token , uint8 minVotes , uint256 minDelay ) {
4146 assert (bridgeOracles.length >= minVotes);
4247 assert (minVotes > 1 );
4348
@@ -46,10 +51,11 @@ contract NODLMigration {
4651 }
4752 nodl = token;
4853 threshold = minVotes;
54+ delay = minDelay;
4955 }
5056
5157 /// @notice Bridge some tokens from the Nodle Parachain to the ZkSync contracts. This
52- /// tracks "votes" from each oracle and only bridges the tokens if the threshold is met .
58+ /// tracks "votes" from each oracle and unlocks execution after a withdrawal delay .
5359 /// @param paraTxHash The transaction hash on the Parachain for this transfer.
5460 /// @param user The user address.
5561 /// @param amount The amount of NODL tokens that the user has burnt on the Parachain.
@@ -61,15 +67,22 @@ contract NODLMigration {
6167 _mustNotHaveVotedYet (paraTxHash, msg .sender );
6268 _mustNotBeChangingParameters (paraTxHash, user, amount);
6369 _recordVote (paraTxHash, msg .sender );
64-
65- if (_enoughVotes (paraTxHash)) {
66- _mintTokens (paraTxHash, user, amount);
67- }
6870 } else {
6971 _createVote (paraTxHash, msg .sender , user, amount);
7072 }
7173 }
7274
75+ /// @notice Withdraw the NODL tokens from the contract to the user's address if the
76+ /// proposal has enough votes and has passed the safety delay.
77+ /// @param paraTxHash The transaction hash on the Parachain for this transfer.
78+ function withdraw (bytes32 paraTxHash ) external {
79+ _mustNotHaveExecutedYet (paraTxHash);
80+ _mustHaveEnoughVotes (paraTxHash);
81+ _mustBePastSafetyDelay (paraTxHash);
82+
83+ _withdraw (paraTxHash, proposals[paraTxHash].target, proposals[paraTxHash].amount);
84+ }
85+
7386 function _mustBeAnOracle (address maybeOracle ) internal view {
7487 if (! isOracle[maybeOracle]) {
7588 revert NotAnOracle (maybeOracle);
@@ -94,6 +107,18 @@ contract NODLMigration {
94107 }
95108 }
96109
110+ function _mustBePastSafetyDelay (bytes32 proposal ) internal view {
111+ if (block .number - proposals[proposal].lastVote < delay) {
112+ revert NotYetWithdrawable (proposal);
113+ }
114+ }
115+
116+ function _mustHaveEnoughVotes (bytes32 proposal ) internal view {
117+ if (proposals[proposal].totalVotes < threshold) {
118+ revert NotEnoughVotes (proposal);
119+ }
120+ }
121+
97122 function _proposalExists (bytes32 proposal ) internal view returns (bool ) {
98123 return proposals[proposal].totalVotes > 0 && proposals[proposal].amount > 0 ;
99124 }
@@ -103,6 +128,7 @@ contract NODLMigration {
103128 proposals[proposal].target = user;
104129 proposals[proposal].amount = amount;
105130 proposals[proposal].totalVotes = 1 ;
131+ proposals[proposal].lastVote = block .number ;
106132
107133 emit VoteStarted (proposal, oracle, user, amount);
108134 }
@@ -111,18 +137,15 @@ contract NODLMigration {
111137 voted[oracle][proposal] = true ;
112138 // this is safe since we are unlikely to have maxUint8 oracles to manage
113139 proposals[proposal].totalVotes += 1 ;
140+ proposals[proposal].lastVote = block .number ;
114141
115142 emit Voted (proposal, oracle);
116143 }
117144
118- function _enoughVotes (bytes32 proposal ) internal view returns (bool ) {
119- return proposals[proposal].totalVotes >= threshold;
120- }
121-
122- function _mintTokens (bytes32 proposal , address user , uint256 amount ) internal {
145+ function _withdraw (bytes32 proposal , address user , uint256 amount ) internal {
123146 proposals[proposal].executed = true ;
124147 nodl.mint (user, amount);
125148
126- emit Bridged (proposal, user, amount);
149+ emit Withdrawn (proposal, user, amount);
127150 }
128151}
0 commit comments