@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.
- package/.prettierrc +5 -0
- package/.solhint.json +18 -0
- package/deploy/BridgeStubCreator.js +10 -0
- package/deploy/HashProofHelper.js +13 -0
- package/deploy/InboxStubCreator.js +17 -0
- package/deploy/OneStepProofEntryCreator.js +19 -0
- package/deploy/OneStepProver0Creator.js +14 -0
- package/deploy/OneStepProverHostIoCreator.js +14 -0
- package/deploy/OneStepProverMathCreator.js +14 -0
- package/deploy/OneStepProverMemoryCreator.js +14 -0
- package/deploy/SequencerInboxStubCreator.js +13 -0
- package/deploy/ValueArrayTesterCreator.js +13 -0
- package/hardhat.config.ts +47 -0
- package/hardhat.prod-config.js +18 -0
- package/package.json +49 -0
- package/scripts/build.bash +5 -0
- package/src/bridge/Bridge.sol +168 -0
- package/src/bridge/IBridge.sol +68 -0
- package/src/bridge/IInbox.sol +80 -0
- package/src/bridge/IMessageProvider.sol +11 -0
- package/src/bridge/IOutbox.sol +52 -0
- package/src/bridge/ISequencerInbox.sol +85 -0
- package/src/bridge/Inbox.sol +414 -0
- package/src/bridge/Messages.sol +38 -0
- package/src/bridge/Outbox.sol +188 -0
- package/src/bridge/SequencerInbox.sol +274 -0
- package/src/challenge/ChallengeLib.sol +135 -0
- package/src/challenge/ChallengeManager.sol +367 -0
- package/src/challenge/IChallengeManager.sol +75 -0
- package/src/challenge/IChallengeResultReceiver.sol +13 -0
- package/src/libraries/AddressAliasHelper.sol +29 -0
- package/src/libraries/AdminFallbackProxy.sol +153 -0
- package/src/libraries/ArbitrumProxy.sol +20 -0
- package/src/libraries/Constants.sol +10 -0
- package/src/libraries/CryptographyPrimitives.sol +323 -0
- package/src/libraries/DelegateCallAware.sol +44 -0
- package/src/libraries/Error.sol +38 -0
- package/src/libraries/IGasRefunder.sol +35 -0
- package/src/libraries/MerkleLib.sol +46 -0
- package/src/libraries/MessageTypes.sol +14 -0
- package/src/libraries/SecondaryLogicUUPSUpgradeable.sol +58 -0
- package/src/libraries/UUPSNotUpgradeable.sol +56 -0
- package/src/mocks/BridgeStub.sol +115 -0
- package/src/mocks/Counter.sol +13 -0
- package/src/mocks/ExecutionManager.sol +41 -0
- package/src/mocks/InboxStub.sol +131 -0
- package/src/mocks/MockResultReceiver.sol +59 -0
- package/src/mocks/SequencerInboxStub.sol +42 -0
- package/src/mocks/SimpleProxy.sol +19 -0
- package/src/node-interface/NodeInterface.sol +50 -0
- package/src/osp/HashProofHelper.sol +154 -0
- package/src/osp/IOneStepProofEntry.sol +20 -0
- package/src/osp/IOneStepProver.sol +27 -0
- package/src/osp/OneStepProofEntry.sol +129 -0
- package/src/osp/OneStepProver0.sol +566 -0
- package/src/osp/OneStepProverHostIo.sol +357 -0
- package/src/osp/OneStepProverMath.sol +514 -0
- package/src/osp/OneStepProverMemory.sol +313 -0
- package/src/precompiles/ArbAddressTable.sol +60 -0
- package/src/precompiles/ArbAggregator.sol +62 -0
- package/src/precompiles/ArbBLS.sol +53 -0
- package/src/precompiles/ArbDebug.sol +39 -0
- package/src/precompiles/ArbFunctionTable.sol +29 -0
- package/src/precompiles/ArbGasInfo.sol +121 -0
- package/src/precompiles/ArbInfo.sol +15 -0
- package/src/precompiles/ArbOwner.sol +65 -0
- package/src/precompiles/ArbOwnerPublic.sol +18 -0
- package/src/precompiles/ArbRetryableTx.sol +89 -0
- package/src/precompiles/ArbStatistics.sol +29 -0
- package/src/precompiles/ArbSys.sol +134 -0
- package/src/precompiles/ArbosActs.sol +41 -0
- package/src/precompiles/ArbosTest.sol +14 -0
- package/src/rollup/BridgeCreator.sol +120 -0
- package/src/rollup/IRollupCore.sol +152 -0
- package/src/rollup/IRollupLogic.sol +183 -0
- package/src/rollup/Node.sol +99 -0
- package/src/rollup/RollupAdminLogic.sol +322 -0
- package/src/rollup/RollupCore.sol +627 -0
- package/src/rollup/RollupCreator.sol +133 -0
- package/src/rollup/RollupEventBridge.sol +46 -0
- package/src/rollup/RollupLib.sol +135 -0
- package/src/rollup/RollupUserLogic.sol +712 -0
- package/src/rollup/ValidatorUtils.sol +243 -0
- package/src/rollup/ValidatorWallet.sol +76 -0
- package/src/rollup/ValidatorWalletCreator.sol +43 -0
- package/src/state/Deserialize.sol +321 -0
- package/src/state/GlobalState.sol +44 -0
- package/src/state/Instructions.sol +159 -0
- package/src/state/Machine.sol +65 -0
- package/src/state/MerkleProof.sol +99 -0
- package/src/state/Module.sol +33 -0
- package/src/state/ModuleMemory.sol +42 -0
- package/src/state/PcArray.sol +45 -0
- package/src/state/PcStack.sol +32 -0
- package/src/state/StackFrame.sol +63 -0
- package/src/state/Value.sol +65 -0
- package/src/state/ValueArray.sol +47 -0
- package/src/state/ValueStack.sol +39 -0
- package/src/test-helpers/CryptographyPrimitivesTester.sol +27 -0
- package/src/test-helpers/MessageTester.sol +34 -0
- package/src/test-helpers/ValueArrayTester.sol +34 -0
- package/test/contract/arbRollup.spec.ts +869 -0
- package/test/contract/common/challengeLib.ts +43 -0
- package/test/contract/common/globalStateLib.ts +17 -0
- package/test/contract/common/rolluplib.ts +259 -0
- package/test/contract/cryptographyPrimitives.spec.ts +82 -0
- package/test/contract/sequencerInboxForceInclude.spec.ts +516 -0
- package/test/contract/utils.ts +40 -0
- package/test/prover/hash-proofs.ts +75 -0
- package/test/prover/one-step-proof.ts +93 -0
- package/test/prover/proofs/.gitkeep +0 -0
- package/test/prover/value-arrays.ts +11 -0
- 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
|
+
}
|