@arbitrum/nitro-contracts 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }