@arbitrum/nitro-contracts 1.0.0-beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. package/.prettierrc +5 -0
  2. package/.solhint.json +18 -0
  3. package/deploy/BridgeStubCreator.js +10 -0
  4. package/deploy/HashProofHelper.js +13 -0
  5. package/deploy/InboxStubCreator.js +17 -0
  6. package/deploy/OneStepProofEntryCreator.js +19 -0
  7. package/deploy/OneStepProver0Creator.js +14 -0
  8. package/deploy/OneStepProverHostIoCreator.js +14 -0
  9. package/deploy/OneStepProverMathCreator.js +14 -0
  10. package/deploy/OneStepProverMemoryCreator.js +14 -0
  11. package/deploy/SequencerInboxStubCreator.js +13 -0
  12. package/deploy/ValueArrayTesterCreator.js +13 -0
  13. package/hardhat.config.ts +47 -0
  14. package/hardhat.prod-config.js +18 -0
  15. package/package.json +49 -0
  16. package/scripts/build.bash +5 -0
  17. package/src/bridge/Bridge.sol +168 -0
  18. package/src/bridge/IBridge.sol +68 -0
  19. package/src/bridge/IInbox.sol +80 -0
  20. package/src/bridge/IMessageProvider.sol +11 -0
  21. package/src/bridge/IOutbox.sol +52 -0
  22. package/src/bridge/ISequencerInbox.sol +85 -0
  23. package/src/bridge/Inbox.sol +414 -0
  24. package/src/bridge/Messages.sol +38 -0
  25. package/src/bridge/Outbox.sol +188 -0
  26. package/src/bridge/SequencerInbox.sol +274 -0
  27. package/src/challenge/ChallengeLib.sol +135 -0
  28. package/src/challenge/ChallengeManager.sol +367 -0
  29. package/src/challenge/IChallengeManager.sol +75 -0
  30. package/src/challenge/IChallengeResultReceiver.sol +13 -0
  31. package/src/libraries/AddressAliasHelper.sol +29 -0
  32. package/src/libraries/AdminFallbackProxy.sol +153 -0
  33. package/src/libraries/ArbitrumProxy.sol +20 -0
  34. package/src/libraries/Constants.sol +10 -0
  35. package/src/libraries/CryptographyPrimitives.sol +323 -0
  36. package/src/libraries/DelegateCallAware.sol +44 -0
  37. package/src/libraries/Error.sol +38 -0
  38. package/src/libraries/IGasRefunder.sol +35 -0
  39. package/src/libraries/MerkleLib.sol +46 -0
  40. package/src/libraries/MessageTypes.sol +14 -0
  41. package/src/libraries/SecondaryLogicUUPSUpgradeable.sol +58 -0
  42. package/src/libraries/UUPSNotUpgradeable.sol +56 -0
  43. package/src/mocks/BridgeStub.sol +115 -0
  44. package/src/mocks/Counter.sol +13 -0
  45. package/src/mocks/ExecutionManager.sol +41 -0
  46. package/src/mocks/InboxStub.sol +131 -0
  47. package/src/mocks/MockResultReceiver.sol +59 -0
  48. package/src/mocks/SequencerInboxStub.sol +42 -0
  49. package/src/mocks/SimpleProxy.sol +19 -0
  50. package/src/node-interface/NodeInterface.sol +50 -0
  51. package/src/osp/HashProofHelper.sol +154 -0
  52. package/src/osp/IOneStepProofEntry.sol +20 -0
  53. package/src/osp/IOneStepProver.sol +27 -0
  54. package/src/osp/OneStepProofEntry.sol +129 -0
  55. package/src/osp/OneStepProver0.sol +566 -0
  56. package/src/osp/OneStepProverHostIo.sol +357 -0
  57. package/src/osp/OneStepProverMath.sol +514 -0
  58. package/src/osp/OneStepProverMemory.sol +313 -0
  59. package/src/precompiles/ArbAddressTable.sol +60 -0
  60. package/src/precompiles/ArbAggregator.sol +62 -0
  61. package/src/precompiles/ArbBLS.sol +53 -0
  62. package/src/precompiles/ArbDebug.sol +39 -0
  63. package/src/precompiles/ArbFunctionTable.sol +29 -0
  64. package/src/precompiles/ArbGasInfo.sol +121 -0
  65. package/src/precompiles/ArbInfo.sol +15 -0
  66. package/src/precompiles/ArbOwner.sol +65 -0
  67. package/src/precompiles/ArbOwnerPublic.sol +18 -0
  68. package/src/precompiles/ArbRetryableTx.sol +89 -0
  69. package/src/precompiles/ArbStatistics.sol +29 -0
  70. package/src/precompiles/ArbSys.sol +134 -0
  71. package/src/precompiles/ArbosActs.sol +41 -0
  72. package/src/precompiles/ArbosTest.sol +14 -0
  73. package/src/rollup/BridgeCreator.sol +120 -0
  74. package/src/rollup/IRollupCore.sol +152 -0
  75. package/src/rollup/IRollupLogic.sol +183 -0
  76. package/src/rollup/Node.sol +99 -0
  77. package/src/rollup/RollupAdminLogic.sol +322 -0
  78. package/src/rollup/RollupCore.sol +627 -0
  79. package/src/rollup/RollupCreator.sol +133 -0
  80. package/src/rollup/RollupEventBridge.sol +46 -0
  81. package/src/rollup/RollupLib.sol +135 -0
  82. package/src/rollup/RollupUserLogic.sol +712 -0
  83. package/src/rollup/ValidatorUtils.sol +243 -0
  84. package/src/rollup/ValidatorWallet.sol +76 -0
  85. package/src/rollup/ValidatorWalletCreator.sol +43 -0
  86. package/src/state/Deserialize.sol +321 -0
  87. package/src/state/GlobalState.sol +44 -0
  88. package/src/state/Instructions.sol +159 -0
  89. package/src/state/Machine.sol +65 -0
  90. package/src/state/MerkleProof.sol +99 -0
  91. package/src/state/Module.sol +33 -0
  92. package/src/state/ModuleMemory.sol +42 -0
  93. package/src/state/PcArray.sol +45 -0
  94. package/src/state/PcStack.sol +32 -0
  95. package/src/state/StackFrame.sol +63 -0
  96. package/src/state/Value.sol +65 -0
  97. package/src/state/ValueArray.sol +47 -0
  98. package/src/state/ValueStack.sol +39 -0
  99. package/src/test-helpers/CryptographyPrimitivesTester.sol +27 -0
  100. package/src/test-helpers/MessageTester.sol +34 -0
  101. package/src/test-helpers/ValueArrayTester.sol +34 -0
  102. package/test/contract/arbRollup.spec.ts +869 -0
  103. package/test/contract/common/challengeLib.ts +43 -0
  104. package/test/contract/common/globalStateLib.ts +17 -0
  105. package/test/contract/common/rolluplib.ts +259 -0
  106. package/test/contract/cryptographyPrimitives.spec.ts +82 -0
  107. package/test/contract/sequencerInboxForceInclude.spec.ts +516 -0
  108. package/test/contract/utils.ts +40 -0
  109. package/test/prover/hash-proofs.ts +75 -0
  110. package/test/prover/one-step-proof.ts +93 -0
  111. package/test/prover/proofs/.gitkeep +0 -0
  112. package/test/prover/value-arrays.ts +11 -0
  113. package/tsconfig.json +13 -0
@@ -0,0 +1,712 @@
1
+ // Copyright 2021-2022, Offchain Labs, Inc.
2
+ // For license information, see https://github.com/nitro/blob/master/LICENSE
3
+ // SPDX-License-Identifier: BUSL-1.1
4
+
5
+ pragma solidity ^0.8.0;
6
+
7
+ import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
8
+
9
+ import {IRollupUser} from "./IRollupLogic.sol";
10
+ import "../libraries/UUPSNotUpgradeable.sol";
11
+ import "./RollupCore.sol";
12
+
13
+ abstract contract AbsRollupUserLogic is
14
+ RollupCore,
15
+ UUPSNotUpgradeable,
16
+ IRollupUserAbs,
17
+ IChallengeResultReceiver
18
+ {
19
+ using NodeLib for Node;
20
+ using GlobalStateLib for GlobalState;
21
+
22
+ modifier onlyValidator() {
23
+ require(isValidator[msg.sender], "NOT_VALIDATOR");
24
+ _;
25
+ }
26
+
27
+ function isERC20Enabled() public view override returns (bool) {
28
+ return stakeToken != address(0);
29
+ }
30
+
31
+ /**
32
+ * @notice Reject the next unresolved node
33
+ * @param stakerAddress Example staker staked on sibling, used to prove a node is on an unconfirmable branch and can be rejected
34
+ */
35
+ function rejectNextNode(address stakerAddress) external onlyValidator whenNotPaused {
36
+ requireUnresolvedExists();
37
+ uint64 latestConfirmedNodeNum = latestConfirmed();
38
+ uint64 firstUnresolvedNodeNum = firstUnresolvedNode();
39
+ Node storage firstUnresolvedNode_ = getNodeStorage(firstUnresolvedNodeNum);
40
+
41
+ if (firstUnresolvedNode_.prevNum == latestConfirmedNodeNum) {
42
+ /**If the first unresolved node is a child of the latest confirmed node, to prove it can be rejected, we show:
43
+ * a) Its deadline has expired
44
+ * b) *Some* staker is staked on a sibling
45
+
46
+ * The following three checks are sufficient to prove b:
47
+ */
48
+
49
+ // 1. StakerAddress is indeed a staker
50
+ require(isStakedOnLatestConfirmed(stakerAddress), "NOT_STAKED");
51
+
52
+ // 2. Staker's latest staked node hasn't been resolved; this proves that staker's latest staked node can't be a parent of firstUnresolvedNode
53
+ requireUnresolved(latestStakedNode(stakerAddress));
54
+
55
+ // 3. staker isn't staked on first unresolved node; this proves staker's latest staked can't be a child of firstUnresolvedNode (recall staking on node requires staking on all of its parents)
56
+ require(!nodeHasStaker(firstUnresolvedNodeNum, stakerAddress), "STAKED_ON_TARGET");
57
+ // If a staker is staked on a node that is neither a child nor a parent of firstUnresolvedNode, it must be a sibling, QED
58
+
59
+ // Verify the block's deadline has passed
60
+ firstUnresolvedNode_.requirePastDeadline();
61
+
62
+ getNodeStorage(latestConfirmedNodeNum).requirePastChildConfirmDeadline();
63
+
64
+ removeOldZombies(0);
65
+
66
+ // Verify that no staker is staked on this node
67
+ require(
68
+ firstUnresolvedNode_.stakerCount == countStakedZombies(firstUnresolvedNodeNum),
69
+ "HAS_STAKERS"
70
+ );
71
+ }
72
+ // Simpler case: if the first unreseolved node doesn't point to the last confirmed node, another branch was confirmed and can simply reject it outright
73
+ _rejectNextNode();
74
+
75
+ emit NodeRejected(firstUnresolvedNodeNum);
76
+ }
77
+
78
+ /**
79
+ * @notice Confirm the next unresolved node
80
+ * @param blockHash The block hash at the end of the assertion
81
+ * @param sendRoot The send root at the end of the assertion
82
+ */
83
+ function confirmNextNode(bytes32 blockHash, bytes32 sendRoot)
84
+ external
85
+ onlyValidator
86
+ whenNotPaused
87
+ {
88
+ requireUnresolvedExists();
89
+
90
+ uint64 nodeNum = firstUnresolvedNode();
91
+ Node storage node = getNodeStorage(nodeNum);
92
+
93
+ // Verify the block's deadline has passed
94
+ node.requirePastDeadline();
95
+
96
+ // Check that prev is latest confirmed
97
+ assert(node.prevNum == latestConfirmed());
98
+
99
+ Node storage prevNode = getNodeStorage(node.prevNum);
100
+ prevNode.requirePastChildConfirmDeadline();
101
+
102
+ removeOldZombies(0);
103
+
104
+ // Require only zombies are staked on siblings to this node, and there's at least one non-zombie staked on this node
105
+ uint256 stakedZombies = countStakedZombies(nodeNum);
106
+ uint256 zombiesStakedOnOtherChildren = countZombiesStakedOnChildren(node.prevNum) -
107
+ stakedZombies;
108
+ require(node.stakerCount > stakedZombies, "NO_STAKERS");
109
+ require(
110
+ prevNode.childStakerCount == node.stakerCount + zombiesStakedOnOtherChildren,
111
+ "NOT_ALL_STAKED"
112
+ );
113
+
114
+ confirmNode(nodeNum, blockHash, sendRoot);
115
+ }
116
+
117
+ /**
118
+ * @notice Create a new stake
119
+ * @param depositAmount The amount of either eth or tokens staked
120
+ */
121
+ function _newStake(uint256 depositAmount) internal onlyValidator whenNotPaused {
122
+ // Verify that sender is not already a staker
123
+ require(!isStaked(msg.sender), "ALREADY_STAKED");
124
+ require(!isZombie(msg.sender), "STAKER_IS_ZOMBIE");
125
+ require(depositAmount >= currentRequiredStake(), "NOT_ENOUGH_STAKE");
126
+
127
+ createNewStake(msg.sender, depositAmount);
128
+ }
129
+
130
+ /**
131
+ * @notice Move stake onto existing child node
132
+ * @param nodeNum Index of the node to move stake to. This must by a child of the node the staker is currently staked on
133
+ * @param nodeHash Node hash of nodeNum (protects against reorgs)
134
+ */
135
+ function stakeOnExistingNode(uint64 nodeNum, bytes32 nodeHash)
136
+ public
137
+ onlyValidator
138
+ whenNotPaused
139
+ {
140
+ require(isStakedOnLatestConfirmed(msg.sender), "NOT_STAKED");
141
+
142
+ require(
143
+ nodeNum >= firstUnresolvedNode() && nodeNum <= latestNodeCreated(),
144
+ "NODE_NUM_OUT_OF_RANGE"
145
+ );
146
+ Node storage node = getNodeStorage(nodeNum);
147
+ require(node.nodeHash == nodeHash, "NODE_REORG");
148
+ require(latestStakedNode(msg.sender) == node.prevNum, "NOT_STAKED_PREV");
149
+ stakeOnNode(msg.sender, nodeNum);
150
+ }
151
+
152
+ /**
153
+ * @notice Create a new node and move stake onto it
154
+ * @param assertion The assertion data
155
+ * @param expectedNodeHash The hash of the node being created (protects against reorgs)
156
+ */
157
+ function stakeOnNewNode(
158
+ RollupLib.Assertion calldata assertion,
159
+ bytes32 expectedNodeHash,
160
+ uint256 prevNodeInboxMaxCount
161
+ ) public onlyValidator whenNotPaused {
162
+ require(isStakedOnLatestConfirmed(msg.sender), "NOT_STAKED");
163
+ // Ensure staker is staked on the previous node
164
+ uint64 prevNode = latestStakedNode(msg.sender);
165
+
166
+ {
167
+ uint256 timeSinceLastNode = block.number - getNode(prevNode).createdAtBlock;
168
+ // Verify that assertion meets the minimum Delta time requirement
169
+ require(timeSinceLastNode >= minimumAssertionPeriod, "TIME_DELTA");
170
+
171
+ // Minimum size requirement: any assertion must consume at least all inbox messages
172
+ // put into L1 inbox before the prev node’s L1 blocknum.
173
+ // We make an exception if the machine enters the errored state,
174
+ // as it can't consume future batches.
175
+ require(
176
+ assertion.afterState.machineStatus == MachineStatus.ERRORED ||
177
+ assertion.afterState.globalState.getInboxPosition() >= prevNodeInboxMaxCount,
178
+ "TOO_SMALL"
179
+ );
180
+ // Minimum size requirement: any assertion must contain at least one block
181
+ require(assertion.numBlocks > 0, "EMPTY_ASSERTION");
182
+
183
+ // The rollup cannot advance normally from an errored state
184
+ require(
185
+ assertion.beforeState.machineStatus == MachineStatus.FINISHED,
186
+ "BAD_PREV_STATUS"
187
+ );
188
+ }
189
+ createNewNode(assertion, prevNode, prevNodeInboxMaxCount, expectedNodeHash);
190
+
191
+ stakeOnNode(msg.sender, latestNodeCreated());
192
+ }
193
+
194
+ /**
195
+ * @notice Refund a staker that is currently staked on or before the latest confirmed node
196
+ * @dev Since a staker is initially placed in the latest confirmed node, if they don't move it
197
+ * a griefer can remove their stake. It is recomended to batch together the txs to place a stake
198
+ * and move it to the desired node.
199
+ * @param stakerAddress Address of the staker whose stake is refunded
200
+ */
201
+ function returnOldDeposit(address stakerAddress) external override onlyValidator whenNotPaused {
202
+ require(latestStakedNode(stakerAddress) <= latestConfirmed(), "TOO_RECENT");
203
+ requireUnchallengedStaker(stakerAddress);
204
+ withdrawStaker(stakerAddress);
205
+ }
206
+
207
+ /**
208
+ * @notice Increase the amount staked for the given staker
209
+ * @param stakerAddress Address of the staker whose stake is increased
210
+ * @param depositAmount The amount of either eth or tokens deposited
211
+ */
212
+ function _addToDeposit(address stakerAddress, uint256 depositAmount)
213
+ internal
214
+ onlyValidator
215
+ whenNotPaused
216
+ {
217
+ requireUnchallengedStaker(stakerAddress);
218
+ increaseStakeBy(stakerAddress, depositAmount);
219
+ }
220
+
221
+ /**
222
+ * @notice Reduce the amount staked for the sender (difference between initial amount staked and target is creditted back to the sender).
223
+ * @param target Target amount of stake for the staker. If this is below the current minimum, it will be set to minimum instead
224
+ */
225
+ function reduceDeposit(uint256 target) external onlyValidator whenNotPaused {
226
+ requireUnchallengedStaker(msg.sender);
227
+ uint256 currentRequired = currentRequiredStake();
228
+ if (target < currentRequired) {
229
+ target = currentRequired;
230
+ }
231
+ reduceStakeTo(msg.sender, target);
232
+ }
233
+
234
+ /**
235
+ * @notice Start a challenge between the given stakers over the node created by the first staker assuming that the two are staked on conflicting nodes. N.B.: challenge creator does not necessarily need to be one of the two asserters.
236
+ * @param stakers Stakers engaged in the challenge. The first staker should be staked on the first node
237
+ * @param nodeNums Nodes of the stakers engaged in the challenge. The first node should be the earliest and is the one challenged
238
+ * @param machineStatuses The before and after machine status for the first assertion
239
+ * @param globalStates The before and after global state for the first assertion
240
+ * @param numBlocks The number of L2 blocks contained in the first assertion
241
+ * @param secondExecutionHash The execution hash of the second assertion
242
+ * @param proposedTimes Times that the two nodes were proposed
243
+ * @param wasmModuleRoots The wasm module roots at the time of the creation of each assertion
244
+ */
245
+ function createChallenge(
246
+ address[2] calldata stakers,
247
+ uint64[2] calldata nodeNums,
248
+ MachineStatus[2] calldata machineStatuses,
249
+ GlobalState[2] calldata globalStates,
250
+ uint64 numBlocks,
251
+ bytes32 secondExecutionHash,
252
+ uint256[2] calldata proposedTimes,
253
+ bytes32[2] calldata wasmModuleRoots
254
+ ) external onlyValidator whenNotPaused {
255
+ require(nodeNums[0] < nodeNums[1], "WRONG_ORDER");
256
+ require(nodeNums[1] <= latestNodeCreated(), "NOT_PROPOSED");
257
+ require(latestConfirmed() < nodeNums[0], "ALREADY_CONFIRMED");
258
+
259
+ Node storage node1 = getNodeStorage(nodeNums[0]);
260
+ Node storage node2 = getNodeStorage(nodeNums[1]);
261
+
262
+ // ensure nodes staked on the same parent (and thus in conflict)
263
+ require(node1.prevNum == node2.prevNum, "DIFF_PREV");
264
+
265
+ // ensure both stakers aren't currently in challenge
266
+ requireUnchallengedStaker(stakers[0]);
267
+ requireUnchallengedStaker(stakers[1]);
268
+
269
+ require(nodeHasStaker(nodeNums[0], stakers[0]), "STAKER1_NOT_STAKED");
270
+ require(nodeHasStaker(nodeNums[1], stakers[1]), "STAKER2_NOT_STAKED");
271
+
272
+ // Check param data against challenge hash
273
+ require(
274
+ node1.challengeHash ==
275
+ RollupLib.challengeRootHash(
276
+ RollupLib.executionHash(machineStatuses, globalStates, numBlocks),
277
+ proposedTimes[0],
278
+ wasmModuleRoots[0]
279
+ ),
280
+ "CHAL_HASH1"
281
+ );
282
+
283
+ require(
284
+ node2.challengeHash ==
285
+ RollupLib.challengeRootHash(
286
+ secondExecutionHash,
287
+ proposedTimes[1],
288
+ wasmModuleRoots[1]
289
+ ),
290
+ "CHAL_HASH2"
291
+ );
292
+
293
+ // Calculate upper limit for allowed node proposal time:
294
+ uint256 commonEndTime = getNodeStorage(node1.prevNum).firstChildBlock +
295
+ // Dispute start: dispute timer for a node starts when its first child is created
296
+ (node1.deadlineBlock - proposedTimes[0]) +
297
+ extraChallengeTimeBlocks; // add dispute window to dispute start time
298
+ if (commonEndTime < proposedTimes[1]) {
299
+ // The 2nd node was created too late; loses challenge automatically.
300
+ completeChallengeImpl(stakers[0], stakers[1]);
301
+ return;
302
+ }
303
+ // Start a challenge between staker1 and staker2. Staker1 will defend the correctness of node1, and staker2 will challenge it.
304
+ uint64 challengeIndex = createChallengeHelper(
305
+ stakers,
306
+ machineStatuses,
307
+ globalStates,
308
+ numBlocks,
309
+ wasmModuleRoots,
310
+ commonEndTime - proposedTimes[0],
311
+ commonEndTime - proposedTimes[1]
312
+ ); // trusted external call
313
+
314
+ challengeStarted(stakers[0], stakers[1], challengeIndex);
315
+
316
+ emit RollupChallengeStarted(challengeIndex, stakers[0], stakers[1], nodeNums[0]);
317
+ }
318
+
319
+ function createChallengeHelper(
320
+ address[2] calldata stakers,
321
+ MachineStatus[2] calldata machineStatuses,
322
+ GlobalState[2] calldata globalStates,
323
+ uint64 numBlocks,
324
+ bytes32[2] calldata wasmModuleRoots,
325
+ uint256 asserterTimeLeft,
326
+ uint256 challengerTimeLeft
327
+ ) internal returns (uint64) {
328
+ return
329
+ challengeManager.createChallenge(
330
+ wasmModuleRoots[0],
331
+ machineStatuses,
332
+ globalStates,
333
+ numBlocks,
334
+ stakers[0],
335
+ stakers[1],
336
+ asserterTimeLeft,
337
+ challengerTimeLeft
338
+ );
339
+ }
340
+
341
+ /**
342
+ * @notice Inform the rollup that the challenge between the given stakers is completed
343
+ * @param winningStaker Address of the winning staker
344
+ * @param losingStaker Address of the losing staker
345
+ */
346
+ function completeChallenge(
347
+ uint256 challengeIndex,
348
+ address winningStaker,
349
+ address losingStaker
350
+ ) external override whenNotPaused {
351
+ // Only the challenge manager contract can call this to declare the winner and loser
352
+ require(msg.sender == address(challengeManager), "WRONG_SENDER");
353
+ require(challengeIndex == inChallenge(winningStaker, losingStaker), "NOT_IN_CHAL");
354
+ completeChallengeImpl(winningStaker, losingStaker);
355
+ }
356
+
357
+ function completeChallengeImpl(address winningStaker, address losingStaker) private {
358
+ uint256 remainingLoserStake = amountStaked(losingStaker);
359
+ uint256 winnerStake = amountStaked(winningStaker);
360
+ if (remainingLoserStake > winnerStake) {
361
+ // If loser has a higher stake than the winner, refund the difference
362
+ remainingLoserStake -= reduceStakeTo(losingStaker, winnerStake);
363
+ }
364
+
365
+ // Reward the winner with half the remaining stake
366
+ uint256 amountWon = remainingLoserStake / 2;
367
+ increaseStakeBy(winningStaker, amountWon);
368
+ remainingLoserStake -= amountWon;
369
+ clearChallenge(winningStaker);
370
+ // Credit the other half to the loserStakeEscrow address
371
+ increaseWithdrawableFunds(loserStakeEscrow, remainingLoserStake);
372
+ // Turning loser into zombie renders the loser's remaining stake inaccessible
373
+ turnIntoZombie(losingStaker);
374
+ }
375
+
376
+ /**
377
+ * @notice Remove the given zombie from nodes it is staked on, moving backwords from the latest node it is staked on
378
+ * @param zombieNum Index of the zombie to remove
379
+ * @param maxNodes Maximum number of nodes to remove the zombie from (to limit the cost of this transaction)
380
+ */
381
+ function removeZombie(uint256 zombieNum, uint256 maxNodes)
382
+ external
383
+ onlyValidator
384
+ whenNotPaused
385
+ {
386
+ require(zombieNum < zombieCount(), "NO_SUCH_ZOMBIE");
387
+ address zombieStakerAddress = zombieAddress(zombieNum);
388
+ uint64 latestNodeStaked = zombieLatestStakedNode(zombieNum);
389
+ uint256 nodesRemoved = 0;
390
+ uint256 latestConfirmedNum = latestConfirmed();
391
+ while (latestNodeStaked >= latestConfirmedNum && nodesRemoved < maxNodes) {
392
+ Node storage node = getNodeStorage(latestNodeStaked);
393
+ removeStaker(latestNodeStaked, zombieStakerAddress);
394
+ latestNodeStaked = node.prevNum;
395
+ nodesRemoved++;
396
+ }
397
+ if (latestNodeStaked < latestConfirmedNum) {
398
+ removeZombie(zombieNum);
399
+ } else {
400
+ zombieUpdateLatestStakedNode(zombieNum, latestNodeStaked);
401
+ }
402
+ }
403
+
404
+ /**
405
+ * @notice Remove any zombies whose latest stake is earlier than the latest confirmed node
406
+ * @param startIndex Index in the zombie list to start removing zombies from (to limit the cost of this transaction)
407
+ */
408
+ function removeOldZombies(uint256 startIndex) public onlyValidator whenNotPaused {
409
+ uint256 currentZombieCount = zombieCount();
410
+ uint256 latestConfirmedNum = latestConfirmed();
411
+ for (uint256 i = startIndex; i < currentZombieCount; i++) {
412
+ while (zombieLatestStakedNode(i) < latestConfirmedNum) {
413
+ removeZombie(i);
414
+ currentZombieCount--;
415
+ if (i >= currentZombieCount) {
416
+ return;
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ /**
423
+ * @notice Calculate the current amount of funds required to place a new stake in the rollup
424
+ * @dev If the stake requirement get's too high, this function may start reverting due to overflow, but
425
+ * that only blocks operations that should be blocked anyway
426
+ * @return The current minimum stake requirement
427
+ */
428
+ function currentRequiredStake(
429
+ uint256 _blockNumber,
430
+ uint64 _firstUnresolvedNodeNum,
431
+ uint256 _latestCreatedNode
432
+ ) internal view returns (uint256) {
433
+ // If there are no unresolved nodes, then you can use the base stake
434
+ if (_firstUnresolvedNodeNum - 1 == _latestCreatedNode) {
435
+ return baseStake;
436
+ }
437
+ uint256 firstUnresolvedDeadline = getNodeStorage(_firstUnresolvedNodeNum).deadlineBlock;
438
+ if (_blockNumber < firstUnresolvedDeadline) {
439
+ return baseStake;
440
+ }
441
+ uint24[10] memory numerators = [
442
+ 1,
443
+ 122971,
444
+ 128977,
445
+ 80017,
446
+ 207329,
447
+ 114243,
448
+ 314252,
449
+ 129988,
450
+ 224562,
451
+ 162163
452
+ ];
453
+ uint24[10] memory denominators = [
454
+ 1,
455
+ 114736,
456
+ 112281,
457
+ 64994,
458
+ 157126,
459
+ 80782,
460
+ 207329,
461
+ 80017,
462
+ 128977,
463
+ 86901
464
+ ];
465
+ uint256 firstUnresolvedAge = _blockNumber - firstUnresolvedDeadline;
466
+ uint256 periodsPassed = (firstUnresolvedAge * 10) / confirmPeriodBlocks;
467
+ uint256 baseMultiplier = 2**(periodsPassed / 10);
468
+ uint256 withNumerator = baseMultiplier * numerators[periodsPassed % 10];
469
+ uint256 multiplier = withNumerator / denominators[periodsPassed % 10];
470
+ if (multiplier == 0) {
471
+ multiplier = 1;
472
+ }
473
+ return baseStake * multiplier;
474
+ }
475
+
476
+ /**
477
+ * @notice Calculate the current amount of funds required to place a new stake in the rollup
478
+ * @dev If the stake requirement get's too high, this function may start reverting due to overflow, but
479
+ * that only blocks operations that should be blocked anyway
480
+ * @return The current minimum stake requirement
481
+ */
482
+ function requiredStake(
483
+ uint256 blockNumber,
484
+ uint64 firstUnresolvedNodeNum,
485
+ uint64 latestCreatedNode
486
+ ) external view returns (uint256) {
487
+ return currentRequiredStake(blockNumber, firstUnresolvedNodeNum, latestCreatedNode);
488
+ }
489
+
490
+ function currentRequiredStake() public view returns (uint256) {
491
+ uint64 firstUnresolvedNodeNum = firstUnresolvedNode();
492
+
493
+ return currentRequiredStake(block.number, firstUnresolvedNodeNum, latestNodeCreated());
494
+ }
495
+
496
+ /**
497
+ * @notice Calculate the number of zombies staked on the given node
498
+ *
499
+ * @dev This function could be uncallable if there are too many zombies. However,
500
+ * removeZombie and removeOldZombies can be used to remove any zombies that exist
501
+ * so that this will then be callable
502
+ *
503
+ * @param nodeNum The node on which to count staked zombies
504
+ * @return The number of zombies staked on the node
505
+ */
506
+ function countStakedZombies(uint64 nodeNum) public view override returns (uint256) {
507
+ uint256 currentZombieCount = zombieCount();
508
+ uint256 stakedZombieCount = 0;
509
+ for (uint256 i = 0; i < currentZombieCount; i++) {
510
+ if (nodeHasStaker(nodeNum, zombieAddress(i))) {
511
+ stakedZombieCount++;
512
+ }
513
+ }
514
+ return stakedZombieCount;
515
+ }
516
+
517
+ /**
518
+ * @notice Calculate the number of zombies staked on a child of the given node
519
+ *
520
+ * @dev This function could be uncallable if there are too many zombies. However,
521
+ * removeZombie and removeOldZombies can be used to remove any zombies that exist
522
+ * so that this will then be callable
523
+ *
524
+ * @param nodeNum The parent node on which to count zombies staked on children
525
+ * @return The number of zombies staked on children of the node
526
+ */
527
+ function countZombiesStakedOnChildren(uint64 nodeNum) public view override returns (uint256) {
528
+ uint256 currentZombieCount = zombieCount();
529
+ uint256 stakedZombieCount = 0;
530
+ for (uint256 i = 0; i < currentZombieCount; i++) {
531
+ Zombie storage zombie = getZombieStorage(i);
532
+ // If this zombie is staked on this node, but its _latest_ staked node isn't this node,
533
+ // then it must be staked on a child of this node.
534
+ if (
535
+ zombie.latestStakedNode != nodeNum && nodeHasStaker(nodeNum, zombie.stakerAddress)
536
+ ) {
537
+ stakedZombieCount++;
538
+ }
539
+ }
540
+ return stakedZombieCount;
541
+ }
542
+
543
+ /**
544
+ * @notice Verify that there are some number of nodes still unresolved
545
+ */
546
+ function requireUnresolvedExists() public view override {
547
+ uint256 firstUnresolved = firstUnresolvedNode();
548
+ require(
549
+ firstUnresolved > latestConfirmed() && firstUnresolved <= latestNodeCreated(),
550
+ "NO_UNRESOLVED"
551
+ );
552
+ }
553
+
554
+ function requireUnresolved(uint256 nodeNum) public view override {
555
+ require(nodeNum >= firstUnresolvedNode(), "ALREADY_DECIDED");
556
+ require(nodeNum <= latestNodeCreated(), "DOESNT_EXIST");
557
+ }
558
+
559
+ /**
560
+ * @notice Verify that the given address is staked and not actively in a challenge
561
+ * @param stakerAddress Address to check
562
+ */
563
+ function requireUnchallengedStaker(address stakerAddress) private view {
564
+ require(isStaked(stakerAddress), "NOT_STAKED");
565
+ require(currentChallenge(stakerAddress) == NO_CHAL_INDEX, "IN_CHAL");
566
+ }
567
+
568
+ function withdrawStakerFunds(address payable destination) external virtual returns (uint256);
569
+ }
570
+
571
+ contract RollupUserLogic is AbsRollupUserLogic, IRollupUser {
572
+ /// @dev the user logic just validated configuration and shouldn't write to state during init
573
+ /// this allows the admin logic to ensure consistency on parameters.
574
+ function initialize(address _stakeToken) external view override onlyProxy {
575
+ require(_stakeToken == address(0), "NO_TOKEN_ALLOWED");
576
+ require(!isERC20Enabled(), "FACET_NOT_ERC20");
577
+ }
578
+
579
+ /**
580
+ * @notice Create a new stake on an existing node
581
+ * @param nodeNum Number of the node your stake will be place one
582
+ * @param nodeHash Node hash of the node with the given nodeNum
583
+ */
584
+ function newStakeOnExistingNode(uint64 nodeNum, bytes32 nodeHash) external payable override {
585
+ _newStake(msg.value);
586
+ stakeOnExistingNode(nodeNum, nodeHash);
587
+ }
588
+
589
+ /**
590
+ * @notice Create a new stake on a new node
591
+ * @param assertion Assertion describing the state change between the old node and the new one
592
+ * @param expectedNodeHash Node hash of the node that will be created
593
+ * @param prevNodeInboxMaxCount Total of messages in the inbox as of the previous node
594
+ */
595
+ function newStakeOnNewNode(
596
+ RollupLib.Assertion calldata assertion,
597
+ bytes32 expectedNodeHash,
598
+ uint256 prevNodeInboxMaxCount
599
+ ) external payable override {
600
+ _newStake(msg.value);
601
+ stakeOnNewNode(assertion, expectedNodeHash, prevNodeInboxMaxCount);
602
+ }
603
+
604
+ /**
605
+ * @notice Increase the amount staked eth for the given staker
606
+ * @param stakerAddress Address of the staker whose stake is increased
607
+ */
608
+ function addToDeposit(address stakerAddress) external payable onlyValidator whenNotPaused {
609
+ _addToDeposit(stakerAddress, msg.value);
610
+ }
611
+
612
+ /**
613
+ * @notice Withdraw uncommitted funds owned by sender from the rollup chain
614
+ * @param destination Address to transfer the withdrawn funds to
615
+ */
616
+ function withdrawStakerFunds(address payable destination)
617
+ external
618
+ override
619
+ onlyValidator
620
+ whenNotPaused
621
+ returns (uint256)
622
+ {
623
+ uint256 amount = withdrawFunds(msg.sender);
624
+ // This is safe because it occurs after all checks and effects
625
+ destination.transfer(amount);
626
+ return amount;
627
+ }
628
+ }
629
+
630
+ contract ERC20RollupUserLogic is AbsRollupUserLogic, IRollupUserERC20 {
631
+ /// @dev the user logic just validated configuration and shouldn't write to state during init
632
+ /// this allows the admin logic to ensure consistency on parameters.
633
+ function initialize(address _stakeToken) external view override onlyProxy {
634
+ require(_stakeToken != address(0), "NEED_STAKE_TOKEN");
635
+ require(isERC20Enabled(), "FACET_NOT_ERC20");
636
+ }
637
+
638
+ /**
639
+ * @notice Create a new stake on an existing node
640
+ * @param tokenAmount Amount of the rollups staking token to stake
641
+ * @param nodeNum Number of the node your stake will be place one
642
+ * @param nodeHash Node hash of the node with the given nodeNum
643
+ */
644
+ function newStakeOnExistingNode(
645
+ uint256 tokenAmount,
646
+ uint64 nodeNum,
647
+ bytes32 nodeHash
648
+ ) external override {
649
+ _newStake(tokenAmount);
650
+ stakeOnExistingNode(nodeNum, nodeHash);
651
+ /// @dev This is an external call, safe because it's at the end of the function
652
+ receiveTokens(tokenAmount);
653
+ }
654
+
655
+ /**
656
+ * @notice Create a new stake on a new node
657
+ * @param tokenAmount Amount of the rollups staking token to stake
658
+ * @param assertion Assertion describing the state change between the old node and the new one
659
+ * @param expectedNodeHash Node hash of the node that will be created
660
+ * @param prevNodeInboxMaxCount Total of messages in the inbox as of the previous node
661
+ */
662
+ function newStakeOnNewNode(
663
+ uint256 tokenAmount,
664
+ RollupLib.Assertion calldata assertion,
665
+ bytes32 expectedNodeHash,
666
+ uint256 prevNodeInboxMaxCount
667
+ ) external override {
668
+ _newStake(tokenAmount);
669
+ stakeOnNewNode(assertion, expectedNodeHash, prevNodeInboxMaxCount);
670
+ /// @dev This is an external call, safe because it's at the end of the function
671
+ receiveTokens(tokenAmount);
672
+ }
673
+
674
+ /**
675
+ * @notice Increase the amount staked tokens for the given staker
676
+ * @param stakerAddress Address of the staker whose stake is increased
677
+ * @param tokenAmount the amount of tokens staked
678
+ */
679
+ function addToDeposit(address stakerAddress, uint256 tokenAmount)
680
+ external
681
+ onlyValidator
682
+ whenNotPaused
683
+ {
684
+ _addToDeposit(stakerAddress, tokenAmount);
685
+ /// @dev This is an external call, safe because it's at the end of the function
686
+ receiveTokens(tokenAmount);
687
+ }
688
+
689
+ /**
690
+ * @notice Withdraw uncommitted funds owned by sender from the rollup chain
691
+ * @param destination Address to transfer the withdrawn funds to
692
+ */
693
+ function withdrawStakerFunds(address payable destination)
694
+ external
695
+ override
696
+ onlyValidator
697
+ whenNotPaused
698
+ returns (uint256)
699
+ {
700
+ uint256 amount = withdrawFunds(msg.sender);
701
+ // This is safe because it occurs after all checks and effects
702
+ require(IERC20Upgradeable(stakeToken).transfer(destination, amount), "TRANSFER_FAILED");
703
+ return amount;
704
+ }
705
+
706
+ function receiveTokens(uint256 tokenAmount) private {
707
+ require(
708
+ IERC20Upgradeable(stakeToken).transferFrom(msg.sender, address(this), tokenAmount),
709
+ "TRANSFER_FAIL"
710
+ );
711
+ }
712
+ }