From f257dc085ea81bfbf9e1583f45b08896ff42f391 Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Wed, 17 May 2023 14:22:45 -0600 Subject: [PATCH 001/126] add support for user programs (copied from other repo) --- src/challenge/ChallengeLib.sol | 2 + src/mocks/InboxStub.sol | 4 +- src/mocks/Program.sol | 103 ++++++++++++++++++++++++ src/mocks/Simple.sol | 10 +-- src/osp/OneStepProofEntry.sol | 26 +++++- src/osp/OneStepProver0.sol | 74 ++++++++++++----- src/osp/OneStepProverHostIo.sol | 137 +++++++++++++++++++++++++++++++- src/osp/OneStepProverMemory.sol | 37 +-------- src/precompiles/ArbDebug.sol | 7 +- src/precompiles/ArbOwner.sol | 75 +++++++++-------- src/precompiles/ArbWasm.sol | 40 ++++++++++ src/state/Deserialize.sol | 46 +++++++++++ src/state/GuardStack.sol | 82 +++++++++++++++++++ src/state/Instructions.sol | 10 ++- src/state/Machine.sol | 54 +++++++++---- src/state/MerkleProof.sol | 20 ++++- src/state/Module.sol | 4 +- src/state/ModuleMemory.sol | 54 +++++++++++++ src/state/StackFrame.sol | 9 ++- src/state/Value.sol | 16 +++- src/state/ValueStack.sol | 9 ++- 21 files changed, 686 insertions(+), 133 deletions(-) create mode 100644 src/mocks/Program.sol create mode 100644 src/precompiles/ArbWasm.sol create mode 100644 src/state/GuardStack.sol diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index e225ea1f..07feffa6 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -62,12 +62,14 @@ library ChallengeLib { ValueStack memory values = ValueStack({proved: valuesArray, remainingHash: 0}); ValueStack memory internalStack; StackFrameWindow memory frameStack; + GuardStack memory guardStack; Machine memory mach = Machine({ status: MachineStatus.RUNNING, valueStack: values, internalStack: internalStack, frameStack: frameStack, + guardStack: guardStack, globalStateHash: globalStateHash, moduleIdx: 0, functionIdx: 0, diff --git a/src/mocks/InboxStub.sol b/src/mocks/InboxStub.sol index 3b183883..a6ef92b6 100644 --- a/src/mocks/InboxStub.sol +++ b/src/mocks/InboxStub.sol @@ -151,7 +151,7 @@ contract InboxStub is IInbox { address, uint256, bytes calldata - ) external pure returns (uint256) { + ) external returns (uint256) { revert("NOT_IMPLEMENTED"); } @@ -161,7 +161,7 @@ contract InboxStub is IInbox { uint256, uint256, address - ) external pure returns (uint256) { + ) external returns (uint256) { revert("NOT_IMPLEMENTED"); } diff --git a/src/mocks/Program.sol b/src/mocks/Program.sol new file mode 100644 index 00000000..ba75656b --- /dev/null +++ b/src/mocks/Program.sol @@ -0,0 +1,103 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.0; +import "../precompiles/ArbSys.sol"; + +contract ProgramTest { + event Hash(bytes32 result); + + function callKeccak(address program, bytes calldata data) external { + // in keccak.rs + // the input is the # of hashings followed by a preimage + // the output is the iterated hash of the preimage + (bool success, bytes memory result) = address(program).call(data); + require(success, "call failed"); + bytes32 hash = bytes32(result); + emit Hash(hash); + require(hash == keccak256(data[1:])); + } + + function staticcallProgram(address program, bytes calldata data) + external + view + returns (bytes memory) + { + (bool success, bytes memory result) = address(program).staticcall(data); + require(success, "call failed"); + return result; + } + + function assert256( + bytes memory data, + string memory text, + uint256 expected + ) internal pure returns (bytes memory) { + uint256 value = abi.decode(data, (uint256)); + require(value == expected, text); + + bytes memory rest = new bytes(data.length - 32); + for (uint256 i = 32; i < data.length; i++) { + rest[i - 32] = data[i]; + } + return rest; + } + + function staticcallEvmData( + address program, + address fundedAccount, + uint64 gas, + bytes calldata data + ) external view returns (bytes memory) { + (bool success, bytes memory result) = address(program).staticcall{gas: gas}(data); + + address arbPrecompile = address(0x69); + address ethPrecompile = address(0x01); + + result = assert256(result, "block number ", block.number - 1); + result = assert256(result, "block hash ", uint256(blockhash(block.number - 1))); + result = assert256(result, "chain id ", block.chainid); + result = assert256(result, "base fee ", block.basefee); + result = assert256(result, "gas price ", tx.gasprice); + result = assert256(result, "gas limit ", block.gaslimit); + result = assert256(result, "value ", 0); + result = assert256(result, "difficulty ", block.difficulty); + result = assert256(result, "timestamp ", block.timestamp); + result = assert256(result, "balance ", fundedAccount.balance); + result = assert256(result, "rust address ", uint256(uint160(program))); + result = assert256(result, "sender ", uint256(uint160(address(this)))); + result = assert256(result, "origin ", uint256(uint160(tx.origin))); + result = assert256(result, "coinbase ", uint256(uint160(address(block.coinbase)))); + result = assert256(result, "rust codehash", uint256(program.codehash)); + result = assert256(result, "arb codehash ", uint256(arbPrecompile.codehash)); + result = assert256(result, "eth codehash ", uint256(ethPrecompile.codehash)); + + return result; + } + + function checkRevertData( + address program, + bytes calldata data, + bytes calldata expected + ) external payable returns (bytes memory) { + (bool success, bytes memory result) = address(program).call{value: msg.value}(data); + require(!success, "unexpected success"); + require(result.length == expected.length, "wrong revert data length"); + for (uint256 i = 0; i < result.length; i++) { + require(result[i] == expected[i], "revert data mismatch"); + } + return result; + } + + function fillBlock() external payable { + bytes32 bridgeToNova = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; + address cryptoIsCute = 0x361594F5429D23ECE0A88E4fBE529E1c49D524d8; + uint8 v = 27; + bytes32 r = 0xc6178c2de1078cd36c3bd302cde755340d7f17fcb3fcc0b9c333ba03b217029f; + bytes32 s = 0x5fdbcefe2675e96219cdae57a7894280bf80fd40d44ce146a35e169ea6a78fd3; + while (true) { + require(ecrecover(bridgeToNova, v, r, s) == cryptoIsCute, "WRONG_ARBINAUT"); + } + } +} diff --git a/src/mocks/Simple.sol b/src/mocks/Simple.sol index 1563fdb4..9df036a3 100644 --- a/src/mocks/Simple.sol +++ b/src/mocks/Simple.sol @@ -13,17 +13,11 @@ contract Simple { event CounterEvent(uint64 count); event RedeemedEvent(address caller, address redeemer); event NullEvent(); - event LogAndIncrementCalled(uint256 expected, uint256 have); function increment() external { counter++; } - function logAndIncrement(uint256 expected) external { - emit LogAndIncrementCalled(expected, counter); - counter++; - } - function incrementEmit() external { counter++; emit CounterEvent(counter); @@ -113,9 +107,7 @@ contract Simple { function checkGasUsed(address to, bytes calldata input) external view returns (uint256) { uint256 before = gasleft(); - // The inner call may revert, but we still want to return the amount of gas used, - // so we ignore the result of this call. - (to.staticcall{gas: before - 10000}(input)); + to.staticcall{gas: before - 10000}(input); return before - gasleft(); } } diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index bae84ca1..c09f0056 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 @@ -14,6 +14,10 @@ contract OneStepProofEntry is IOneStepProofEntry { using MerkleProofLib for MerkleProof; using MachineLib for Machine; + using ValueStackLib for ValueStack; + using GuardStackLib for GuardStack; + using StackFrameLib for StackFrameWindow; + IOneStepProver public prover0; IOneStepProver public proverMem; IOneStepProver public proverMath; @@ -113,7 +117,7 @@ contract OneStepProofEntry is IOneStepProofEntry { } else if ( (opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 && opcode <= Instructions.SET_GLOBAL_STATE_U64) || - (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.HALT_AND_SET_FINISHED) + (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) ) { prover = proverHostIo; } else { @@ -122,7 +126,23 @@ contract OneStepProofEntry is IOneStepProofEntry { (mach, mod) = prover.executeOneStep(execCtx, mach, mod, inst, proof); - mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod); + bool updateRoot = !(opcode == Instructions.LINK_MODULE || + opcode == Instructions.UNLINK_MODULE); + if (updateRoot) { + mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod); + } + + if (mach.status == MachineStatus.ERRORED && !mach.guardStack.empty()) { + ErrorGuard memory guard = mach.guardStack.pop(); + mach.frameStack.overwrite(guard.frameStack); + mach.valueStack.overwrite(guard.valueStack); + mach.internalStack.overwrite(guard.interStack); + mach.setPc(guard.onErrorPc); + + // indicate an error and continue + mach.valueStack.push(ValueLib.newI32(0)); + mach.status = MachineStatus.RUNNING; + } return mach.hash(); } diff --git a/src/osp/OneStepProver0.sol b/src/osp/OneStepProver0.sol index 2767a8e9..a7aee38b 100644 --- a/src/osp/OneStepProver0.sol +++ b/src/osp/OneStepProver0.sol @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 @@ -11,6 +11,7 @@ import "../state/Deserialize.sol"; import "./IOneStepProver.sol"; contract OneStepProver0 is IOneStepProver { + using MachineLib for Machine; using MerkleProofLib for MerkleProof; using StackFrameLib for StackFrameWindow; using ValueLib for Value; @@ -90,28 +91,11 @@ contract OneStepProver0 is IOneStepProver { bytes calldata ) internal pure { StackFrame memory frame = mach.frameStack.pop(); - if (frame.returnPc.valueType == ValueType.REF_NULL) { - mach.status = MachineStatus.ERRORED; - return; - } else if (frame.returnPc.valueType != ValueType.INTERNAL_REF) { - revert("INVALID_RETURN_PC_TYPE"); - } - uint256 data = frame.returnPc.contents; - uint32 pc = uint32(data); - uint32 func = uint32(data >> 32); - uint32 mod = uint32(data >> 64); - require(data >> 96 == 0, "INVALID_RETURN_PC_DATA"); - mach.functionPc = pc; - mach.functionIdx = func; - mach.moduleIdx = mod; + mach.setPc(frame.returnPc); } function createReturnValue(Machine memory mach) internal pure returns (Value memory) { - uint256 returnData = 0; - returnData |= mach.functionPc; - returnData |= uint256(mach.functionIdx) << 32; - returnData |= uint256(mach.moduleIdx) << 64; - return Value({valueType: ValueType.INTERNAL_REF, contents: returnData}); + return ValueLib.newPc(mach.functionPc, mach.functionIdx, mach.moduleIdx); } function executeCall( @@ -157,6 +141,52 @@ contract OneStepProver0 is IOneStepProver { mach.functionPc = 0; } + function executeCrossModuleForward( + Machine memory mach, + Module memory mod, + Instruction calldata inst, + bytes calldata + ) internal pure { + // Push the return pc to the stack + mach.valueStack.push(createReturnValue(mach)); + + // Push caller's caller module info to the stack + StackFrame memory frame = mach.frameStack.peek(); + mach.valueStack.push(ValueLib.newI32(frame.callerModule)); + mach.valueStack.push(ValueLib.newI32(frame.callerModuleInternals)); + + // Jump to the target + uint32 func = uint32(inst.argumentData); + uint32 module = uint32(inst.argumentData >> 32); + require(inst.argumentData >> 64 == 0, "BAD_CROSS_MODULE_CALL_DATA"); + mach.moduleIdx = module; + mach.functionIdx = func; + mach.functionPc = 0; + } + + function executeCrossModuleDynamicCall( + Machine memory mach, + Module memory mod, + Instruction calldata inst, + bytes calldata + ) internal pure { + // Get the target from the stack + uint32 func = mach.valueStack.pop().assumeI32(); + uint32 module = mach.valueStack.pop().assumeI32(); + + // Push the return pc to the stack + mach.valueStack.push(createReturnValue(mach)); + + // Push caller module info to the stack + mach.valueStack.push(ValueLib.newI32(mach.moduleIdx)); + mach.valueStack.push(ValueLib.newI32(mod.internalsOffset)); + + // Jump to the target + mach.moduleIdx = module; + mach.functionIdx = func; + mach.functionPc = 0; + } + function executeCallerModuleInternalCall( Machine memory mach, Module memory mod, @@ -454,6 +484,10 @@ contract OneStepProver0 is IOneStepProver { impl = executeCall; } else if (opcode == Instructions.CROSS_MODULE_CALL) { impl = executeCrossModuleCall; + } else if (opcode == Instructions.CROSS_MODULE_FORWARD) { + impl = executeCrossModuleForward; + } else if (opcode == Instructions.CROSS_MODULE_DYNAMIC_CALL) { + impl = executeCrossModuleDynamicCall; } else if (opcode == Instructions.CALLER_MODULE_INTERNAL_CALL) { impl = executeCallerModuleInternalCall; } else if (opcode == Instructions.CALL_INDIRECT) { diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index bbedf000..f4c04c77 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 @@ -6,6 +6,7 @@ pragma solidity ^0.8.0; import "../state/Value.sol"; import "../state/Machine.sol"; +import "../state/MerkleProof.sol"; import "../state/Deserialize.sol"; import "./IOneStepProver.sol"; import "../bridge/Messages.sol"; @@ -17,6 +18,8 @@ contract OneStepProverHostIo is IOneStepProver { using ModuleMemoryLib for ModuleMemory; using ValueLib for Value; using ValueStackLib for ValueStack; + using StackFrameLib for StackFrameWindow; + using GuardStackLib for GuardStack; uint256 private constant LEAF_SIZE = 32; uint256 private constant INBOX_NUM = 2; @@ -51,7 +54,7 @@ contract OneStepProverHostIo is IOneStepProver { mach.status = MachineStatus.ERRORED; return; } - if (ptr + 32 > mod.moduleMemory.size || ptr % LEAF_SIZE != 0) { + if (!mod.moduleMemory.isValidLeaf(ptr)) { mach.status = MachineStatus.ERRORED; return; } @@ -286,6 +289,128 @@ contract OneStepProverHostIo is IOneStepProver { mach.status = MachineStatus.FINISHED; } + function isPowerOfTwo(uint256 value) internal pure returns (bool) { + return value != 0 && (value & (value - 1) == 0); + } + + function proveLastLeaf( + Machine memory mach, + uint256 offset, + bytes calldata proof + ) + internal + pure + returns ( + uint256 leaf, + MerkleProof memory leafProof, + MerkleProof memory zeroProof + ) + { + string memory prefix = "Module merkle tree:"; + bytes32 root = mach.modulesRoot; + + { + Module memory leafModule; + uint32 leaf32; + (leafModule, offset) = Deserialize.module(proof, offset); + (leaf32, offset) = Deserialize.u32(proof, offset); + (leafProof, offset) = Deserialize.merkleProof(proof, offset); + leaf = uint256(leaf32); + + bytes32 compRoot = leafProof.computeRootFromModule(leaf, leafModule); + require(compRoot == root, "WRONG_ROOT_FOR_LEAF"); + } + + // if tree is unbalanced, check that the next leaf is 0 + bool balanced = isPowerOfTwo(leaf + 1); + if (balanced) { + require(1 << leafProof.counterparts.length == leaf + 1, "WRONG_LEAF"); + } else { + (zeroProof, offset) = Deserialize.merkleProof(proof, offset); + bytes32 compRoot = zeroProof.computeRootUnsafe(leaf + 1, 0, prefix); + require(compRoot == root, "WRONG_ROOT_FOR_ZERO"); + } + + return (leaf, leafProof, zeroProof); + } + + function executeLinkModule( + ExecutionContext calldata, + Machine memory mach, + Module memory mod, + Instruction calldata, + bytes calldata proof + ) internal pure { + string memory prefix = "Module merkle tree:"; + bytes32 root = mach.modulesRoot; + + uint256 pointer = mach.valueStack.pop().assumeI32(); + if (!mod.moduleMemory.isValidLeaf(pointer)) { + mach.status = MachineStatus.ERRORED; + return; + } + (bytes32 userMod, uint256 offset, ) = mod.moduleMemory.proveLeaf( + pointer / LEAF_SIZE, + proof, + 0 + ); + + (uint256 leaf, , MerkleProof memory zeroProof) = proveLastLeaf(mach, offset, proof); + + bool balanced = isPowerOfTwo(leaf + 1); + if (balanced) { + mach.modulesRoot = MerkleProofLib.growToNewRoot(root, leaf + 1, userMod, 0, prefix); + } else { + mach.modulesRoot = zeroProof.computeRootUnsafe(leaf + 1, userMod, prefix); + } + + mach.valueStack.push(ValueLib.newI32(uint32(leaf + 1))); + } + + function executeUnlinkModule( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata, + bytes calldata proof + ) internal pure { + string memory prefix = "Module merkle tree:"; + + (uint256 leaf, MerkleProof memory leafProof, ) = proveLastLeaf(mach, 0, proof); + + bool shrink = isPowerOfTwo(leaf); + if (shrink) { + mach.modulesRoot = leafProof.counterparts[leafProof.counterparts.length - 1]; + } else { + mach.modulesRoot = leafProof.computeRootUnsafe(leaf, 0, prefix); + } + } + + function executePushErrorGuard( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata, + bytes calldata proof + ) internal view { + bytes32 frames = mach.frameStack.hash(); + bytes32 values = mach.valueStack.hash(); + bytes32 inters = mach.internalStack.hash(); + Value memory onError = ValueLib.newPc(mach.functionPc, mach.functionIdx, mach.moduleIdx); + mach.guardStack.push(GuardStackLib.newErrorGuard(frames, values, inters, onError)); + mach.valueStack.push(ValueLib.newI32(1)); + } + + function executePopErrorGuard( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata, + bytes calldata + ) internal pure { + mach.guardStack.pop(); + } + function executeGlobalStateAccess( ExecutionContext calldata, Machine memory mach, @@ -347,6 +472,14 @@ contract OneStepProverHostIo is IOneStepProver { impl = executeReadInboxMessage; } else if (opcode == Instructions.HALT_AND_SET_FINISHED) { impl = executeHaltAndSetFinished; + } else if (opcode == Instructions.LINK_MODULE) { + impl = executeLinkModule; + } else if (opcode == Instructions.UNLINK_MODULE) { + impl = executeUnlinkModule; + } else if (opcode == Instructions.PUSH_ERROR_GUARD) { + impl = executePushErrorGuard; + } else if (opcode == Instructions.POP_ERROR_GUARD) { + impl = executePopErrorGuard; } else { revert("INVALID_MEMORY_OPCODE"); } diff --git a/src/osp/OneStepProverMemory.sol b/src/osp/OneStepProverMemory.sol index 0135ef67..2031149b 100644 --- a/src/osp/OneStepProverMemory.sol +++ b/src/osp/OneStepProverMemory.sol @@ -18,13 +18,6 @@ contract OneStepProverMemory is IOneStepProver { uint256 private constant LEAF_SIZE = 32; uint64 private constant PAGE_SIZE = 65536; - function pullLeafByte(bytes32 leaf, uint256 idx) internal pure returns (uint8) { - require(idx < LEAF_SIZE, "BAD_PULL_LEAF_BYTE_IDX"); - // Take into account that we are casting the leaf to a big-endian integer - uint256 leafShift = (LEAF_SIZE - 1 - idx) * 8; - return uint8(uint256(leaf) >> leafShift); - } - function setLeafByte( bytes32 oldLeaf, uint256 idx, @@ -108,35 +101,13 @@ contract OneStepProverMemory is IOneStepProver { revert("INVALID_MEMORY_LOAD_OPCODE"); } - // Neither of these can overflow as they're computed with much less than 256 bit integers. - uint256 startIdx = inst.argumentData + mach.valueStack.pop().assumeI32(); - if (startIdx + readBytes > mod.moduleMemory.size) { + uint256 index = inst.argumentData + mach.valueStack.pop().assumeI32(); + (bool err, uint256 value, ) = mod.moduleMemory.load(index, readBytes, proof, 0); + if (err) { mach.status = MachineStatus.ERRORED; return; } - - uint256 proofOffset = 0; - uint256 lastProvedLeafIdx = ~uint256(0); - bytes32 lastProvedLeafContents; - uint64 readValue; - for (uint256 i = 0; i < readBytes; i++) { - uint256 idx = startIdx + i; - uint256 leafIdx = idx / LEAF_SIZE; - if (leafIdx != lastProvedLeafIdx) { - // This hits the stack size if we phrase it as mod.moduleMemory.proveLeaf(...) - (lastProvedLeafContents, proofOffset, ) = ModuleMemoryLib.proveLeaf( - mod.moduleMemory, - leafIdx, - proof, - proofOffset - ); - lastProvedLeafIdx = leafIdx; - } - uint256 indexWithinLeaf = idx % LEAF_SIZE; - readValue |= - uint64(pullLeafByte(lastProvedLeafContents, indexWithinLeaf)) << - uint64(i * 8); - } + uint64 readValue = uint64(value); if (signed) { // Go down to the original uint size, change to signed, go up to correct size, convert back to unsigned diff --git a/src/precompiles/ArbDebug.sol b/src/precompiles/ArbDebug.sol index 3cfb3cf4..32e43bc6 100644 --- a/src/precompiles/ArbDebug.sol +++ b/src/precompiles/ArbDebug.sol @@ -1,5 +1,5 @@ -// Copyright 2021-2023, Offchain Labs, Inc. -// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.4.21 <0.9.0; @@ -15,9 +15,6 @@ interface ArbDebug { /// @notice Emit events with values based on the args provided function events(bool flag, bytes32 value) external payable returns (address, uint256); - /// @notice Tries (and fails) to emit logs in a view context - function eventsView() external view; - // Events that exist for testing log creation and pricing event Basic(bool flag, bytes32 indexed value); event Mixed( diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 2bca3b94..e9ed6d76 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -1,89 +1,100 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.4.21 <0.9.0; -/// @title Provides owners with tools for managing the rollup. -/// @notice Calls by non-owners will always revert. -/// Most of Arbitrum Classic's owner methods have been removed since they no longer make sense in Nitro: -/// - What were once chain parameters are now parts of ArbOS's state, and those that remain are set at genesis. -/// - ArbOS upgrades happen with the rest of the system rather than being independent -/// - Exemptions to address aliasing are no longer offered. Exemptions were intended to support backward compatibility for contracts deployed before aliasing was introduced, but no exemptions were ever requested. -/// Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000070. +/** + * @title Provides owners with tools for managing the rollup. + * @notice Calls by non-owners will always revert. + * Most of Arbitrum Classic's owner methods have been removed since they no longer make sense in Nitro: + * - What were once chain parameters are now parts of ArbOS's state, and those that remain are set at genesis. + * - ArbOS upgrades happen with the rest of the system rather than being independent + * - Exemptions to address aliasing are no longer offered. Exemptions were intended to support backward compatibility for contracts deployed before aliasing was introduced, but no exemptions were ever requested. + * Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000070. + **/ interface ArbOwner { - /// @notice Add account as a chain owner + // @notice Add account as a chain owner function addChainOwner(address newOwner) external; - /// @notice Remove account from the list of chain owners + // @notice Remove account from the list of chain owners function removeChainOwner(address ownerToRemove) external; - /// @notice See if the user is a chain owner + // @notice See if the user is a chain owner function isChainOwner(address addr) external view returns (bool); - /// @notice Retrieves the list of chain owners + // @notice Retrieves the list of chain owners function getAllChainOwners() external view returns (address[] memory); - /// @notice Set how slowly ArbOS updates its estimate of the L1 basefee + // @notice Set how slowly ArbOS updates its estimate of the L1 basefee function setL1BaseFeeEstimateInertia(uint64 inertia) external; - /// @notice Set the L2 basefee directly, bypassing the pool calculus + // @notice Set the L2 basefee directly, bypassing the pool calculus function setL2BaseFee(uint256 priceInWei) external; - /// @notice Set the minimum basefee needed for a transaction to succeed + // @notice Set the minimum basefee needed for a transaction to succeed function setMinimumL2BaseFee(uint256 priceInWei) external; - /// @notice Set the computational speed limit for the chain + // @notice Set the computational speed limit for the chain function setSpeedLimit(uint64 limit) external; - /// @notice Set the maximum size a tx (and block) can be + // @notice Set the maximum size a tx (and block) can be function setMaxTxGasLimit(uint64 limit) external; - /// @notice Set the L2 gas pricing inertia + // @notice Set the L2 gas pricing inertia function setL2GasPricingInertia(uint64 sec) external; - /// @notice Set the L2 gas backlog tolerance + // @notice Set the L2 gas backlog tolerance function setL2GasBacklogTolerance(uint64 sec) external; - /// @notice Get the network fee collector + // @notice Get the network fee collector function getNetworkFeeAccount() external view returns (address); - /// @notice Get the infrastructure fee collector + // @notice Get the infrastructure fee collector function getInfraFeeAccount() external view returns (address); - /// @notice Set the network fee collector + // @notice Set the network fee collector function setNetworkFeeAccount(address newNetworkFeeAccount) external; - /// @notice Set the infrastructure fee collector + // @notice Set the infrastructure fee collector function setInfraFeeAccount(address newInfraFeeAccount) external; - /// @notice Upgrades ArbOS to the requested version at the requested timestamp + // @notice Upgrades ArbOS to the requested version at the requested timestamp function scheduleArbOSUpgrade(uint64 newVersion, uint64 timestamp) external; - /// @notice Sets equilibration units parameter for L1 price adjustment algorithm + // @notice Sets equilibration units parameter for L1 price adjustment algorithm function setL1PricingEquilibrationUnits(uint256 equilibrationUnits) external; - /// @notice Sets inertia parameter for L1 price adjustment algorithm + // @notice Sets inertia parameter for L1 price adjustment algorithm function setL1PricingInertia(uint64 inertia) external; - /// @notice Sets reward recipient address for L1 price adjustment algorithm + // @notice Sets reward recipient address for L1 price adjustment algorithm function setL1PricingRewardRecipient(address recipient) external; - /// @notice Sets reward amount for L1 price adjustment algorithm, in wei per unit + // @notice Sets reward amount for L1 price adjustment algorithm, in wei per unit function setL1PricingRewardRate(uint64 weiPerUnit) external; - /// @notice Set how much ArbOS charges per L1 gas spent on transaction data. + // @notice Set how much ArbOS charges per L1 gas spent on transaction data. function setL1PricePerUnit(uint256 pricePerUnit) external; - /// @notice Sets the base charge (in L1 gas) attributed to each data batch in the calldata pricer + // @notice Sets the base charge (in L1 gas) attributed to each data batch in the calldata pricer function setPerBatchGasCharge(int64 cost) external; - /// @notice Sets the cost amortization cap in basis points + // @notice Sets the cost amortization cap in basis points function setAmortizedCostCapBips(uint64 cap) external; - /// @notice Releases surplus funds from L1PricerFundsPoolAddress for use + // @notice Releases surplus funds from L1PricerFundsPoolAddress for use function releaseL1PricerSurplusFunds(uint256 maxWeiToRelease) external returns (uint256); + // @notice sets the price (in evm gas basis points) of ink + function setInkPrice(uint64 price) external; + + // @notice sets the maximum depth (in wasm words) a wasm stack may grow + function setWasmMaxDepth(uint32 depth) external; + + // @notice sets the cost of starting a stylus hostio call + function setWasmHostioInk(uint64 cost) external; + // Emitted when a successful call is made to this precompile event OwnerActs(bytes4 indexed method, address indexed owner, bytes data); } diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol new file mode 100644 index 00000000..acfd69e5 --- /dev/null +++ b/src/precompiles/ArbWasm.sol @@ -0,0 +1,40 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity >=0.4.21 <0.9.0; + +/** + * @title Methods for managing user programs + * @notice Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000071. + */ +interface ArbWasm { + // @notice compile a wasm program + // @param program the program to compile + // @return version the stylus version the program was compiled against + function compileProgram(address program) external returns (uint32 version); + + // @notice gets the latest stylus version + // @return version the stylus version + function stylusVersion() external view returns (uint32 version); + + // @notice gets the conversion rate between gas and ink + // @return price the price (in evm gas basis points) of ink + function inkPrice() external view returns (uint64 price); + + // @notice gets the wasm stack size limit + // @return depth the maximum depth (in wasm words) a wasm stack may grow + function wasmMaxDepth() external view returns (uint32 depth); + + // @notice gets the fixed-cost overhead needed to initiate a hostio call + // @return cost the cost (in ink) of starting a stylus hostio call + function wasmHostioInk() external view returns (uint64 price); + + // @notice gets the stylus version the program was most recently compiled against. + // @return version the program version (0 for EVM contracts) + function programVersion(address program) external view returns (uint32 version); + + error ProgramNotCompiled(); + error ProgramOutOfDate(uint32 version); + error ProgramUpToDate(); +} diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 8c98baa1..adcf8530 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -9,6 +9,7 @@ import "./ValueStack.sol"; import "./Machine.sol"; import "./Instructions.sol"; import "./StackFrame.sol"; +import "./GuardStack.sol"; import "./MerkleProof.sol"; import "./ModuleMemory.sol"; import "./Module.sol"; @@ -174,6 +175,48 @@ library Deserialize { window = StackFrameWindow({proved: proved, remainingHash: remainingHash}); } + function errorGuard(bytes calldata proof, uint256 startOffset) + internal + pure + returns (ErrorGuard memory guard, uint256 offset) + { + offset = startOffset; + Value memory onErrorPc; + bytes32 frameStack; + bytes32 valueStack; + bytes32 interStack; + (frameStack, offset) = b32(proof, offset); + (valueStack, offset) = b32(proof, offset); + (interStack, offset) = b32(proof, offset); + (onErrorPc, offset) = value(proof, offset); + guard = ErrorGuard({ + frameStack: frameStack, + valueStack: valueStack, + interStack: interStack, + onErrorPc: onErrorPc + }); + } + + function guardStack(bytes calldata proof, uint256 startOffset) + internal + pure + returns (GuardStack memory window, uint256 offset) + { + offset = startOffset; + bytes32 remainingHash; + (remainingHash, offset) = b32(proof, offset); + ErrorGuard[] memory proved; + if (proof[offset] != 0) { + offset++; + proved = new ErrorGuard[](1); + (proved[0], offset) = errorGuard(proof, offset); + } else { + offset++; + proved = new ErrorGuard[](0); + } + window = GuardStack({proved: proved, remainingHash: remainingHash}); + } + function moduleMemory(bytes calldata proof, uint256 startOffset) internal pure @@ -263,10 +306,12 @@ library Deserialize { uint32 functionIdx; uint32 functionPc; StackFrameWindow memory frameStack; + GuardStack memory guards; bytes32 modulesRoot; (values, offset) = valueStack(proof, offset); (internalStack, offset) = valueStack(proof, offset); (frameStack, offset) = stackFrameWindow(proof, offset); + (guards, offset) = guardStack(proof, offset); (globalStateHash, offset) = b32(proof, offset); (moduleIdx, offset) = u32(proof, offset); (functionIdx, offset) = u32(proof, offset); @@ -277,6 +322,7 @@ library Deserialize { valueStack: values, internalStack: internalStack, frameStack: frameStack, + guardStack: guards, globalStateHash: globalStateHash, moduleIdx: moduleIdx, functionIdx: functionIdx, diff --git a/src/state/GuardStack.sol b/src/state/GuardStack.sol new file mode 100644 index 00000000..59c75892 --- /dev/null +++ b/src/state/GuardStack.sol @@ -0,0 +1,82 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.0; + +import "./Value.sol"; + +struct ErrorGuard { + bytes32 frameStack; + bytes32 valueStack; + bytes32 interStack; + Value onErrorPc; +} + +struct GuardStack { + ErrorGuard[] proved; + bytes32 remainingHash; +} + +library GuardStackLib { + using ValueLib for Value; + + function newErrorGuard( + bytes32 frameStack, + bytes32 valueStack, + bytes32 interStack, + Value memory onErrorPc + ) internal pure returns (ErrorGuard memory) { + return + ErrorGuard({ + frameStack: frameStack, + valueStack: valueStack, + interStack: interStack, + onErrorPc: onErrorPc + }); + } + + function hash(ErrorGuard memory guard) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + "Error guard:", + guard.frameStack, + guard.valueStack, + guard.interStack, + guard.onErrorPc.hash() + ) + ); + } + + function hash(GuardStack memory guards) internal pure returns (bytes32 h) { + h = guards.remainingHash; + for (uint256 i = 0; i < guards.proved.length; i++) { + h = keccak256(abi.encodePacked("Guard stack:", hash(guards.proved[i]), h)); + } + } + + function empty(GuardStack memory guards) internal pure returns (bool) { + return guards.proved.length == 0 && guards.remainingHash == 0; + } + + function peek(GuardStack memory guards) internal pure returns (ErrorGuard memory) { + require(guards.proved.length == 1, "BAD_GUARDS_LENGTH"); + return guards.proved[0]; + } + + function pop(GuardStack memory guards) internal pure returns (ErrorGuard memory frame) { + require(guards.proved.length == 1, "BAD_GUARDS_LENGTH"); + frame = guards.proved[0]; + guards.proved = new ErrorGuard[](0); + } + + function push(GuardStack memory guards, ErrorGuard memory guard) internal pure { + ErrorGuard[] memory newProved = new ErrorGuard[](guards.proved.length + 1); + for (uint256 i = 0; i < guards.proved.length; i++) { + newProved[i] = guards.proved[i]; + } + newProved[guards.proved.length] = guard; + guards.proved = newProved; + } +} diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index 196899c9..a2c63ed3 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -1,5 +1,5 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; @@ -134,6 +134,8 @@ library Instructions { uint16 internal constant DUP = 0x8008; uint16 internal constant CROSS_MODULE_CALL = 0x8009; uint16 internal constant CALLER_MODULE_INTERNAL_CALL = 0x800A; + uint16 internal constant CROSS_MODULE_FORWARD = 0x800B; + uint16 internal constant CROSS_MODULE_DYNAMIC_CALL = 0x800C; uint16 internal constant GET_GLOBAL_STATE_BYTES32 = 0x8010; uint16 internal constant SET_GLOBAL_STATE_BYTES32 = 0x8011; @@ -143,6 +145,10 @@ library Instructions { uint16 internal constant READ_PRE_IMAGE = 0x8020; uint16 internal constant READ_INBOX_MESSAGE = 0x8021; uint16 internal constant HALT_AND_SET_FINISHED = 0x8022; + uint16 internal constant LINK_MODULE = 0x8023; + uint16 internal constant UNLINK_MODULE = 0x8024; + uint16 internal constant PUSH_ERROR_GUARD = 0x8025; + uint16 internal constant POP_ERROR_GUARD = 0x8026; uint256 internal constant INBOX_INDEX_SEQUENCER = 0; uint256 internal constant INBOX_INDEX_DELAYED = 1; diff --git a/src/state/Machine.sol b/src/state/Machine.sol index a7a5e927..d315586f 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -1,5 +1,5 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; @@ -7,6 +7,7 @@ pragma solidity ^0.8.0; import "./ValueStack.sol"; import "./Instructions.sol"; import "./StackFrame.sol"; +import "./GuardStack.sol"; enum MachineStatus { RUNNING, @@ -20,6 +21,7 @@ struct Machine { ValueStack valueStack; ValueStack internalStack; StackFrameWindow frameStack; + GuardStack guardStack; bytes32 globalStateHash; uint32 moduleIdx; uint32 functionIdx; @@ -29,25 +31,30 @@ struct Machine { library MachineLib { using StackFrameLib for StackFrameWindow; + using GuardStackLib for GuardStack; using ValueStackLib for ValueStack; function hash(Machine memory mach) internal pure returns (bytes32) { // Warning: the non-running hashes are replicated in Challenge if (mach.status == MachineStatus.RUNNING) { - return - keccak256( - abi.encodePacked( - "Machine running:", - mach.valueStack.hash(), - mach.internalStack.hash(), - mach.frameStack.hash(), - mach.globalStateHash, - mach.moduleIdx, - mach.functionIdx, - mach.functionPc, - mach.modulesRoot - ) - ); + bytes memory preimage = abi.encodePacked( + "Machine running:", + mach.valueStack.hash(), + mach.internalStack.hash(), + mach.frameStack.hash(), + mach.globalStateHash, + mach.moduleIdx, + mach.functionIdx, + mach.functionPc, + mach.modulesRoot + ); + + if (mach.guardStack.empty()) { + return keccak256(preimage); + } else { + return + keccak256(abi.encodePacked(preimage, "With guards:", mach.guardStack.hash())); + } } else if (mach.status == MachineStatus.FINISHED) { return keccak256(abi.encodePacked("Machine finished:", mach.globalStateHash)); } else if (mach.status == MachineStatus.ERRORED) { @@ -58,4 +65,19 @@ library MachineLib { revert("BAD_MACH_STATUS"); } } + + function setPc(Machine memory mach, Value memory pc) internal pure { + if (pc.valueType == ValueType.REF_NULL) { + mach.status = MachineStatus.ERRORED; + return; + } + + uint256 data = pc.contents; + require(pc.valueType == ValueType.INTERNAL_REF, "INVALID_PC_TYPE"); + require(data >> 96 == 0, "INVALID_PC_DATA"); + + mach.functionPc = uint32(data); + mach.functionIdx = uint32(data >> 32); + mach.moduleIdx = uint32(data >> 64); + } } diff --git a/src/state/MerkleProof.sol b/src/state/MerkleProof.sol index 560e3913..ea0b4dad 100644 --- a/src/state/MerkleProof.sol +++ b/src/state/MerkleProof.sol @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 @@ -95,5 +95,23 @@ library MerkleProofLib { } index >>= 1; } + require(index == 0, "PROOF_TOO_SHORT"); + } + + function growToNewRoot( + bytes32 root, + uint256 leaf, + bytes32 hash, + bytes32 zero, + string memory prefix + ) internal pure returns (bytes32) { + bytes32 h = hash; + uint256 node = leaf; + while (node > 1) { + h = keccak256(abi.encodePacked(prefix, h, zero)); + zero = keccak256(abi.encodePacked(prefix, zero, zero)); + node >>= 1; + } + return keccak256(abi.encodePacked(prefix, root, h)); } } diff --git a/src/state/Module.sol b/src/state/Module.sol index 71c775c2..1a515a1d 100644 --- a/src/state/Module.sol +++ b/src/state/Module.sol @@ -1,5 +1,5 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; diff --git a/src/state/ModuleMemory.sol b/src/state/ModuleMemory.sol index c1f0adb1..f3efb501 100644 --- a/src/state/ModuleMemory.sol +++ b/src/state/ModuleMemory.sol @@ -16,6 +16,8 @@ struct ModuleMemory { library ModuleMemoryLib { using MerkleProofLib for MerkleProof; + uint256 private constant LEAF_SIZE = 32; + function hash(ModuleMemory memory mem) internal pure returns (bytes32) { return keccak256(abi.encodePacked("Memory:", mem.size, mem.maxSize, mem.merkleRoot)); } @@ -40,4 +42,56 @@ library ModuleMemoryLib { bytes32 recomputedRoot = merkle.computeRootFromMemory(leafIdx, contents); require(recomputedRoot == mem.merkleRoot, "WRONG_MEM_ROOT"); } + + function isValidLeaf(ModuleMemory memory mem, uint256 pointer) internal pure returns (bool) { + return pointer + 32 <= mem.size && pointer % LEAF_SIZE == 0; + } + + function pullLeafByte(bytes32 leaf, uint256 idx) internal pure returns (uint8) { + require(idx < LEAF_SIZE, "BAD_PULL_LEAF_BYTE_IDX"); + // Take into account that we are casting the leaf to a big-endian integer + uint256 leafShift = (LEAF_SIZE - 1 - idx) * 8; + return uint8(uint256(leaf) >> leafShift); + } + + // loads a big-endian value from memory + function load( + ModuleMemory memory mem, + uint256 start, + uint256 width, + bytes calldata proof, + uint256 proofOffset + ) + internal + pure + returns ( + bool err, + uint256 value, + uint256 offset + ) + { + if (start + width > mem.size) { + return (true, 0, proofOffset); + } + + uint256 lastProvedLeafIdx = ~uint256(0); + bytes32 lastProvedLeafContents; + uint256 readValue; + for (uint256 i = 0; i < width; i++) { + uint256 idx = start + i; + uint256 leafIdx = idx / LEAF_SIZE; + if (leafIdx != lastProvedLeafIdx) { + (lastProvedLeafContents, proofOffset, ) = proveLeaf( + mem, + leafIdx, + proof, + proofOffset + ); + lastProvedLeafIdx = leafIdx; + } + uint256 indexWithinLeaf = idx % LEAF_SIZE; + readValue |= uint256(pullLeafByte(lastProvedLeafContents, indexWithinLeaf)) << (i * 8); + } + return (false, readValue, proofOffset); + } } diff --git a/src/state/StackFrame.sol b/src/state/StackFrame.sol index 465d6376..86b2762c 100644 --- a/src/state/StackFrame.sol +++ b/src/state/StackFrame.sol @@ -1,5 +1,5 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; @@ -60,4 +60,9 @@ library StackFrameLib { newProved[window.proved.length] = frame; window.proved = newProved; } + + function overwrite(StackFrameWindow memory window, bytes32 root) internal pure { + window.remainingHash = root; + delete window.proved; + } } diff --git a/src/state/Value.sol b/src/state/Value.sol index 6e0a837b..8f307056 100644 --- a/src/state/Value.sol +++ b/src/state/Value.sol @@ -1,5 +1,5 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; @@ -61,4 +61,16 @@ library ValueLib { return newI32(uint32(0)); } } + + function newPc( + uint32 funcPc, + uint32 func, + uint32 module + ) internal pure returns (Value memory) { + uint256 data = 0; + data |= funcPc; + data |= uint256(func) << 32; + data |= uint256(module) << 64; + return Value({valueType: ValueType.INTERNAL_REF, contents: data}); + } } diff --git a/src/state/ValueStack.sol b/src/state/ValueStack.sol index ccfe9ddc..0814d9c8 100644 --- a/src/state/ValueStack.sol +++ b/src/state/ValueStack.sol @@ -1,5 +1,5 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; @@ -36,4 +36,9 @@ library ValueStackLib { function push(ValueStack memory stack, Value memory val) internal pure { return stack.proved.push(val); } + + function overwrite(ValueStack memory stack, bytes32 root) internal pure { + stack.remainingHash = root; + delete stack.proved; + } } From d8871124843e5543d46593721c97a13cdb4a960f Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Wed, 17 May 2023 22:42:07 -0600 Subject: [PATCH 002/126] incorporate upstream changes --- src/mocks/InboxStub.sol | 4 ++-- src/mocks/Simple.sol | 10 +++++++++- src/precompiles/ArbDebug.sol | 5 ++++- src/precompiles/ArbOwner.sol | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/mocks/InboxStub.sol b/src/mocks/InboxStub.sol index a6ef92b6..3b183883 100644 --- a/src/mocks/InboxStub.sol +++ b/src/mocks/InboxStub.sol @@ -151,7 +151,7 @@ contract InboxStub is IInbox { address, uint256, bytes calldata - ) external returns (uint256) { + ) external pure returns (uint256) { revert("NOT_IMPLEMENTED"); } @@ -161,7 +161,7 @@ contract InboxStub is IInbox { uint256, uint256, address - ) external returns (uint256) { + ) external pure returns (uint256) { revert("NOT_IMPLEMENTED"); } diff --git a/src/mocks/Simple.sol b/src/mocks/Simple.sol index 9df036a3..1563fdb4 100644 --- a/src/mocks/Simple.sol +++ b/src/mocks/Simple.sol @@ -13,11 +13,17 @@ contract Simple { event CounterEvent(uint64 count); event RedeemedEvent(address caller, address redeemer); event NullEvent(); + event LogAndIncrementCalled(uint256 expected, uint256 have); function increment() external { counter++; } + function logAndIncrement(uint256 expected) external { + emit LogAndIncrementCalled(expected, counter); + counter++; + } + function incrementEmit() external { counter++; emit CounterEvent(counter); @@ -107,7 +113,9 @@ contract Simple { function checkGasUsed(address to, bytes calldata input) external view returns (uint256) { uint256 before = gasleft(); - to.staticcall{gas: before - 10000}(input); + // The inner call may revert, but we still want to return the amount of gas used, + // so we ignore the result of this call. + (to.staticcall{gas: before - 10000}(input)); return before - gasleft(); } } diff --git a/src/precompiles/ArbDebug.sol b/src/precompiles/ArbDebug.sol index 32e43bc6..68c67e21 100644 --- a/src/precompiles/ArbDebug.sol +++ b/src/precompiles/ArbDebug.sol @@ -1,5 +1,5 @@ // Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.4.21 <0.9.0; @@ -15,6 +15,9 @@ interface ArbDebug { /// @notice Emit events with values based on the args provided function events(bool flag, bytes32 value) external payable returns (address, uint256); + /// @notice Tries (and fails) to emit logs in a view context + function eventsView() external view; + // Events that exist for testing log creation and pricing event Basic(bool flag, bytes32 indexed value); event Mixed( diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index e9ed6d76..c7cf3514 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -1,5 +1,5 @@ // Copyright 2021-2023, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.4.21 <0.9.0; From edb6279528fee0f38e689c3e98939c98d36c3837 Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Tue, 20 Jun 2023 12:34:30 -0600 Subject: [PATCH 003/126] add contract methods --- src/precompiles/ArbOwner.sol | 14 +++++++++++++- src/precompiles/ArbWasm.sol | 26 +++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 63be3a56..42c7a991 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -93,7 +93,19 @@ interface ArbOwner { function setWasmMaxDepth(uint32 depth) external; // @notice sets the cost of starting a stylus hostio call - function setWasmHostioInk(uint64 cost) external; + function setWasmHostioInk(uint64 ink) external; + + // @notice sets the number of free wasm pages a tx gets + function setWasmFreePages(uint16 pages) external; + + // @notice sets the base cost of each additional wasm page + function setWasmPageGas(uint32 gas) external; + + // @notice sets the ramp that drives exponential wasm memory costs + function setWasmPageRamp(uint64 ramp) external; + + // @notice sets the maximum number of pages a wasm may allocate + function setWasmPageLimit(uint16 limit) external view; /// @notice Sets serialized chain config in ArbOS state function setChainConfig(string calldata chainConfig) external; diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index acfd69e5..18dabead 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -18,6 +18,10 @@ interface ArbWasm { // @return version the stylus version function stylusVersion() external view returns (uint32 version); + // @notice gets the stylus version the program was most recently compiled against. + // @return version the program version (0 for EVM contracts) + function programVersion(address program) external view returns (uint32 version); + // @notice gets the conversion rate between gas and ink // @return price the price (in evm gas basis points) of ink function inkPrice() external view returns (uint64 price); @@ -27,12 +31,24 @@ interface ArbWasm { function wasmMaxDepth() external view returns (uint32 depth); // @notice gets the fixed-cost overhead needed to initiate a hostio call - // @return cost the cost (in ink) of starting a stylus hostio call - function wasmHostioInk() external view returns (uint64 price); + // @return ink the cost of starting a stylus hostio call + function wasmHostioInk() external view returns (uint64 ink); - // @notice gets the stylus version the program was most recently compiled against. - // @return version the program version (0 for EVM contracts) - function programVersion(address program) external view returns (uint32 version); + // @notice gets the number of free wasm pages a program gets + // @return pages the number of wasm pages (2^16 bytes) + function freePages() external view returns (uint16 pages); + + // @notice gets the base cost of each additional wasm page (2^16 bytes) + // @return gas base amount of gas needed to grow another wasm page + function pageGas() external view returns (uint32 gas); + + // @notice gets the ramp that drives exponential memory costs + // @return ramp bits representing the floating point value + function pageRamp() external view returns (uint64 ramp); + + // @notice gets the maximum number of pages a wasm may allocate + // @return limit the number of pages + function pageLimit() external view returns (uint16 limit); error ProgramNotCompiled(); error ProgramOutOfDate(uint32 version); From c8b5ad0ff51c372b02c01934725758c0cf528e74 Mon Sep 17 00:00:00 2001 From: Joshua Colvin Date: Wed, 5 Jul 2023 11:28:58 -0700 Subject: [PATCH 004/126] Remove blockhash and difficulty from unit tests --- src/mocks/Program.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mocks/Program.sol b/src/mocks/Program.sol index ba75656b..6fedc50e 100644 --- a/src/mocks/Program.sol +++ b/src/mocks/Program.sol @@ -56,13 +56,11 @@ contract ProgramTest { address ethPrecompile = address(0x01); result = assert256(result, "block number ", block.number - 1); - result = assert256(result, "block hash ", uint256(blockhash(block.number - 1))); result = assert256(result, "chain id ", block.chainid); result = assert256(result, "base fee ", block.basefee); result = assert256(result, "gas price ", tx.gasprice); result = assert256(result, "gas limit ", block.gaslimit); result = assert256(result, "value ", 0); - result = assert256(result, "difficulty ", block.difficulty); result = assert256(result, "timestamp ", block.timestamp); result = assert256(result, "balance ", fundedAccount.balance); result = assert256(result, "rust address ", uint256(uint160(program))); From 0f0a59f8b33294abbcdf90375641593b0eef3f3d Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 1 Aug 2023 15:24:47 -0600 Subject: [PATCH 005/126] OneStepProof: CrossModuleInternal instead of dynamic --- src/osp/OneStepProver0.sol | 26 ++++++++++++++++++-------- src/state/Instructions.sol | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/osp/OneStepProver0.sol b/src/osp/OneStepProver0.sol index a7aee38b..56d54fc2 100644 --- a/src/osp/OneStepProver0.sol +++ b/src/osp/OneStepProver0.sol @@ -164,15 +164,25 @@ contract OneStepProver0 is IOneStepProver { mach.functionPc = 0; } - function executeCrossModuleDynamicCall( + function executeCrossModuleInternalCall( Machine memory mach, Module memory mod, Instruction calldata inst, - bytes calldata + bytes calldata proof ) internal pure { // Get the target from the stack - uint32 func = mach.valueStack.pop().assumeI32(); - uint32 module = mach.valueStack.pop().assumeI32(); + uint32 internalIndex = uint32(inst.argumentData); + uint32 moduleIndex = mach.valueStack.pop().assumeI32(); + Module memory calledMod; + + MerkleProof memory modProof; + uint256 offset = 0; + (calledMod, offset) = Deserialize.module(proof, offset); + (modProof, offset) = Deserialize.merkleProof(proof, offset); + require( + modProof.computeRootFromModule(moduleIndex, calledMod) == mach.modulesRoot, + "CROSS_MODULE_INTERNAL_MODULES_ROOT" + ); // Push the return pc to the stack mach.valueStack.push(createReturnValue(mach)); @@ -182,8 +192,8 @@ contract OneStepProver0 is IOneStepProver { mach.valueStack.push(ValueLib.newI32(mod.internalsOffset)); // Jump to the target - mach.moduleIdx = module; - mach.functionIdx = func; + mach.moduleIdx = moduleIndex; + mach.functionIdx = internalIndex + calledMod.internalsOffset; mach.functionPc = 0; } @@ -486,8 +496,8 @@ contract OneStepProver0 is IOneStepProver { impl = executeCrossModuleCall; } else if (opcode == Instructions.CROSS_MODULE_FORWARD) { impl = executeCrossModuleForward; - } else if (opcode == Instructions.CROSS_MODULE_DYNAMIC_CALL) { - impl = executeCrossModuleDynamicCall; + } else if (opcode == Instructions.CROSS_MODULE_INTERNAL_CALL) { + impl = executeCrossModuleInternalCall; } else if (opcode == Instructions.CALLER_MODULE_INTERNAL_CALL) { impl = executeCallerModuleInternalCall; } else if (opcode == Instructions.CALL_INDIRECT) { diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index a2c63ed3..54e396dc 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -135,7 +135,7 @@ library Instructions { uint16 internal constant CROSS_MODULE_CALL = 0x8009; uint16 internal constant CALLER_MODULE_INTERNAL_CALL = 0x800A; uint16 internal constant CROSS_MODULE_FORWARD = 0x800B; - uint16 internal constant CROSS_MODULE_DYNAMIC_CALL = 0x800C; + uint16 internal constant CROSS_MODULE_INTERNAL_CALL = 0x800C; uint16 internal constant GET_GLOBAL_STATE_BYTES32 = 0x8010; uint16 internal constant SET_GLOBAL_STATE_BYTES32 = 0x8011; From df85f5bc6f0f471a8e0da7a5bcff2d2b59375239 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 3 Aug 2023 09:11:26 -0600 Subject: [PATCH 006/126] add testcase --- src/mocks/SdkStorage.sol | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/mocks/SdkStorage.sol diff --git a/src/mocks/SdkStorage.sol b/src/mocks/SdkStorage.sol new file mode 100644 index 00000000..7309ef26 --- /dev/null +++ b/src/mocks/SdkStorage.sol @@ -0,0 +1,94 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.0; + +contract SdkStorage { + bool flag; + address owner; + address other; + Struct sub; + Struct[] structs; + uint64[] vector; + uint40[][] nested; + bytes bytesFull; + bytes bytesLong; + string chars; + Maps maps; + + struct Struct { + uint16 num; + int32 other; + bytes32 word; + } + + struct Maps { + mapping(uint256 => address) basic; + mapping(address => bool[]) vects; + mapping(uint32 => address)[] array; + mapping(bytes1 => mapping(bool => uint256)) nested; + mapping(string => Struct) structs; + } + + function test() external { + flag = true; + owner = address(0x70); + other = address(0x30); + + sub.num = 32; + sub.other = type(int32).max; + sub.word = bytes32(uint(64)); + + for (uint64 i = 0; i < 32; i++) { + vector.push(i); + } + vector[7] = 77; + + for (uint w = 0; w < 10; w++) { + nested.push(new uint40[](w)); + for (uint i = 0; i < w; i++) { + nested[w][i] = uint40(i); + } + } + for (uint w = 0; w < 10; w++) { + for (uint i = 0; i < w; i++) { + nested[w][i] *= 2; + } + } + + for (uint8 i = 0; i < 31; i++) { + bytesFull = abi.encodePacked(bytesFull, i); + } + for (uint8 i = 0; i < 34; i++) { + bytesLong = abi.encodePacked(bytesLong, i); + } + chars = "arbitrum stylus"; + + for (uint i = 0; i < 16; i++) { + maps.basic[i] = address(uint160(i)); + } + + for (uint160 a = 0; a < 4; a++) { + maps.vects[address(a)] = new bool[](0); + for (uint i = 0; i <= a; i++) { + maps.vects[address(a)].push(true); + } + } + + for (uint32 i = 0; i < 4; i++) { + maps.array.push(); + maps.array[i][i] = address(uint160(i)); + } + + for (uint8 i = 0; i < 4; i++) { + maps.nested[bytes1(i)][i % 2 == 0] = i + 1; + } + + maps.structs["stylus"] = sub; + + for (uint i = 0; i < 4; i++) { + structs.push(sub); + } + } +} From 58006ac0359c51fa81b15c2c25954cc927a7cfbb Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 5 Aug 2023 12:34:49 -0600 Subject: [PATCH 007/126] deletion test --- src/mocks/SdkStorage.sol | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/mocks/SdkStorage.sol b/src/mocks/SdkStorage.sol index 7309ef26..da56a207 100644 --- a/src/mocks/SdkStorage.sol +++ b/src/mocks/SdkStorage.sol @@ -26,33 +26,33 @@ contract SdkStorage { struct Maps { mapping(uint256 => address) basic; mapping(address => bool[]) vects; - mapping(uint32 => address)[] array; + mapping(int32 => address)[] array; mapping(bytes1 => mapping(bool => uint256)) nested; mapping(string => Struct) structs; } - function test() external { + function populate() external { flag = true; owner = address(0x70); other = address(0x30); sub.num = 32; sub.other = type(int32).max; - sub.word = bytes32(uint(64)); + sub.word = bytes32(uint256(64)); for (uint64 i = 0; i < 32; i++) { vector.push(i); } vector[7] = 77; - for (uint w = 0; w < 10; w++) { + for (uint256 w = 0; w < 10; w++) { nested.push(new uint40[](w)); - for (uint i = 0; i < w; i++) { + for (uint256 i = 0; i < w; i++) { nested[w][i] = uint40(i); } } - for (uint w = 0; w < 10; w++) { - for (uint i = 0; i < w; i++) { + for (uint256 w = 0; w < 10; w++) { + for (uint256 i = 0; i < w; i++) { nested[w][i] *= 2; } } @@ -65,20 +65,20 @@ contract SdkStorage { } chars = "arbitrum stylus"; - for (uint i = 0; i < 16; i++) { + for (uint256 i = 0; i < 16; i++) { maps.basic[i] = address(uint160(i)); } for (uint160 a = 0; a < 4; a++) { maps.vects[address(a)] = new bool[](0); - for (uint i = 0; i <= a; i++) { + for (uint256 i = 0; i <= a; i++) { maps.vects[address(a)].push(true); } } - for (uint32 i = 0; i < 4; i++) { + for (int32 i = 0; i < 4; i++) { maps.array.push(); - maps.array[i][i] = address(uint160(i)); + maps.array[uint256(uint32(i))][i] = address(uint160(uint32(i))); } for (uint8 i = 0; i < 4; i++) { @@ -87,8 +87,10 @@ contract SdkStorage { maps.structs["stylus"] = sub; - for (uint i = 0; i < 4; i++) { + for (uint256 i = 0; i < 4; i++) { structs.push(sub); } } + + function remove() external {} } From 92fd08763dfa35ecd614de2bd1f799c82aebc4e6 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 7 Aug 2023 09:08:14 -0600 Subject: [PATCH 008/126] block benchmarks --- src/mocks/Benchmarks.sol | 32 ++++++++++++++++++++++++++++++++ src/mocks/Program.sol | 11 ----------- 2 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 src/mocks/Benchmarks.sol diff --git a/src/mocks/Benchmarks.sol b/src/mocks/Benchmarks.sol new file mode 100644 index 00000000..6269e240 --- /dev/null +++ b/src/mocks/Benchmarks.sol @@ -0,0 +1,32 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.0; + +contract Benchmarks { + function fillBlockRecover() external payable { + bytes32 bridgeToNova = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; + address cryptoIsCute = 0x361594F5429D23ECE0A88E4fBE529E1c49D524d8; + uint8 v = 27; + bytes32 r = 0xc6178c2de1078cd36c3bd302cde755340d7f17fcb3fcc0b9c333ba03b217029f; + bytes32 s = 0x5fdbcefe2675e96219cdae57a7894280bf80fd40d44ce146a35e169ea6a78fd3; + while (true) { + require(ecrecover(bridgeToNova, v, r, s) == cryptoIsCute, "WRONG_ARBINAUT"); + } + } + + function fillBlockHash() external payable { + bytes32 hash = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; + while (true) { + hash = keccak256(abi.encodePacked(hash)); + } + } + + function fillBlockQuickStep() external payable { + uint256 value = 0; + while (true) { + value = msg.value; + } + } +} diff --git a/src/mocks/Program.sol b/src/mocks/Program.sol index 6fedc50e..c71410ea 100644 --- a/src/mocks/Program.sol +++ b/src/mocks/Program.sol @@ -87,15 +87,4 @@ contract ProgramTest { } return result; } - - function fillBlock() external payable { - bytes32 bridgeToNova = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; - address cryptoIsCute = 0x361594F5429D23ECE0A88E4fBE529E1c49D524d8; - uint8 v = 27; - bytes32 r = 0xc6178c2de1078cd36c3bd302cde755340d7f17fcb3fcc0b9c333ba03b217029f; - bytes32 s = 0x5fdbcefe2675e96219cdae57a7894280bf80fd40d44ce146a35e169ea6a78fd3; - while (true) { - require(ecrecover(bridgeToNova, v, r, s) == cryptoIsCute, "WRONG_ARBINAUT"); - } - } } From c95399d7d4059deed7082fd85b85918b43b7860f Mon Sep 17 00:00:00 2001 From: Joshua Colvin Date: Mon, 7 Aug 2023 21:18:17 -0700 Subject: [PATCH 009/126] Add `setWasmCallScalar` --- src/precompiles/ArbOwner.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 42c7a991..419eb815 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -107,6 +107,9 @@ interface ArbOwner { // @notice sets the maximum number of pages a wasm may allocate function setWasmPageLimit(uint16 limit) external view; + // @notice sets the cost of calling 64kb wasm, cost is scaled according to actual wasm size + function setWasmCallScalar(uint16 gas) external view; + /// @notice Sets serialized chain config in ArbOS state function setChainConfig(string calldata chainConfig) external; From 2cc238ff82939a0e6daf9541af4d56e1c990e30f Mon Sep 17 00:00:00 2001 From: Joshua Colvin Date: Tue, 8 Aug 2023 08:42:07 -0700 Subject: [PATCH 010/126] Address code review comments * Change cost to per eighth of a kb * Add getter for call scalar --- src/precompiles/ArbOwner.sol | 2 +- src/precompiles/ArbWasm.sol | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 419eb815..9bf1a99d 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -107,7 +107,7 @@ interface ArbOwner { // @notice sets the maximum number of pages a wasm may allocate function setWasmPageLimit(uint16 limit) external view; - // @notice sets the cost of calling 64kb wasm, cost is scaled according to actual wasm size + // @notice sets the call overhead priced per eighth of a kb of compressed wasm function setWasmCallScalar(uint16 gas) external view; /// @notice Sets serialized chain config in ArbOS state diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 18dabead..eb5993e5 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -50,6 +50,10 @@ interface ArbWasm { // @return limit the number of pages function pageLimit() external view returns (uint16 limit); + // @notice gets the call overhead priced per eighth of a kb of compressed wasm + // @return call overhead priced per eighth of a kb of compressed wasm + function callScalar() external view returns (uint16 limit); + error ProgramNotCompiled(); error ProgramOutOfDate(uint32 version); error ProgramUpToDate(); From 14151a3b7df5586412b63c24279fc697452f3fda Mon Sep 17 00:00:00 2001 From: Joshua Colvin Date: Tue, 8 Aug 2023 11:56:22 -0700 Subject: [PATCH 011/126] Update wasmcallscalar to be per half kb --- src/precompiles/ArbOwner.sol | 2 +- src/precompiles/ArbWasm.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 9bf1a99d..d4ac7bcc 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -107,7 +107,7 @@ interface ArbOwner { // @notice sets the maximum number of pages a wasm may allocate function setWasmPageLimit(uint16 limit) external view; - // @notice sets the call overhead priced per eighth of a kb of compressed wasm + // @notice sets the call overhead priced per half of a kb of compressed wasm function setWasmCallScalar(uint16 gas) external view; /// @notice Sets serialized chain config in ArbOS state diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index eb5993e5..48aa8f05 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -50,8 +50,8 @@ interface ArbWasm { // @return limit the number of pages function pageLimit() external view returns (uint16 limit); - // @notice gets the call overhead priced per eighth of a kb of compressed wasm - // @return call overhead priced per eighth of a kb of compressed wasm + // @notice gets the call overhead priced per half of a kb of compressed wasm + // @return call overhead priced per half of a kb of compressed wasm function callScalar() external view returns (uint16 limit); error ProgramNotCompiled(); From 8dc93a760094b5189f538871ead61c5a74ff2f70 Mon Sep 17 00:00:00 2001 From: Joshua Colvin Date: Tue, 8 Aug 2023 13:01:07 -0700 Subject: [PATCH 012/126] Make method names consistent --- src/precompiles/ArbWasm.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 48aa8f05..9d43f84b 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -28,11 +28,11 @@ interface ArbWasm { // @notice gets the wasm stack size limit // @return depth the maximum depth (in wasm words) a wasm stack may grow - function wasmMaxDepth() external view returns (uint32 depth); + function maxStackDepth() external view returns (uint32 depth); // @notice gets the fixed-cost overhead needed to initiate a hostio call // @return ink the cost of starting a stylus hostio call - function wasmHostioInk() external view returns (uint64 ink); + function hostioInk() external view returns (uint64 ink); // @notice gets the number of free wasm pages a program gets // @return pages the number of wasm pages (2^16 bytes) From b13351237007064a1a46a6a1753f6d97791394db Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 9 Aug 2023 21:29:53 -0600 Subject: [PATCH 013/126] updates the types used to configure stylus parameters --- src/precompiles/ArbOwner.sol | 10 ++++------ src/precompiles/ArbWasm.sol | 18 +++++++----------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 42c7a991..4c59e686 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -86,20 +86,18 @@ interface ArbOwner { // @notice Releases surplus funds from L1PricerFundsPoolAddress for use function releaseL1PricerSurplusFunds(uint256 maxWeiToRelease) external returns (uint256); - // @notice sets the price (in evm gas basis points) of ink - function setInkPrice(uint64 price) external; + // @notice sets the amount of ink 1 gas buys + // @param price the conversion rate (must fit in a uint24) + function setInkPrice(uint32 price) external; // @notice sets the maximum depth (in wasm words) a wasm stack may grow function setWasmMaxDepth(uint32 depth) external; - // @notice sets the cost of starting a stylus hostio call - function setWasmHostioInk(uint64 ink) external; - // @notice sets the number of free wasm pages a tx gets function setWasmFreePages(uint16 pages) external; // @notice sets the base cost of each additional wasm page - function setWasmPageGas(uint32 gas) external; + function setWasmPageGas(uint16 gas) external; // @notice sets the ramp that drives exponential wasm memory costs function setWasmPageRamp(uint64 ramp) external; diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 18dabead..f1a364df 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -12,35 +12,31 @@ interface ArbWasm { // @notice compile a wasm program // @param program the program to compile // @return version the stylus version the program was compiled against - function compileProgram(address program) external returns (uint32 version); + function compileProgram(address program) external returns (uint16 version); // @notice gets the latest stylus version // @return version the stylus version - function stylusVersion() external view returns (uint32 version); + function stylusVersion() external view returns (uint16 version); // @notice gets the stylus version the program was most recently compiled against. // @return version the program version (0 for EVM contracts) - function programVersion(address program) external view returns (uint32 version); + function programVersion(address program) external view returns (uint16 version); // @notice gets the conversion rate between gas and ink - // @return price the price (in evm gas basis points) of ink - function inkPrice() external view returns (uint64 price); + // @return price the amount of ink 1 gas buys + function inkPrice() external view returns (uint32 price); // @notice gets the wasm stack size limit // @return depth the maximum depth (in wasm words) a wasm stack may grow function wasmMaxDepth() external view returns (uint32 depth); - // @notice gets the fixed-cost overhead needed to initiate a hostio call - // @return ink the cost of starting a stylus hostio call - function wasmHostioInk() external view returns (uint64 ink); - // @notice gets the number of free wasm pages a program gets // @return pages the number of wasm pages (2^16 bytes) function freePages() external view returns (uint16 pages); // @notice gets the base cost of each additional wasm page (2^16 bytes) // @return gas base amount of gas needed to grow another wasm page - function pageGas() external view returns (uint32 gas); + function pageGas() external view returns (uint16 gas); // @notice gets the ramp that drives exponential memory costs // @return ramp bits representing the floating point value @@ -51,6 +47,6 @@ interface ArbWasm { function pageLimit() external view returns (uint16 limit); error ProgramNotCompiled(); - error ProgramOutOfDate(uint32 version); + error ProgramOutOfDate(uint16 version); error ProgramUpToDate(); } From c86ee1c24cd228c67cb4dd023db956f9f6316192 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 10 Aug 2023 20:48:27 -0600 Subject: [PATCH 014/126] pop & delete tests --- src/mocks/SdkStorage.sol | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/mocks/SdkStorage.sol b/src/mocks/SdkStorage.sol index da56a207..c90c64d0 100644 --- a/src/mocks/SdkStorage.sol +++ b/src/mocks/SdkStorage.sol @@ -60,7 +60,7 @@ contract SdkStorage { for (uint8 i = 0; i < 31; i++) { bytesFull = abi.encodePacked(bytesFull, i); } - for (uint8 i = 0; i < 34; i++) { + for (uint8 i = 0; i < 80; i++) { bytesLong = abi.encodePacked(bytesLong, i); } chars = "arbitrum stylus"; @@ -92,5 +92,32 @@ contract SdkStorage { } } - function remove() external {} + function remove() external { + while (bytesFull.length != 0) { + bytesFull.pop(); + } + + while (bytesLong.length > 16) { + bytesLong.pop(); + } + + chars = "wasm is cute <3"; + + while (vector.length != 0) { + vector.pop(); + } + + while (nested.length > 1) { + nested.pop(); + } + + for (uint256 i = 0; i < 8; i++) { + delete maps.basic[i]; + } + maps.basic[8] = address(32); + + for (uint160 i = 0; i < 4; i++) { + delete maps.vects[address(i)]; + } + } } From e65b52d8df83299f29fff2ba534cc64575febf5d Mon Sep 17 00:00:00 2001 From: Joshua Colvin Date: Fri, 11 Aug 2023 12:30:20 -0700 Subject: [PATCH 015/126] Fix method name --- src/precompiles/ArbOwner.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 9af3b0ec..01ca3303 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -91,7 +91,7 @@ interface ArbOwner { function setInkPrice(uint32 price) external; // @notice sets the maximum depth (in wasm words) a wasm stack may grow - function setWasmMaxDepth(uint32 depth) external; + function setWasmMaxStackDepth(uint32 depth) external; // @notice sets the number of free wasm pages a tx gets function setWasmFreePages(uint16 pages) external; From 369c3f023365194f068b113335720bdd3d29320b Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 12 Aug 2023 08:51:13 -0600 Subject: [PATCH 016/126] struct pop test --- src/mocks/SdkStorage.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mocks/SdkStorage.sol b/src/mocks/SdkStorage.sol index c90c64d0..bf92b8e5 100644 --- a/src/mocks/SdkStorage.sol +++ b/src/mocks/SdkStorage.sol @@ -119,5 +119,7 @@ contract SdkStorage { for (uint160 i = 0; i < 4; i++) { delete maps.vects[address(i)]; } + + structs.pop(); } } From 1f6172c8b958d5856e5a6c8e8d9e91b1cb7a56bb Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 12 Aug 2023 09:52:09 -0600 Subject: [PATCH 017/126] update comments --- src/precompiles/ArbOwner.sol | 2 +- src/precompiles/ArbWasm.sol | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 01ca3303..45a29703 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -105,7 +105,7 @@ interface ArbOwner { // @notice sets the maximum number of pages a wasm may allocate function setWasmPageLimit(uint16 limit) external view; - // @notice sets the call overhead priced per half of a kb of compressed wasm + // @notice sets the added wasm call cost based on binary size function setWasmCallScalar(uint16 gas) external view; /// @notice Sets serialized chain config in ArbOS state diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 78ae36e4..14716c19 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -46,9 +46,9 @@ interface ArbWasm { // @return limit the number of pages function pageLimit() external view returns (uint16 limit); - // @notice gets the call overhead priced per half of a kb of compressed wasm - // @return call overhead priced per half of a kb of compressed wasm - function callScalar() external view returns (uint16 limit); + // @notice gets the added wasm call cost based on binary size + // @return gas cost paid per half kb uncompressed. + function callScalar() external view returns (uint16 gas); error ProgramNotCompiled(); error ProgramOutOfDate(uint16 version); From 4a15b42b7aeb24b50a46a03c18af0cf61e318312 Mon Sep 17 00:00:00 2001 From: Joshua Colvin Date: Sun, 20 Aug 2023 17:24:34 -0700 Subject: [PATCH 018/126] Add stylus programSize and programMemoryFootprint precompiles --- src/precompiles/ArbWasm.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 14716c19..45fc895c 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -22,6 +22,14 @@ interface ArbWasm { // @return version the program version (0 for EVM contracts) function programVersion(address program) external view returns (uint16 version); + // @notice gets the uncompressed size of the program at the given address in bytes + // @return version the program version (0 for EVM contracts) + function programSize(address program) external view returns (uint32 size); + + // @notice gets the memory footprint of the program at the given address in pages + // @return version the program version (0 for EVM contracts) + function programMemoryFootprint(address program) external view returns (uint16 footprint); + // @notice gets the conversion rate between gas and ink // @return price the amount of ink 1 gas buys function inkPrice() external view returns (uint32 price); From 8adbc2b5ddd05829ecc0ac36b840ab41f6500ada Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 22 Aug 2023 12:45:20 -0600 Subject: [PATCH 019/126] arbwasm: rename compile to activate --- src/precompiles/ArbWasm.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 14716c19..e646a246 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -12,7 +12,7 @@ interface ArbWasm { // @notice compile a wasm program // @param program the program to compile // @return version the stylus version the program was compiled against - function compileProgram(address program) external returns (uint16 version); + function activateProgram(address program) external returns (uint16 version); // @notice gets the latest stylus version // @return version the stylus version From 1d84c0d963ffec15b8569de1d37c473027a1ffc5 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 23 Aug 2023 19:09:42 -0600 Subject: [PATCH 020/126] rename program actiated error --- src/precompiles/ArbWasm.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index e646a246..0059e99f 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -50,7 +50,7 @@ interface ArbWasm { // @return gas cost paid per half kb uncompressed. function callScalar() external view returns (uint16 gas); - error ProgramNotCompiled(); + error ProgramNotActivated(); error ProgramOutOfDate(uint16 version); error ProgramUpToDate(); } From 1e31d42396af2b231b915873e2085880560b396f Mon Sep 17 00:00:00 2001 From: Joshua Colvin Date: Mon, 28 Aug 2023 14:31:33 -0700 Subject: [PATCH 021/126] Add codehashversion precompile --- src/precompiles/ArbWasm.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 0059e99f..6ae4449e 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -18,6 +18,10 @@ interface ArbWasm { // @return version the stylus version function stylusVersion() external view returns (uint16 version); + // @notice gets the stylus version the program with codehash was most recently compiled against. + // @return version the program version (0 for EVM contracts) + function codehashVersion(bytes32 codehash) external view returns (uint16 version); + // @notice gets the stylus version the program was most recently compiled against. // @return version the program version (0 for EVM contracts) function programVersion(address program) external view returns (uint16 version); From 9d10c41cdad189893f5c08827113fece7200b560 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 5 Sep 2023 20:18:32 -0600 Subject: [PATCH 022/126] fixed array tests --- src/mocks/SdkStorage.sol | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/mocks/SdkStorage.sol b/src/mocks/SdkStorage.sol index bf92b8e5..4f624ee9 100644 --- a/src/mocks/SdkStorage.sol +++ b/src/mocks/SdkStorage.sol @@ -16,6 +16,7 @@ contract SdkStorage { bytes bytesLong; string chars; Maps maps; + Arrays arrays; struct Struct { uint16 num; @@ -31,6 +32,18 @@ contract SdkStorage { mapping(string => Struct) structs; } + struct Arrays { + string[4] strings; + uint8 spacer; + uint24[5] packed; + uint8 trail; + address[2] spill; + uint8[2][4] matrix; + int96[4][] vector; + int96[][4] vectors; + Struct[3] structs; + } + function populate() external { flag = true; owner = address(0x70); @@ -90,6 +103,39 @@ contract SdkStorage { for (uint256 i = 0; i < 4; i++) { structs.push(sub); } + + arrays.strings[2] = "L2 is for you!"; + + for (uint256 i = 0; i < 5; i++) { + arrays.packed[i] = uint24(i); + } + + for (uint256 i = 0; i < 2; i++) { + arrays.spill[i] = address(uint160(i)); + } + + for (uint256 i = 0; i < 4; i++) { + arrays.matrix[i][0] = uint8(i); + arrays.matrix[i][1] = arrays.matrix[i][0] + 1; + } + + for (uint256 w = 0; w < 3; w++) { + int96[4] memory array; + for (int256 i = 0; i < 4; i++) { + array[uint256(i)] = int96(i); + } + arrays.vector.push(array); + } + + for (uint256 w = 0; w < 4; w++) { + for (int96 i = 0; i < 4; i++) { + arrays.vectors[w].push(i); + } + } + + for (uint256 i = 0; i < 3; i++) { + arrays.structs[i] = sub; + } } function remove() external { From 94a3baa997a2200190a6552b9486890419f9922e Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 5 Sep 2023 20:55:09 -0600 Subject: [PATCH 023/126] add erasure test --- src/mocks/SdkStorage.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mocks/SdkStorage.sol b/src/mocks/SdkStorage.sol index 4f624ee9..dac31592 100644 --- a/src/mocks/SdkStorage.sol +++ b/src/mocks/SdkStorage.sol @@ -167,5 +167,10 @@ contract SdkStorage { } structs.pop(); + + delete arrays.matrix; + delete arrays.vector; + delete arrays.vectors; + delete arrays.structs; } } From d5ce0937222aa51f67eda9b3b5f3a1cc833df2a1 Mon Sep 17 00:00:00 2001 From: Joshua Colvin Date: Tue, 5 Sep 2023 22:06:41 -0700 Subject: [PATCH 024/126] Fix docstrings --- src/precompiles/ArbWasm.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index e81c66c4..0741e961 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -27,11 +27,11 @@ interface ArbWasm { function programVersion(address program) external view returns (uint16 version); // @notice gets the uncompressed size of the program at the given address in bytes - // @return version the program version (0 for EVM contracts) + // @return size the size of the program in bytes rounded up to a multiple of 512 function programSize(address program) external view returns (uint32 size); // @notice gets the memory footprint of the program at the given address in pages - // @return version the program version (0 for EVM contracts) + // @return footprint the memory footprint of program in pages function programMemoryFootprint(address program) external view returns (uint16 footprint); // @notice gets the conversion rate between gas and ink From 99f0abb0642160345310e7205949e1fb40b9a642 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 6 Sep 2023 10:43:49 -0600 Subject: [PATCH 025/126] arbwasm: add ProgramActivated event --- src/precompiles/ArbWasm.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 6ae4449e..f7f6673b 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -54,6 +54,8 @@ interface ArbWasm { // @return gas cost paid per half kb uncompressed. function callScalar() external view returns (uint16 gas); + event ProgramActivated(address program, bytes32 codehash, uint16 version); + error ProgramNotActivated(); error ProgramOutOfDate(uint16 version); error ProgramUpToDate(); From 09475d3bd47c41b4cd1bcace615e5bce1cc058b6 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 6 Sep 2023 12:04:48 -0600 Subject: [PATCH 026/126] arbwasm: index program activated --- src/precompiles/ArbWasm.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index f7f6673b..7d693f08 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -54,7 +54,7 @@ interface ArbWasm { // @return gas cost paid per half kb uncompressed. function callScalar() external view returns (uint16 gas); - event ProgramActivated(address program, bytes32 codehash, uint16 version); + event ProgramActivated(address indexed program, bytes32 indexed codehash, uint16 version); error ProgramNotActivated(); error ProgramOutOfDate(uint16 version); From 4c4c789470179d03dccf7abfc16fdf4e7943c464 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 6 Sep 2023 13:19:17 -0600 Subject: [PATCH 027/126] arbwasm: update event --- src/precompiles/ArbWasm.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 7d693f08..86c40852 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -54,7 +54,7 @@ interface ArbWasm { // @return gas cost paid per half kb uncompressed. function callScalar() external view returns (uint16 gas); - event ProgramActivated(address indexed program, bytes32 indexed codehash, uint16 version); + event ProgramActivated(bytes32 indexed codehash, address program, uint16 version); error ProgramNotActivated(); error ProgramOutOfDate(uint16 version); From 2c10f94f118ba21d463280a39e8dccaa7c390705 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 23:07:30 -0600 Subject: [PATCH 028/126] add modulehash field in activated event log --- src/precompiles/ArbWasm.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index b836a55a..b63deec0 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -62,7 +62,7 @@ interface ArbWasm { // @return gas cost paid per half kb uncompressed. function callScalar() external view returns (uint16 gas); - event ProgramActivated(bytes32 indexed codehash, address program, uint16 version); + event ProgramActivated(bytes32 indexed codehash, bytes32 moduleHash, address program, uint16 version); error ProgramNotActivated(); error ProgramOutOfDate(uint16 version); From 70682f242380296c18359a2e4e2b994e5e099cac Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 23:07:52 -0600 Subject: [PATCH 029/126] format --- src/precompiles/ArbWasm.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index b63deec0..d84786c8 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -62,7 +62,12 @@ interface ArbWasm { // @return gas cost paid per half kb uncompressed. function callScalar() external view returns (uint16 gas); - event ProgramActivated(bytes32 indexed codehash, bytes32 moduleHash, address program, uint16 version); + event ProgramActivated( + bytes32 indexed codehash, + bytes32 moduleHash, + address program, + uint16 version + ); error ProgramNotActivated(); error ProgramOutOfDate(uint16 version); From f7bcf64cd4067533f461399545a279c957cb0821 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 21 Nov 2023 23:30:08 -0700 Subject: [PATCH 030/126] error guard policy --- src/osp/OneStepProofEntry.sol | 4 +- src/osp/OneStepProverHostIo.sol | 19 ++++++++- src/precompiles/ArbOwner.sol | 68 ++++++++++++++++----------------- src/state/Deserialize.sol | 4 +- src/state/GuardStack.sol | 13 ++++++- src/state/Instructions.sol | 1 + 6 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index c90480d7..b22d43e7 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -117,7 +117,7 @@ contract OneStepProofEntry is IOneStepProofEntry { } else if ( (opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 && opcode <= Instructions.SET_GLOBAL_STATE_U64) || - (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) + (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.SET_ERROR_POLICY) ) { prover = proverHostIo; } else { @@ -132,7 +132,7 @@ contract OneStepProofEntry is IOneStepProofEntry { mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod); } - if (mach.status == MachineStatus.ERRORED && !mach.guardStack.empty()) { + if (mach.status == MachineStatus.ERRORED && mach.guardStack.canPop()) { ErrorGuard memory guard = mach.guardStack.pop(); mach.frameStack.overwrite(guard.frameStack); mach.valueStack.overwrite(guard.valueStack); diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index d8d0d340..24465ded 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -392,14 +392,15 @@ contract OneStepProverHostIo is IOneStepProver { Machine memory mach, Module memory, Instruction calldata, - bytes calldata proof - ) internal view { + bytes calldata + ) internal pure { bytes32 frames = mach.frameStack.hash(); bytes32 values = mach.valueStack.hash(); bytes32 inters = mach.internalStack.hash(); Value memory onError = ValueLib.newPc(mach.functionPc, mach.functionIdx, mach.moduleIdx); mach.guardStack.push(GuardStackLib.newErrorGuard(frames, values, inters, onError)); mach.valueStack.push(ValueLib.newI32(1)); + mach.guardStack.enabled = true; } function executePopErrorGuard( @@ -410,6 +411,18 @@ contract OneStepProverHostIo is IOneStepProver { bytes calldata ) internal pure { mach.guardStack.pop(); + mach.guardStack.enabled = false; + } + + function executeSetErrorPolicy( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata inst, + bytes calldata + ) internal pure { + uint32 status = mach.valueStack.pop().assumeI32(); + mach.guardStack.enabled = status != 0; } function executeGlobalStateAccess( @@ -481,6 +494,8 @@ contract OneStepProverHostIo is IOneStepProver { impl = executePushErrorGuard; } else if (opcode == Instructions.POP_ERROR_GUARD) { impl = executePopErrorGuard; + } else if (opcode == Instructions.SET_ERROR_POLICY) { + impl = executeSetErrorPolicy; } else { revert("INVALID_MEMORY_OPCODE"); } diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 86fee30c..ed3a6720 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -14,103 +14,103 @@ pragma solidity >=0.4.21 <0.9.0; * Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000070. **/ interface ArbOwner { - // @notice Add account as a chain owner + /// @notice Add account as a chain owner function addChainOwner(address newOwner) external; - // @notice Remove account from the list of chain owners + /// @notice Remove account from the list of chain owners function removeChainOwner(address ownerToRemove) external; - // @notice See if the user is a chain owner + /// @notice See if the user is a chain owner function isChainOwner(address addr) external view returns (bool); - // @notice Retrieves the list of chain owners + /// @notice Retrieves the list of chain owners function getAllChainOwners() external view returns (address[] memory); - // @notice Set how slowly ArbOS updates its estimate of the L1 basefee + /// @notice Set how slowly ArbOS updates its estimate of the L1 basefee function setL1BaseFeeEstimateInertia(uint64 inertia) external; - // @notice Set the L2 basefee directly, bypassing the pool calculus + /// @notice Set the L2 basefee directly, bypassing the pool calculus function setL2BaseFee(uint256 priceInWei) external; - // @notice Set the minimum basefee needed for a transaction to succeed + /// @notice Set the minimum basefee needed for a transaction to succeed function setMinimumL2BaseFee(uint256 priceInWei) external; - // @notice Set the computational speed limit for the chain + /// @notice Set the computational speed limit for the chain function setSpeedLimit(uint64 limit) external; - // @notice Set the maximum size a tx (and block) can be + /// @notice Set the maximum size a tx (and block) can be function setMaxTxGasLimit(uint64 limit) external; - // @notice Set the L2 gas pricing inertia + /// @notice Set the L2 gas pricing inertia function setL2GasPricingInertia(uint64 sec) external; - // @notice Set the L2 gas backlog tolerance + /// @notice Set the L2 gas backlog tolerance function setL2GasBacklogTolerance(uint64 sec) external; - // @notice Get the network fee collector + /// @notice Get the network fee collector function getNetworkFeeAccount() external view returns (address); - // @notice Get the infrastructure fee collector + /// @notice Get the infrastructure fee collector function getInfraFeeAccount() external view returns (address); - // @notice Set the network fee collector + /// @notice Set the network fee collector function setNetworkFeeAccount(address newNetworkFeeAccount) external; - // @notice Set the infrastructure fee collector + /// @notice Set the infrastructure fee collector function setInfraFeeAccount(address newInfraFeeAccount) external; - // @notice Upgrades ArbOS to the requested version at the requested timestamp + /// @notice Upgrades ArbOS to the requested version at the requested timestamp function scheduleArbOSUpgrade(uint64 newVersion, uint64 timestamp) external; - // @notice Sets equilibration units parameter for L1 price adjustment algorithm + /// @notice Sets equilibration units parameter for L1 price adjustment algorithm function setL1PricingEquilibrationUnits(uint256 equilibrationUnits) external; - // @notice Sets inertia parameter for L1 price adjustment algorithm + /// @notice Sets inertia parameter for L1 price adjustment algorithm function setL1PricingInertia(uint64 inertia) external; - // @notice Sets reward recipient address for L1 price adjustment algorithm + /// @notice Sets reward recipient address for L1 price adjustment algorithm function setL1PricingRewardRecipient(address recipient) external; - // @notice Sets reward amount for L1 price adjustment algorithm, in wei per unit + /// @notice Sets reward amount for L1 price adjustment algorithm, in wei per unit function setL1PricingRewardRate(uint64 weiPerUnit) external; - // @notice Set how much ArbOS charges per L1 gas spent on transaction data. + /// @notice Set how much ArbOS charges per L1 gas spent on transaction data. function setL1PricePerUnit(uint256 pricePerUnit) external; - // @notice Sets the base charge (in L1 gas) attributed to each data batch in the calldata pricer + /// @notice Sets the base charge (in L1 gas) attributed to each data batch in the calldata pricer function setPerBatchGasCharge(int64 cost) external; - // @notice Sets the cost amortization cap in basis points + /// @notice Sets the cost amortization cap in basis points function setAmortizedCostCapBips(uint64 cap) external; - // @notice Releases surplus funds from L1PricerFundsPoolAddress for use + /// @notice Releases surplus funds from L1PricerFundsPoolAddress for use function releaseL1PricerSurplusFunds(uint256 maxWeiToRelease) external returns (uint256); - // @notice sets the amount of ink 1 gas buys - // @param price the conversion rate (must fit in a uint24) + /// @notice sets the amount of ink 1 gas buys + /// @param price the conversion rate (must fit in a uint24) function setInkPrice(uint32 price) external; - // @notice sets the maximum depth (in wasm words) a wasm stack may grow + /// @notice sets the maximum depth (in wasm words) a wasm stack may grow function setWasmMaxStackDepth(uint32 depth) external; - // @notice sets the number of free wasm pages a tx gets + /// @notice sets the number of free wasm pages a tx gets function setWasmFreePages(uint16 pages) external; - // @notice sets the base cost of each additional wasm page + /// @notice sets the base cost of each additional wasm page function setWasmPageGas(uint16 gas) external; - // @notice sets the ramp that drives exponential wasm memory costs + /// @notice sets the ramp that drives exponential wasm memory costs function setWasmPageRamp(uint64 ramp) external; - // @notice sets the maximum number of pages a wasm may allocate + /// @notice sets the maximum number of pages a wasm may allocate function setWasmPageLimit(uint16 limit) external view; - // @notice sets the added wasm call cost based on binary size + /// @notice sets the added wasm call cost based on binary size function setWasmCallScalar(uint16 gas) external view; - /// @notice Sets serialized chain config in ArbOS state + //// @notice Sets serialized chain config in ArbOS state function setChainConfig(string calldata chainConfig) external; - // Emitted when a successful call is made to this precompile + /// Emitted when a successful call is made to this precompile event OwnerActs(bytes4 indexed method, address indexed owner, bytes data); } diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 5ab44045..1bb1bc57 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -206,6 +206,8 @@ library Deserialize { bytes32 remainingHash; (remainingHash, offset) = b32(proof, offset); ErrorGuard[] memory proved; + + bool enabled = uint8(proof[offset]) == 2; if (proof[offset] != 0) { offset++; proved = new ErrorGuard[](1); @@ -214,7 +216,7 @@ library Deserialize { offset++; proved = new ErrorGuard[](0); } - window = GuardStack({proved: proved, remainingHash: remainingHash}); + window = GuardStack({proved: proved, remainingHash: remainingHash, enabled: enabled}); } function moduleMemory(bytes calldata proof, uint256 startOffset) diff --git a/src/state/GuardStack.sol b/src/state/GuardStack.sol index 59c75892..b4f4ed2f 100644 --- a/src/state/GuardStack.sol +++ b/src/state/GuardStack.sol @@ -16,6 +16,7 @@ struct ErrorGuard { struct GuardStack { ErrorGuard[] proved; bytes32 remainingHash; + bool enabled; } library GuardStackLib { @@ -50,14 +51,22 @@ library GuardStackLib { } function hash(GuardStack memory guards) internal pure returns (bytes32 h) { + string memory prefix = "Guard stack (off):"; + if (guards.enabled) { + prefix = "Guard stack (on):"; + } h = guards.remainingHash; for (uint256 i = 0; i < guards.proved.length; i++) { - h = keccak256(abi.encodePacked("Guard stack:", hash(guards.proved[i]), h)); + h = keccak256(abi.encodePacked(prefix, hash(guards.proved[i]), h)); } } + function canPop(GuardStack memory guards) internal pure returns (bool) { + return guards.enabled && !empty(guards); + } + function empty(GuardStack memory guards) internal pure returns (bool) { - return guards.proved.length == 0 && guards.remainingHash == 0; + return guards.proved.length == 0 || guards.remainingHash == 0; } function peek(GuardStack memory guards) internal pure returns (ErrorGuard memory) { diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index 2d04dc5c..edac4c77 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -149,6 +149,7 @@ library Instructions { uint16 internal constant UNLINK_MODULE = 0x8024; uint16 internal constant PUSH_ERROR_GUARD = 0x8025; uint16 internal constant POP_ERROR_GUARD = 0x8026; + uint16 internal constant SET_ERROR_POLICY = 0x8027; uint256 internal constant INBOX_INDEX_SEQUENCER = 0; uint256 internal constant INBOX_INDEX_DELAYED = 1; From 5c022f395a924c66d086a201c14dadad6ba15d05 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 22 Nov 2023 11:25:51 -0700 Subject: [PATCH 031/126] remove side effects --- src/osp/OneStepProverHostIo.sol | 2 -- src/precompiles/ArbDebug.sol | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index 24465ded..ea027766 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -400,7 +400,6 @@ contract OneStepProverHostIo is IOneStepProver { Value memory onError = ValueLib.newPc(mach.functionPc, mach.functionIdx, mach.moduleIdx); mach.guardStack.push(GuardStackLib.newErrorGuard(frames, values, inters, onError)); mach.valueStack.push(ValueLib.newI32(1)); - mach.guardStack.enabled = true; } function executePopErrorGuard( @@ -411,7 +410,6 @@ contract OneStepProverHostIo is IOneStepProver { bytes calldata ) internal pure { mach.guardStack.pop(); - mach.guardStack.enabled = false; } function executeSetErrorPolicy( diff --git a/src/precompiles/ArbDebug.sol b/src/precompiles/ArbDebug.sol index 9924eded..01ee127a 100644 --- a/src/precompiles/ArbDebug.sol +++ b/src/precompiles/ArbDebug.sol @@ -37,6 +37,8 @@ interface ArbDebug { function customRevert(uint64 number) external pure; + function panic() external; + function legacyError() external pure; error Custom(uint64, string, bool); From dbde8c1a7834bb0bde7ed3177012a43ee0792f60 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 12 Dec 2023 20:12:52 -0700 Subject: [PATCH 032/126] fix flag hash --- src/state/GuardStack.sol | 6 +----- src/state/Machine.sol | 10 ++++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/state/GuardStack.sol b/src/state/GuardStack.sol index b4f4ed2f..3bac8a74 100644 --- a/src/state/GuardStack.sol +++ b/src/state/GuardStack.sol @@ -51,13 +51,9 @@ library GuardStackLib { } function hash(GuardStack memory guards) internal pure returns (bytes32 h) { - string memory prefix = "Guard stack (off):"; - if (guards.enabled) { - prefix = "Guard stack (on):"; - } h = guards.remainingHash; for (uint256 i = 0; i < guards.proved.length; i++) { - h = keccak256(abi.encodePacked(prefix, hash(guards.proved[i]), h)); + h = keccak256(abi.encodePacked("Guard stack:", hash(guards.proved[i]), h)); } } diff --git a/src/state/Machine.sol b/src/state/Machine.sol index d2a3b286..47fb9b57 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -49,11 +49,17 @@ library MachineLib { mach.modulesRoot ); - if (mach.guardStack.empty()) { + if (mach.guardStack.empty() && !mach.guardStack.enabled) { return keccak256(preimage); } else { + bytes1 flag = 0x00; + if (mach.guardStack.enabled) { + flag = 0x01; + } return - keccak256(abi.encodePacked(preimage, "With guards:", mach.guardStack.hash())); + keccak256( + abi.encodePacked(preimage, "With guards:", flag, mach.guardStack.hash()) + ); } } else if (mach.status == MachineStatus.FINISHED) { return keccak256(abi.encodePacked("Machine finished:", mach.globalStateHash)); From f879ffde86976ede6f1e6a470e9b2321c4c28f82 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 12 Dec 2023 21:33:49 -0700 Subject: [PATCH 033/126] fix empty conditional --- src/state/GuardStack.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/GuardStack.sol b/src/state/GuardStack.sol index 3bac8a74..d4500d73 100644 --- a/src/state/GuardStack.sol +++ b/src/state/GuardStack.sol @@ -62,7 +62,7 @@ library GuardStackLib { } function empty(GuardStack memory guards) internal pure returns (bool) { - return guards.proved.length == 0 || guards.remainingHash == 0; + return guards.proved.length == 0 && guards.remainingHash == 0; } function peek(GuardStack memory guards) internal pure returns (ErrorGuard memory) { From bb98714f2deb1ea3d20b898531fef0de28ecd622 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 13 Dec 2023 10:32:45 -0700 Subject: [PATCH 034/126] more expressive error guard deserialization --- src/state/Deserialize.sol | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 1bb1bc57..bf68b78e 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -89,6 +89,16 @@ library Deserialize { ret = bytes32(retInt); } + function boolean(bytes calldata proof, uint256 startOffset) + internal + pure + returns (bool ret, uint256 offset) + { + offset = startOffset; + ret = uint8(proof[offset]) != 0; + offset++; + } + function value(bytes calldata proof, uint256 startOffset) internal pure @@ -204,17 +214,18 @@ library Deserialize { { offset = startOffset; bytes32 remainingHash; + bool enabled; + bool empty; + (enabled, offset) = boolean(proof, offset); + (empty, offset) = boolean(proof, offset); (remainingHash, offset) = b32(proof, offset); - ErrorGuard[] memory proved; - bool enabled = uint8(proof[offset]) == 2; - if (proof[offset] != 0) { - offset++; + ErrorGuard[] memory proved; + if (empty) { + proved = new ErrorGuard[](0); + } else { proved = new ErrorGuard[](1); (proved[0], offset) = errorGuard(proof, offset); - } else { - offset++; - proved = new ErrorGuard[](0); } window = GuardStack({proved: proved, remainingHash: remainingHash, enabled: enabled}); } From 01da97808f8a5db0f6154c83a3cf8616d236a746 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 19 Dec 2023 12:09:00 -0700 Subject: [PATCH 035/126] mulmod and add benchmarks --- src/mocks/Benchmarks.sol | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/mocks/Benchmarks.sol b/src/mocks/Benchmarks.sol index 6269e240..83e9bfed 100644 --- a/src/mocks/Benchmarks.sol +++ b/src/mocks/Benchmarks.sol @@ -16,6 +16,17 @@ contract Benchmarks { } } + function fillBlockMulMod() external payable { + uint256 hash = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; + while (true) { + hash = mulmod( + hash, + 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f + ); + } + } + function fillBlockHash() external payable { bytes32 hash = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; while (true) { @@ -23,6 +34,15 @@ contract Benchmarks { } } + function fillBlockAdd() external payable { + uint256 value = 0; + while (true) { + unchecked { + value += 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; + } + } + } + function fillBlockQuickStep() external payable { uint256 value = 0; while (true) { From 662d7a1b8ec9c23b96244bb0f505bbb80ab6db80 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 19 Dec 2023 12:15:29 -0700 Subject: [PATCH 036/126] change constant --- src/mocks/Benchmarks.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mocks/Benchmarks.sol b/src/mocks/Benchmarks.sol index 83e9bfed..66d5ebba 100644 --- a/src/mocks/Benchmarks.sol +++ b/src/mocks/Benchmarks.sol @@ -17,11 +17,11 @@ contract Benchmarks { } function fillBlockMulMod() external payable { - uint256 hash = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; + uint256 value = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; while (true) { - hash = mulmod( - hash, - 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786, + value = mulmod( + value, + 0xc6178c2de1078cd36c3bd302cde755340d7f17fcb3fcc0b9c333ba03b217029f, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f ); } From b9a6b3fde25fdbd90e2672d622799485849b7c31 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 30 Dec 2023 17:37:32 -0600 Subject: [PATCH 037/126] update change date --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 8217feee..120089dc 100644 --- a/LICENSE +++ b/LICENSE @@ -30,7 +30,7 @@ Additional Use Grant: You may use the Licensed Work in a production environment -Change Date: Dec 31, 2027 +Change Date: Dec 31, 2028 Change License: Apache License Version 2.0 From a0d094672d2dca7074608279643dd8124adcf743 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 30 Dec 2023 17:46:53 -0600 Subject: [PATCH 038/126] update license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 120089dc..56b5f042 100644 --- a/LICENSE +++ b/LICENSE @@ -10,7 +10,7 @@ Parameters Licensor: Offchain Labs Licensed Work: Arbitrum Nitro Contracts - The Licensed Work is (c) 2021-2023 Offchain Labs + The Licensed Work is (c) 2021-2024 Offchain Labs Additional Use Grant: You may use the Licensed Work in a production environment solely to provide a point of interface to permit end users or applications From de1687ad9ee92cb0f31f892cfbbec5e24c417073 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Fri, 5 Jan 2024 11:11:15 -0700 Subject: [PATCH 039/126] new ArbWasm methods --- src/precompiles/ArbWasm.sol | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index d84786c8..60f9a237 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -18,22 +18,26 @@ interface ArbWasm { // @return version the stylus version function stylusVersion() external view returns (uint16 version); - // @notice gets the stylus version the program with codehash was most recently compiled against. - // @return version the program version (0 for EVM contracts) + // @notice gets the stylus version the program with codehash was most recently compiled against + // @return version the program version (reverts for EVM contracts) function codehashVersion(bytes32 codehash) external view returns (uint16 version); - // @notice gets the stylus version the program was most recently compiled against. - // @return version the program version (0 for EVM contracts) + // @notice gets the stylus version the program was most recently compiled against + // @return version the program version (reverts for EVM contracts) function programVersion(address program) external view returns (uint16 version); // @notice gets the uncompressed size of the program at the given address in bytes - // @return size the size of the program in bytes rounded up to a multiple of 512 + // @return size the size of the program in bytes rounded up to a multiple of 512 (reverts for EVM contracts) function programSize(address program) external view returns (uint32 size); // @notice gets the memory footprint of the program at the given address in pages - // @return footprint the memory footprint of program in pages + // @return footprint the memory footprint of program in pages (reverts for EVM contracts) function programMemoryFootprint(address program) external view returns (uint16 footprint); + // @notice gets the amount of time remaining until the program expires + // @return _secs the time left in seconds (reverts for EVM contracts) + function programTimeLeft(address program) external view returns (uint64 _secs); + // @notice gets the conversion rate between gas and ink // @return price the amount of ink 1 gas buys function inkPrice() external view returns (uint32 price); @@ -62,6 +66,10 @@ interface ArbWasm { // @return gas cost paid per half kb uncompressed. function callScalar() external view returns (uint16 gas); + // @notice gets the number of days after which programs deactivate + // @return _days the number of days + function expiryDays() external view returns (uint16 _days); + event ProgramActivated( bytes32 indexed codehash, bytes32 moduleHash, @@ -70,6 +78,8 @@ interface ArbWasm { ); error ProgramNotActivated(); - error ProgramOutOfDate(uint16 version); + error ProgramNeedsUpgrade(uint16 version, uint16 stylusVersion); + error ProgramExpired(uint64 ageInSeconds); error ProgramUpToDate(); + error ProgramKeepaliveTooSoon(uint64 ageInSeconds); } From f09a3600c3eac539330aa428ab1740195437c44c Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Fri, 5 Jan 2024 11:36:46 -0700 Subject: [PATCH 040/126] keepalive method --- src/precompiles/ArbWasm.sol | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 60f9a237..c0c89967 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -9,23 +9,26 @@ pragma solidity >=0.4.21 <0.9.0; * @notice Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000071. */ interface ArbWasm { - // @notice compile a wasm program - // @param program the program to compile - // @return version the stylus version the program was compiled against + // @notice activate a wasm program + // @param program the program to activate + // @return version the stylus version the program was activated against function activateProgram(address program) external returns (uint16 version); // @notice gets the latest stylus version // @return version the stylus version function stylusVersion() external view returns (uint16 version); - // @notice gets the stylus version the program with codehash was most recently compiled against + // @notice gets the stylus version the program with codehash was most recently activated against // @return version the program version (reverts for EVM contracts) function codehashVersion(bytes32 codehash) external view returns (uint16 version); - // @notice gets the stylus version the program was most recently compiled against + // @notice gets the stylus version the program was most recently activated against // @return version the program version (reverts for EVM contracts) function programVersion(address program) external view returns (uint16 version); + // @notice extends a program's lifetime (reverts if too soon) + function codehashKeepalive(bytes32 program) external; + // @notice gets the uncompressed size of the program at the given address in bytes // @return size the size of the program in bytes rounded up to a multiple of 512 (reverts for EVM contracts) function programSize(address program) external view returns (uint32 size); @@ -70,6 +73,10 @@ interface ArbWasm { // @return _days the number of days function expiryDays() external view returns (uint16 _days); + // @notice gets the age a program must be to have its expiry extended + // @return _days the number of days + function keepaliveDays() external view returns (uint16 _days); + event ProgramActivated( bytes32 indexed codehash, bytes32 moduleHash, From f2a14c607bdf635c9596cce2421bb8eb1f188411 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Fri, 5 Jan 2024 15:25:15 -0700 Subject: [PATCH 041/126] ArbOwner methods --- src/precompiles/ArbOwner.sol | 12 +++++++++--- src/precompiles/ArbWasm.sol | 8 ++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index ed3a6720..275849ab 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -103,12 +103,18 @@ interface ArbOwner { function setWasmPageRamp(uint64 ramp) external; /// @notice sets the maximum number of pages a wasm may allocate - function setWasmPageLimit(uint16 limit) external view; + function setWasmPageLimit(uint16 limit) external; /// @notice sets the added wasm call cost based on binary size - function setWasmCallScalar(uint16 gas) external view; + function setWasmCallScalar(uint16 gas) external; - //// @notice Sets serialized chain config in ArbOS state + /// @notice sets the number of days after which programs deactivate + function setWasmExpiryDays(uint16 _days) external; + + /// @notice sets the age a program must be to perform a keepalive + function setWasmKeepaliveDays(uint16 _days) external; + + /// @notice Sets serialized chain config in ArbOS state function setChainConfig(string calldata chainConfig) external; /// Emitted when a successful call is made to this precompile diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index c0c89967..a30f3483 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -22,13 +22,13 @@ interface ArbWasm { // @return version the program version (reverts for EVM contracts) function codehashVersion(bytes32 codehash) external view returns (uint16 version); + // @notice extends a program's expiration date (reverts if too soon) + function codehashKeepalive(bytes32 codehash) external; + // @notice gets the stylus version the program was most recently activated against // @return version the program version (reverts for EVM contracts) function programVersion(address program) external view returns (uint16 version); - // @notice extends a program's lifetime (reverts if too soon) - function codehashKeepalive(bytes32 program) external; - // @notice gets the uncompressed size of the program at the given address in bytes // @return size the size of the program in bytes rounded up to a multiple of 512 (reverts for EVM contracts) function programSize(address program) external view returns (uint32 size); @@ -73,7 +73,7 @@ interface ArbWasm { // @return _days the number of days function expiryDays() external view returns (uint16 _days); - // @notice gets the age a program must be to have its expiry extended + // @notice gets the age a program must be to perform a keepalive // @return _days the number of days function keepaliveDays() external view returns (uint16 _days); From a9529179e455e1ce6527680e564d77e900ea6d73 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 8 Jan 2024 17:06:44 -0700 Subject: [PATCH 042/126] docstrings & errors --- src/precompiles/ArbOwner.sol | 2 +- src/precompiles/ArbWasm.sol | 76 ++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 275849ab..7330847b 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -1,4 +1,4 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE // SPDX-License-Identifier: BUSL-1.1 diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index a30f3483..402b3d34 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -1,4 +1,4 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 @@ -9,72 +9,73 @@ pragma solidity >=0.4.21 <0.9.0; * @notice Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000071. */ interface ArbWasm { - // @notice activate a wasm program - // @param program the program to activate - // @return version the stylus version the program was activated against - function activateProgram(address program) external returns (uint16 version); + /// @notice activate a wasm program + /// @param program the program to activate + /// @return version the stylus version the program was activated against + function activateProgram(address program) external payable returns (uint16 version); - // @notice gets the latest stylus version - // @return version the stylus version + /// @notice gets the latest stylus version + /// @return version the stylus version function stylusVersion() external view returns (uint16 version); - // @notice gets the stylus version the program with codehash was most recently activated against - // @return version the program version (reverts for EVM contracts) + /// @notice gets the stylus version the program with codehash was most recently activated against + /// @return version the program version (reverts for EVM contracts) function codehashVersion(bytes32 codehash) external view returns (uint16 version); - // @notice extends a program's expiration date (reverts if too soon) - function codehashKeepalive(bytes32 codehash) external; + /// @notice extends a program's expiration date. + /// Reverts if too soon or if the program is not up to date. + function codehashKeepalive(bytes32 codehash) external payable; - // @notice gets the stylus version the program was most recently activated against - // @return version the program version (reverts for EVM contracts) + /// @notice gets the stylus version the program was most recently activated against + /// @return version the program version (reverts for EVM contracts) function programVersion(address program) external view returns (uint16 version); - // @notice gets the uncompressed size of the program at the given address in bytes - // @return size the size of the program in bytes rounded up to a multiple of 512 (reverts for EVM contracts) + /// @notice gets the uncompressed size of the program at the given address in bytes + /// @return size the size of the program in bytes rounded up to a multiple of 512 (reverts for EVM contracts) function programSize(address program) external view returns (uint32 size); - // @notice gets the memory footprint of the program at the given address in pages - // @return footprint the memory footprint of program in pages (reverts for EVM contracts) + /// @notice gets the memory footprint of the program at the given address in pages + /// @return footprint the memory footprint of program in pages (reverts for EVM contracts) function programMemoryFootprint(address program) external view returns (uint16 footprint); - // @notice gets the amount of time remaining until the program expires - // @return _secs the time left in seconds (reverts for EVM contracts) + /// @notice gets the amount of time remaining until the program expires + /// @return _secs the time left in seconds (reverts for EVM contracts) function programTimeLeft(address program) external view returns (uint64 _secs); - // @notice gets the conversion rate between gas and ink - // @return price the amount of ink 1 gas buys + /// @notice gets the conversion rate between gas and ink + /// @return price the amount of ink 1 gas buys function inkPrice() external view returns (uint32 price); - // @notice gets the wasm stack size limit - // @return depth the maximum depth (in wasm words) a wasm stack may grow + /// @notice gets the wasm stack size limit + /// @return depth the maximum depth (in wasm words) a wasm stack may grow function maxStackDepth() external view returns (uint32 depth); - // @notice gets the number of free wasm pages a program gets - // @return pages the number of wasm pages (2^16 bytes) + /// @notice gets the number of free wasm pages a program gets + /// @return pages the number of wasm pages (2^16 bytes) function freePages() external view returns (uint16 pages); - // @notice gets the base cost of each additional wasm page (2^16 bytes) - // @return gas base amount of gas needed to grow another wasm page + /// @notice gets the base cost of each additional wasm page (2^16 bytes) + /// @return gas base amount of gas needed to grow another wasm page function pageGas() external view returns (uint16 gas); - // @notice gets the ramp that drives exponential memory costs - // @return ramp bits representing the floating point value + /// @notice gets the ramp that drives exponential memory costs + /// @return ramp bits representing the floating point value function pageRamp() external view returns (uint64 ramp); - // @notice gets the maximum number of pages a wasm may allocate - // @return limit the number of pages + /// @notice gets the maximum number of pages a wasm may allocate + /// @return limit the number of pages function pageLimit() external view returns (uint16 limit); - // @notice gets the added wasm call cost based on binary size - // @return gas cost paid per half kb uncompressed. + /// @notice gets the added wasm call cost based on binary size + /// @return gas cost paid per half kb uncompressed. function callScalar() external view returns (uint16 gas); - // @notice gets the number of days after which programs deactivate - // @return _days the number of days + /// @notice gets the number of days after which programs deactivate + /// @return _days the number of days function expiryDays() external view returns (uint16 _days); - // @notice gets the age a program must be to perform a keepalive - // @return _days the number of days + /// @notice gets the age a program must be to perform a keepalive + /// @return _days the number of days function keepaliveDays() external view returns (uint16 _days); event ProgramActivated( @@ -89,4 +90,5 @@ interface ArbWasm { error ProgramExpired(uint64 ageInSeconds); error ProgramUpToDate(); error ProgramKeepaliveTooSoon(uint64 ageInSeconds); + error ProgramInsufficientValue(uint256 have, uint256 want); } From 56e15c4969879f79b39dae81aaf550bcb1f32801 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 9 Jan 2024 10:31:18 -0700 Subject: [PATCH 043/126] initGas --- src/precompiles/ArbOwner.sol | 4 ++-- src/precompiles/ArbWasm.sol | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 7330847b..67cdc163 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -105,8 +105,8 @@ interface ArbOwner { /// @notice sets the maximum number of pages a wasm may allocate function setWasmPageLimit(uint16 limit) external; - /// @notice sets the added wasm call cost based on binary size - function setWasmCallScalar(uint16 gas) external; + /// @notice sets the minimum cost to invoke a program + function setWasmMinInitGas(uint16 gas) external; /// @notice sets the number of days after which programs deactivate function setWasmExpiryDays(uint16 _days) external; diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 402b3d34..8463216f 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -30,9 +30,9 @@ interface ArbWasm { /// @return version the program version (reverts for EVM contracts) function programVersion(address program) external view returns (uint16 version); - /// @notice gets the uncompressed size of the program at the given address in bytes - /// @return size the size of the program in bytes rounded up to a multiple of 512 (reverts for EVM contracts) - function programSize(address program) external view returns (uint32 size); + /// @notice gets the cost to invoke the program (not including minInitGas) + /// @return gas the amount of gas + function programInitGas(address program) external view returns (uint32 gas); /// @notice gets the memory footprint of the program at the given address in pages /// @return footprint the memory footprint of program in pages (reverts for EVM contracts) @@ -66,9 +66,9 @@ interface ArbWasm { /// @return limit the number of pages function pageLimit() external view returns (uint16 limit); - /// @notice gets the added wasm call cost based on binary size - /// @return gas cost paid per half kb uncompressed. - function callScalar() external view returns (uint16 gas); + /// @notice gets the minimum cost to invoke a program + /// @return gas amount of gas + function minInitGas() external view returns (uint16 gas); /// @notice gets the number of days after which programs deactivate /// @return _days the number of days From 4f6aeb81ff5fc97959ad3547357c5032b217a973 Mon Sep 17 00:00:00 2001 From: Michael Benfield Date: Mon, 8 Jan 2024 22:53:42 -0800 Subject: [PATCH 044/126] account_code checks --- src/mocks/Program.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/mocks/Program.sol b/src/mocks/Program.sol index c71410ea..9d65d183 100644 --- a/src/mocks/Program.sol +++ b/src/mocks/Program.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "../precompiles/ArbSys.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; contract ProgramTest { event Hash(bytes32 result); @@ -70,6 +71,17 @@ contract ProgramTest { result = assert256(result, "rust codehash", uint256(program.codehash)); result = assert256(result, "arb codehash ", uint256(arbPrecompile.codehash)); result = assert256(result, "eth codehash ", uint256(ethPrecompile.codehash)); + bytes memory code = new bytes(program.code.length); + for (uint256 i = 0; i < program.code.length; i++) { + code[i] = result[i]; + } + require(keccak256(code) == keccak256(program.code), "code"); + bytes memory rest = new bytes(result.length - program.code.length); + for (uint256 i = program.code.length; i < result.length; i++) { + rest[i - program.code.length] = result[i]; + } + + result = rest; return result; } From 43bf9a834eacf7ce8b1b833dfd29cc08d704d3f0 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 22 Jan 2024 16:02:53 -0700 Subject: [PATCH 045/126] add datafee --- src/precompiles/ArbWasm.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 8463216f..a1bc99e6 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -12,7 +12,10 @@ interface ArbWasm { /// @notice activate a wasm program /// @param program the program to activate /// @return version the stylus version the program was activated against - function activateProgram(address program) external payable returns (uint16 version); + function activateProgram(address program) + external + payable + returns (uint16 version, uint256 dataFee); /// @notice gets the latest stylus version /// @return version the stylus version @@ -82,6 +85,7 @@ interface ArbWasm { bytes32 indexed codehash, bytes32 moduleHash, address program, + uint256 dataFee, uint16 version ); From c47d917d78bfe66c2dc924c0e8d890939facb798 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 22 Jan 2024 16:07:07 -0700 Subject: [PATCH 046/126] add comment --- src/precompiles/ArbWasm.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index a1bc99e6..098ba9e8 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -12,6 +12,7 @@ interface ArbWasm { /// @notice activate a wasm program /// @param program the program to activate /// @return version the stylus version the program was activated against + /// @return dataFee the data fee paid to store the activated program function activateProgram(address program) external payable From 2ba63156379a1cf5f7d3d8e0a0b99d474e6b228f Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 22 Jan 2024 20:42:32 -0700 Subject: [PATCH 047/126] add keepalive event --- src/precompiles/ArbWasm.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 098ba9e8..17703db9 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -89,6 +89,7 @@ interface ArbWasm { uint256 dataFee, uint16 version ); + event ProgramKeepalive(bytes32 indexed codehash, uint256 dataFee); error ProgramNotActivated(); error ProgramNeedsUpgrade(uint16 version, uint16 stylusVersion); From b454b21a1b18a9e676802e24bf172f5b59a08cfe Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 22 Jan 2024 20:44:19 -0700 Subject: [PATCH 048/126] rename programLifetimeExtended --- src/precompiles/ArbWasm.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 17703db9..38972bfa 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -89,7 +89,7 @@ interface ArbWasm { uint256 dataFee, uint16 version ); - event ProgramKeepalive(bytes32 indexed codehash, uint256 dataFee); + event ProgramLifetimeExtended(bytes32 indexed codehash, uint256 dataFee); error ProgramNotActivated(); error ProgramNeedsUpgrade(uint16 version, uint16 stylusVersion); From 900e09455c6f14e488aaee41ff31c677c2d7fd33 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 30 Jan 2024 00:36:56 -0700 Subject: [PATCH 049/126] improve spacing --- src/mocks/Program.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mocks/Program.sol b/src/mocks/Program.sol index 9d65d183..2ff15f43 100644 --- a/src/mocks/Program.sol +++ b/src/mocks/Program.sol @@ -71,6 +71,7 @@ contract ProgramTest { result = assert256(result, "rust codehash", uint256(program.codehash)); result = assert256(result, "arb codehash ", uint256(arbPrecompile.codehash)); result = assert256(result, "eth codehash ", uint256(ethPrecompile.codehash)); + bytes memory code = new bytes(program.code.length); for (uint256 i = 0; i < program.code.length; i++) { code[i] = result[i]; @@ -82,7 +83,6 @@ contract ProgramTest { } result = rest; - return result; } From 41b3de944171da92636ca76e26abb055ace6af0c Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 29 Jan 2024 16:46:14 -0700 Subject: [PATCH 050/126] machine: support multistack in hashes and proofs --- src/challenge/ChallengeLib.sol | 6 +- src/state/Deserialize.sol | 113 +++++++++++++++++++-------------- src/state/Machine.sol | 11 +++- src/state/MultiStack.sol | 20 ++++++ 4 files changed, 101 insertions(+), 49 deletions(-) create mode 100644 src/state/MultiStack.sol diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index eb1f1bd4..e4781970 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -63,18 +63,22 @@ library ChallengeLib { ValueStack memory internalStack; StackFrameWindow memory frameStack; GuardStack memory guardStack; + MultiStack memory emptyMultiStack; Machine memory mach = Machine({ status: MachineStatus.RUNNING, valueStack: values, + valueMultiStack:emptyMultiStack, internalStack: internalStack, frameStack: frameStack, + frameMultiStack: emptyMultiStack, guardStack: guardStack, globalStateHash: globalStateHash, moduleIdx: 0, functionIdx: 0, functionPc: 0, - modulesRoot: wasmModuleRoot + modulesRoot: wasmModuleRoot, + cothread: false }); return mach.hash(); } diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index bf68b78e..2afcbd4e 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.0; import "./Value.sol"; import "./ValueStack.sol"; import "./Machine.sol"; +import "./MultiStack.sol"; import "./Instructions.sol"; import "./StackFrame.sol"; import "./GuardStack.sol"; @@ -130,6 +131,19 @@ library Deserialize { stack = ValueStack({proved: ValueArray(proved), remainingHash: remainingHash}); } + function multiStack(bytes calldata proof, uint256 startOffset) + internal + pure + returns (MultiStack memory multistack, uint256 offset) + { + offset = startOffset; + bytes32 inactiveStackHash; + (inactiveStackHash, offset) = b32(proof, offset); + bytes32 remainingHash; + (remainingHash, offset) = b32(proof, offset); + multistack = MultiStack({inactiveStackHash: inactiveStackHash, remainingHash: remainingHash}); + } + function instruction(bytes calldata proof, uint256 startOffset) internal pure @@ -193,15 +207,15 @@ library Deserialize { offset = startOffset; Value memory onErrorPc; bytes32 frameStack; - bytes32 valueStack; + bytes32 valueStackHash; bytes32 interStack; (frameStack, offset) = b32(proof, offset); - (valueStack, offset) = b32(proof, offset); + (valueStackHash, offset) = b32(proof, offset); (interStack, offset) = b32(proof, offset); (onErrorPc, offset) = value(proof, offset); guard = ErrorGuard({ frameStack: frameStack, - valueStack: valueStack, + valueStack: valueStackHash, interStack: interStack, onErrorPc: onErrorPc }); @@ -296,52 +310,59 @@ library Deserialize { returns (Machine memory mach, uint256 offset) { offset = startOffset; - MachineStatus status; { - uint8 statusU8; - (statusU8, offset) = u8(proof, offset); - if (statusU8 == 0) { - status = MachineStatus.RUNNING; - } else if (statusU8 == 1) { - status = MachineStatus.FINISHED; - } else if (statusU8 == 2) { - status = MachineStatus.ERRORED; - } else if (statusU8 == 3) { - status = MachineStatus.TOO_FAR; - } else { - revert("UNKNOWN_MACH_STATUS"); + MachineStatus status; + { + uint8 statusU8; + (statusU8, offset) = u8(proof, offset); + if (statusU8 == 0) { + status = MachineStatus.RUNNING; + } else if (statusU8 == 1) { + status = MachineStatus.FINISHED; + } else if (statusU8 == 2) { + status = MachineStatus.ERRORED; + } else if (statusU8 == 3) { + status = MachineStatus.TOO_FAR; + } else { + revert("UNKNOWN_MACH_STATUS"); + } } + ValueStack memory values; + ValueStack memory internalStack; + MultiStack memory valuesMulti; + StackFrameWindow memory frameStack; + MultiStack memory framesMulti; + GuardStack memory guards; + (values, offset) = valueStack(proof, offset); + (valuesMulti, offset) = multiStack(proof, offset); + (internalStack, offset) = valueStack(proof, offset); + (frameStack, offset) = stackFrameWindow(proof, offset); + (framesMulti, offset) = multiStack(proof, offset); + (guards, offset) = guardStack(proof, offset); + mach = Machine({ + status: status, + valueStack: values, + valueMultiStack: valuesMulti, + internalStack: internalStack, + frameStack: frameStack, + frameMultiStack: framesMulti, + guardStack: guards, + // below this lines vars are initialized later in the function, + // due to solidity's bounds on number of stack variables + globalStateHash: bytes32(0), + moduleIdx: 0, + functionIdx: 0, + functionPc: 0, + modulesRoot: bytes32(0), + cothread: false + }); } - ValueStack memory values; - ValueStack memory internalStack; - bytes32 globalStateHash; - uint32 moduleIdx; - uint32 functionIdx; - uint32 functionPc; - StackFrameWindow memory frameStack; - GuardStack memory guards; - bytes32 modulesRoot; - (values, offset) = valueStack(proof, offset); - (internalStack, offset) = valueStack(proof, offset); - (frameStack, offset) = stackFrameWindow(proof, offset); - (guards, offset) = guardStack(proof, offset); - (globalStateHash, offset) = b32(proof, offset); - (moduleIdx, offset) = u32(proof, offset); - (functionIdx, offset) = u32(proof, offset); - (functionPc, offset) = u32(proof, offset); - (modulesRoot, offset) = b32(proof, offset); - mach = Machine({ - status: status, - valueStack: values, - internalStack: internalStack, - frameStack: frameStack, - guardStack: guards, - globalStateHash: globalStateHash, - moduleIdx: moduleIdx, - functionIdx: functionIdx, - functionPc: functionPc, - modulesRoot: modulesRoot - }); + (mach.globalStateHash, offset) = b32(proof, offset); + (mach.moduleIdx, offset) = u32(proof, offset); + (mach.functionIdx, offset) = u32(proof, offset); + (mach.functionPc, offset) = u32(proof, offset); + (mach.modulesRoot, offset) = b32(proof, offset); + (mach.cothread, offset) = boolean(proof, offset); } function merkleProof(bytes calldata proof, uint256 startOffset) diff --git a/src/state/Machine.sol b/src/state/Machine.sol index 47fb9b57..8458f694 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.0; import "./ValueStack.sol"; import "./Instructions.sol"; +import "./MultiStack.sol"; import "./StackFrame.sol"; import "./GuardStack.sol"; @@ -19,29 +20,35 @@ enum MachineStatus { struct Machine { MachineStatus status; ValueStack valueStack; + MultiStack valueMultiStack; ValueStack internalStack; StackFrameWindow frameStack; + MultiStack frameMultiStack; GuardStack guardStack; bytes32 globalStateHash; uint32 moduleIdx; uint32 functionIdx; uint32 functionPc; bytes32 modulesRoot; + bool cothread; } library MachineLib { using StackFrameLib for StackFrameWindow; using GuardStackLib for GuardStack; using ValueStackLib for ValueStack; + using MultiStackLib for MultiStack; function hash(Machine memory mach) internal pure returns (bytes32) { // Warning: the non-running hashes are replicated in Challenge if (mach.status == MachineStatus.RUNNING) { + bytes32 valueMultiHash = mach.valueMultiStack.hash(mach.valueStack.hash(), mach.cothread); + bytes32 frameMultiHash = mach.frameMultiStack.hash(mach.frameStack.hash(), mach.cothread); bytes memory preimage = abi.encodePacked( "Machine running:", - mach.valueStack.hash(), + valueMultiHash, mach.internalStack.hash(), - mach.frameStack.hash(), + frameMultiHash, mach.globalStateHash, mach.moduleIdx, mach.functionIdx, diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol new file mode 100644 index 00000000..1f6efe56 --- /dev/null +++ b/src/state/MultiStack.sol @@ -0,0 +1,20 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.0; + +struct MultiStack { + bytes32 inactiveStackHash; + bytes32 remainingHash; +} + +library MultiStackLib { + function hash(MultiStack memory multi, bytes32 activeStackHash, bool cothread) internal pure returns (bytes32 h) { + if (cothread) { + return keccak256(abi.encodePacked("Multistack:", multi.inactiveStackHash, activeStackHash, multi.remainingHash)); + } else { + return keccak256(abi.encodePacked("Multistack:", activeStackHash, multi.inactiveStackHash, multi.remainingHash)); + } + } +} From 95547405eb41647a7f79e77d7e7be821883ce61f Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 29 Jan 2024 17:40:37 -0700 Subject: [PATCH 051/126] remove error guard's enable replaced by machine.cothread --- src/osp/OneStepProofEntry.sol | 7 +++++-- src/osp/OneStepProverHostIo.sol | 13 ------------- src/state/Deserialize.sol | 4 +--- src/state/GuardStack.sol | 5 ----- src/state/Instructions.sol | 1 - src/state/Machine.sol | 8 ++------ 6 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index b22d43e7..d736edfd 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -117,7 +117,7 @@ contract OneStepProofEntry is IOneStepProofEntry { } else if ( (opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 && opcode <= Instructions.SET_GLOBAL_STATE_U64) || - (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.SET_ERROR_POLICY) + (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) ) { prover = proverHostIo; } else { @@ -132,8 +132,11 @@ contract OneStepProofEntry is IOneStepProofEntry { mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod); } - if (mach.status == MachineStatus.ERRORED && mach.guardStack.canPop()) { + if (mach.status == MachineStatus.ERRORED && mach.cothread && !mach.guardStack.empty()) { ErrorGuard memory guard = mach.guardStack.pop(); + mach.cothread = false; + mach.frameMultiStack.inactiveStackHash = mach.frameStack.hash(); + mach.valueMultiStack.inactiveStackHash = mach.valueStack.hash(); mach.frameStack.overwrite(guard.frameStack); mach.valueStack.overwrite(guard.valueStack); mach.internalStack.overwrite(guard.interStack); diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index ea027766..b8158978 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -412,17 +412,6 @@ contract OneStepProverHostIo is IOneStepProver { mach.guardStack.pop(); } - function executeSetErrorPolicy( - ExecutionContext calldata, - Machine memory mach, - Module memory, - Instruction calldata inst, - bytes calldata - ) internal pure { - uint32 status = mach.valueStack.pop().assumeI32(); - mach.guardStack.enabled = status != 0; - } - function executeGlobalStateAccess( ExecutionContext calldata, Machine memory mach, @@ -492,8 +481,6 @@ contract OneStepProverHostIo is IOneStepProver { impl = executePushErrorGuard; } else if (opcode == Instructions.POP_ERROR_GUARD) { impl = executePopErrorGuard; - } else if (opcode == Instructions.SET_ERROR_POLICY) { - impl = executeSetErrorPolicy; } else { revert("INVALID_MEMORY_OPCODE"); } diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 2afcbd4e..8938b7c6 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -228,9 +228,7 @@ library Deserialize { { offset = startOffset; bytes32 remainingHash; - bool enabled; bool empty; - (enabled, offset) = boolean(proof, offset); (empty, offset) = boolean(proof, offset); (remainingHash, offset) = b32(proof, offset); @@ -241,7 +239,7 @@ library Deserialize { proved = new ErrorGuard[](1); (proved[0], offset) = errorGuard(proof, offset); } - window = GuardStack({proved: proved, remainingHash: remainingHash, enabled: enabled}); + window = GuardStack({proved: proved, remainingHash: remainingHash}); } function moduleMemory(bytes calldata proof, uint256 startOffset) diff --git a/src/state/GuardStack.sol b/src/state/GuardStack.sol index d4500d73..59c75892 100644 --- a/src/state/GuardStack.sol +++ b/src/state/GuardStack.sol @@ -16,7 +16,6 @@ struct ErrorGuard { struct GuardStack { ErrorGuard[] proved; bytes32 remainingHash; - bool enabled; } library GuardStackLib { @@ -57,10 +56,6 @@ library GuardStackLib { } } - function canPop(GuardStack memory guards) internal pure returns (bool) { - return guards.enabled && !empty(guards); - } - function empty(GuardStack memory guards) internal pure returns (bool) { return guards.proved.length == 0 && guards.remainingHash == 0; } diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index edac4c77..2d04dc5c 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -149,7 +149,6 @@ library Instructions { uint16 internal constant UNLINK_MODULE = 0x8024; uint16 internal constant PUSH_ERROR_GUARD = 0x8025; uint16 internal constant POP_ERROR_GUARD = 0x8026; - uint16 internal constant SET_ERROR_POLICY = 0x8027; uint256 internal constant INBOX_INDEX_SEQUENCER = 0; uint256 internal constant INBOX_INDEX_DELAYED = 1; diff --git a/src/state/Machine.sol b/src/state/Machine.sol index 8458f694..8f81f8c8 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -56,16 +56,12 @@ library MachineLib { mach.modulesRoot ); - if (mach.guardStack.empty() && !mach.guardStack.enabled) { + if (mach.guardStack.empty() && !mach.cothread) { return keccak256(preimage); } else { - bytes1 flag = 0x00; - if (mach.guardStack.enabled) { - flag = 0x01; - } return keccak256( - abi.encodePacked(preimage, "With guards:", flag, mach.guardStack.hash()) + abi.encodePacked(preimage, "With guards:", mach.guardStack.hash()) ); } } else if (mach.status == MachineStatus.FINISHED) { From b0a14ee3b49d213ea490b30d57c7d275a7f4372e Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 29 Jan 2024 19:40:02 -0700 Subject: [PATCH 052/126] fix introducing cothreads --- src/challenge/ChallengeLib.sol | 5 +++- src/state/Deserialize.sol | 19 +++++++-------- src/state/Machine.sol | 28 +++++++++++++++++++---- src/state/MultiStack.sol | 42 +++++++++++++++++++++++++++++++--- 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index e4781970..0bdc3504 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -5,11 +5,13 @@ pragma solidity ^0.8.0; import "../state/Machine.sol"; +import "../state/MultiStack.sol"; import "../state/GlobalState.sol"; library ChallengeLib { using MachineLib for Machine; using ChallengeLib for Challenge; + using MultiStackLib for MultiStack; /// @dev It's assumed that that uninitialzed challenges have mode NONE enum ChallengeMode { @@ -64,11 +66,12 @@ library ChallengeLib { StackFrameWindow memory frameStack; GuardStack memory guardStack; MultiStack memory emptyMultiStack; + emptyMultiStack.setEmpty(); Machine memory mach = Machine({ status: MachineStatus.RUNNING, valueStack: values, - valueMultiStack:emptyMultiStack, + valueMultiStack: emptyMultiStack, internalStack: internalStack, frameStack: frameStack, frameMultiStack: emptyMultiStack, diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 8938b7c6..08680f73 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -141,7 +141,10 @@ library Deserialize { (inactiveStackHash, offset) = b32(proof, offset); bytes32 remainingHash; (remainingHash, offset) = b32(proof, offset); - multistack = MultiStack({inactiveStackHash: inactiveStackHash, remainingHash: remainingHash}); + multistack = MultiStack({ + inactiveStackHash: inactiveStackHash, + remainingHash: remainingHash + }); } function instruction(bytes calldata proof, uint256 startOffset) @@ -345,14 +348,12 @@ library Deserialize { frameStack: frameStack, frameMultiStack: framesMulti, guardStack: guards, - // below this lines vars are initialized later in the function, - // due to solidity's bounds on number of stack variables - globalStateHash: bytes32(0), - moduleIdx: 0, - functionIdx: 0, - functionPc: 0, - modulesRoot: bytes32(0), - cothread: false + globalStateHash: bytes32(0), // filled later + moduleIdx: 0, // filled later + functionIdx: 0, // filled later + functionPc: 0, // filled later + modulesRoot: bytes32(0), // filled later + cothread: false // filled later }); } (mach.globalStateHash, offset) = b32(proof, offset); diff --git a/src/state/Machine.sol b/src/state/Machine.sol index 8f81f8c8..c18f1710 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -42,8 +42,14 @@ library MachineLib { function hash(Machine memory mach) internal pure returns (bytes32) { // Warning: the non-running hashes are replicated in Challenge if (mach.status == MachineStatus.RUNNING) { - bytes32 valueMultiHash = mach.valueMultiStack.hash(mach.valueStack.hash(), mach.cothread); - bytes32 frameMultiHash = mach.frameMultiStack.hash(mach.frameStack.hash(), mach.cothread); + bytes32 valueMultiHash = mach.valueMultiStack.hash( + mach.valueStack.hash(), + mach.cothread + ); + bytes32 frameMultiHash = mach.frameMultiStack.hash( + mach.frameStack.hash(), + mach.cothread + ); bytes memory preimage = abi.encodePacked( "Machine running:", valueMultiHash, @@ -60,9 +66,7 @@ library MachineLib { return keccak256(preimage); } else { return - keccak256( - abi.encodePacked(preimage, "With guards:", mach.guardStack.hash()) - ); + keccak256(abi.encodePacked(preimage, "With guards:", mach.guardStack.hash())); } } else if (mach.status == MachineStatus.FINISHED) { return keccak256(abi.encodePacked("Machine finished:", mach.globalStateHash)); @@ -75,6 +79,20 @@ library MachineLib { } } + function switchCoThread(Machine memory mach) internal pure { + bytes32 newActiveValue = mach.valueMultiStack.inactiveStackHash; + bytes32 newActiveFrame = mach.frameMultiStack.inactiveStackHash; + if (newActiveFrame == bytes32(0) || newActiveValue == bytes32(0)) { + mach.status = MachineStatus.ERRORED; + return; + } + mach.cothread = !mach.cothread; + mach.frameMultiStack.inactiveStackHash = mach.frameStack.hash(); + mach.valueMultiStack.inactiveStackHash = mach.valueStack.hash(); + mach.frameStack.overwrite(newActiveValue); + mach.valueStack.overwrite(newActiveFrame); + } + function setPc(Machine memory mach, Value memory pc) internal pure { if (pc.valueType == ValueType.REF_NULL) { mach.status = MachineStatus.ERRORED; diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol index 1f6efe56..cb539891 100644 --- a/src/state/MultiStack.sol +++ b/src/state/MultiStack.sol @@ -10,11 +10,47 @@ struct MultiStack { } library MultiStackLib { - function hash(MultiStack memory multi, bytes32 activeStackHash, bool cothread) internal pure returns (bytes32 h) { + bytes32 internal constant NO_STACK_HASH = ~bytes32(0); + + function hash( + MultiStack memory multi, + bytes32 activeStackHash, + bool cothread + ) internal pure returns (bytes32 h) { if (cothread) { - return keccak256(abi.encodePacked("Multistack:", multi.inactiveStackHash, activeStackHash, multi.remainingHash)); + return + keccak256( + abi.encodePacked( + "multistack: ", + multi.inactiveStackHash, + activeStackHash, + multi.remainingHash + ) + ); } else { - return keccak256(abi.encodePacked("Multistack:", activeStackHash, multi.inactiveStackHash, multi.remainingHash)); + return + keccak256( + abi.encodePacked( + "multistack: ", + activeStackHash, + multi.inactiveStackHash, + multi.remainingHash + ) + ); + } + } + + function setEmpty(MultiStack memory multi) internal pure { + multi.inactiveStackHash = NO_STACK_HASH; + multi.remainingHash = NO_STACK_HASH; + } + + function pushNew(MultiStack memory multi) internal pure { + if (multi.inactiveStackHash != NO_STACK_HASH) { + multi.remainingHash = keccak256( + abi.encodePacked("cothread: ", multi.inactiveStackHash, multi.remainingHash) + ); } + multi.inactiveStackHash = 0; } } From ee178c851ec3fa741b5d3f12cb2f98ee0e9e68d1 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 29 Jan 2024 19:40:25 -0700 Subject: [PATCH 053/126] add cothread onestepproofs --- src/osp/OneStepProofEntry.sol | 11 ++--- src/osp/OneStepProverHostIo.sol | 81 ++++++++++++++++++++++++++++++++- src/state/Instructions.sol | 4 ++ 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index d736edfd..19076038 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -117,7 +117,8 @@ contract OneStepProofEntry is IOneStepProofEntry { } else if ( (opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 && opcode <= Instructions.SET_GLOBAL_STATE_U64) || - (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) + (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) || + (opcode >= Instructions.NEW_COTHREAD && opcode <= Instructions.SWITCH_COTHREAD) ) { prover = proverHostIo; } else { @@ -134,11 +135,9 @@ contract OneStepProofEntry is IOneStepProofEntry { if (mach.status == MachineStatus.ERRORED && mach.cothread && !mach.guardStack.empty()) { ErrorGuard memory guard = mach.guardStack.pop(); - mach.cothread = false; - mach.frameMultiStack.inactiveStackHash = mach.frameStack.hash(); - mach.valueMultiStack.inactiveStackHash = mach.valueStack.hash(); - mach.frameStack.overwrite(guard.frameStack); - mach.valueStack.overwrite(guard.valueStack); + mach.frameMultiStack.inactiveStackHash = guard.frameStack; + mach.valueMultiStack.inactiveStackHash = guard.valueStack; + mach.switchCoThread(); mach.internalStack.overwrite(guard.interStack); mach.setPc(guard.onErrorPc); diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index b8158978..c9c8cc1c 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.0; import "../state/Value.sol"; import "../state/Machine.sol"; import "../state/MerkleProof.sol"; +import "../state/MultiStack.sol"; import "../state/Deserialize.sol"; import "../state/ModuleMemory.sol"; import "./IOneStepProver.sol"; @@ -15,8 +16,10 @@ import "../bridge/IBridge.sol"; contract OneStepProverHostIo is IOneStepProver { using GlobalStateLib for GlobalState; + using MachineLib for Machine; using MerkleProofLib for MerkleProof; using ModuleMemoryLib for ModuleMemory; + using MultiStackLib for MultiStack; using ValueLib for Value; using ValueStackLib for ValueStack; using StackFrameLib for StackFrameWindow; @@ -442,6 +445,80 @@ contract OneStepProverHostIo is IOneStepProver { mach.globalStateHash = state.hash(); } + function executeNewCoThread( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata, + bytes calldata + ) internal pure { + if (mach.cothread) { + // cannot create new cothread from inside cothread + mach.status = MachineStatus.ERRORED; + return; + } + mach.frameMultiStack.pushNew(); + } + + function proovePopCothread(MultiStack memory multi, bytes calldata proof) internal pure { + uint256 proofOffset = 0; + bytes32 newInactiveCoThread; + bytes32 newRemaining; + (newInactiveCoThread, proofOffset) = Deserialize.b32(proof, proofOffset); + (newRemaining, proofOffset) = Deserialize.b32(proof, proofOffset); + if (newInactiveCoThread == MultiStackLib.NO_STACK_HASH) { + require(multi.remainingHash == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); + return; + } + require( + keccak256(abi.encodePacked("cothread: ", newInactiveCoThread, newRemaining)) == + multi.remainingHash, + "WRONG_COTHREAD_POP" + ); + multi.remainingHash = newRemaining; + multi.inactiveStackHash = newInactiveCoThread; + return; + } + + function executePopCoThread( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata, + bytes calldata proof + ) internal pure { + if (mach.cothread) { + // cannot pop cothread from inside cothread + mach.status = MachineStatus.ERRORED; + return; + } + if (mach.frameMultiStack.inactiveStackHash == MultiStackLib.NO_STACK_HASH) { + // cannot pop cothread if there isn't one + mach.status = MachineStatus.ERRORED; + return; + } + proovePopCothread(mach.valueMultiStack, proof); + proovePopCothread(mach.frameMultiStack, proof[64:]); + } + + function executeSwitchCoThread( + ExecutionContext calldata, + Machine memory mach, + Module memory, + Instruction calldata inst, + bytes calldata + ) internal pure { + if (mach.frameMultiStack.inactiveStackHash == MultiStackLib.NO_STACK_HASH) { + // cannot switch cothread if there isn't one + mach.status = MachineStatus.ERRORED; + return; + } + bool jumpToCoThread = (inst.argumentData != 0); + if (jumpToCoThread != mach.cothread) { + mach.switchCoThread(); + } + } + function executeOneStep( ExecutionContext calldata execCtx, Machine calldata startMach, @@ -481,7 +558,9 @@ contract OneStepProverHostIo is IOneStepProver { impl = executePushErrorGuard; } else if (opcode == Instructions.POP_ERROR_GUARD) { impl = executePopErrorGuard; - } else { + } else if (opcode == Instructions.NEW_COTHREAD) {} else if ( + opcode == Instructions.POP_COTHREAD + ) {} else if (opcode == Instructions.SWITCH_COTHREAD) {} else { revert("INVALID_MEMORY_OPCODE"); } diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index 2d04dc5c..45108211 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -150,6 +150,10 @@ library Instructions { uint16 internal constant PUSH_ERROR_GUARD = 0x8025; uint16 internal constant POP_ERROR_GUARD = 0x8026; + uint16 internal constant NEW_COTHREAD = 0x8030; + uint16 internal constant POP_COTHREAD = 0x8031; + uint16 internal constant SWITCH_COTHREAD = 0x8032; + uint256 internal constant INBOX_INDEX_SEQUENCER = 0; uint256 internal constant INBOX_INDEX_DELAYED = 1; From c735bd2f7d338e4bc9caf4a267f0675fefca23d1 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Thu, 1 Feb 2024 12:20:47 -0700 Subject: [PATCH 054/126] removing error guard --- src/challenge/ChallengeLib.sol | 2 - src/osp/OneStepProofEntry.sol | 13 ++---- src/osp/OneStepProverHostIo.sol | 40 +++------------- src/state/Deserialize.sol | 47 ------------------- src/state/GuardStack.sol | 82 --------------------------------- src/state/Instructions.sol | 2 - src/state/Machine.sol | 11 +---- 7 files changed, 11 insertions(+), 186 deletions(-) delete mode 100644 src/state/GuardStack.sol diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index 0bdc3504..398e8b47 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -64,7 +64,6 @@ library ChallengeLib { ValueStack memory values = ValueStack({proved: valuesArray, remainingHash: 0}); ValueStack memory internalStack; StackFrameWindow memory frameStack; - GuardStack memory guardStack; MultiStack memory emptyMultiStack; emptyMultiStack.setEmpty(); @@ -75,7 +74,6 @@ library ChallengeLib { internalStack: internalStack, frameStack: frameStack, frameMultiStack: emptyMultiStack, - guardStack: guardStack, globalStateHash: globalStateHash, moduleIdx: 0, functionIdx: 0, diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index 19076038..c146e4ce 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -15,7 +15,6 @@ contract OneStepProofEntry is IOneStepProofEntry { using MachineLib for Machine; using ValueStackLib for ValueStack; - using GuardStackLib for GuardStack; using StackFrameLib for StackFrameWindow; IOneStepProver public prover0; @@ -117,7 +116,7 @@ contract OneStepProofEntry is IOneStepProofEntry { } else if ( (opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 && opcode <= Instructions.SET_GLOBAL_STATE_U64) || - (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.POP_ERROR_GUARD) || + (opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.UNLINK_MODULE) || (opcode >= Instructions.NEW_COTHREAD && opcode <= Instructions.SWITCH_COTHREAD) ) { prover = proverHostIo; @@ -133,16 +132,10 @@ contract OneStepProofEntry is IOneStepProofEntry { mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod); } - if (mach.status == MachineStatus.ERRORED && mach.cothread && !mach.guardStack.empty()) { - ErrorGuard memory guard = mach.guardStack.pop(); - mach.frameMultiStack.inactiveStackHash = guard.frameStack; - mach.valueMultiStack.inactiveStackHash = guard.valueStack; + if (mach.status == MachineStatus.ERRORED && mach.cothread) { mach.switchCoThread(); - mach.internalStack.overwrite(guard.interStack); - mach.setPc(guard.onErrorPc); - // indicate an error and continue - mach.valueStack.push(ValueLib.newI32(0)); + mach.valueStack.push(ValueLib.newI32(1)); mach.status = MachineStatus.RUNNING; } diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index c9c8cc1c..ce0fc38a 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -23,7 +23,6 @@ contract OneStepProverHostIo is IOneStepProver { using ValueLib for Value; using ValueStackLib for ValueStack; using StackFrameLib for StackFrameWindow; - using GuardStackLib for GuardStack; uint256 private constant LEAF_SIZE = 32; uint256 private constant INBOX_NUM = 2; @@ -390,31 +389,6 @@ contract OneStepProverHostIo is IOneStepProver { } } - function executePushErrorGuard( - ExecutionContext calldata, - Machine memory mach, - Module memory, - Instruction calldata, - bytes calldata - ) internal pure { - bytes32 frames = mach.frameStack.hash(); - bytes32 values = mach.valueStack.hash(); - bytes32 inters = mach.internalStack.hash(); - Value memory onError = ValueLib.newPc(mach.functionPc, mach.functionIdx, mach.moduleIdx); - mach.guardStack.push(GuardStackLib.newErrorGuard(frames, values, inters, onError)); - mach.valueStack.push(ValueLib.newI32(1)); - } - - function executePopErrorGuard( - ExecutionContext calldata, - Machine memory mach, - Module memory, - Instruction calldata, - bytes calldata - ) internal pure { - mach.guardStack.pop(); - } - function executeGlobalStateAccess( ExecutionContext calldata, Machine memory mach, @@ -554,13 +528,13 @@ contract OneStepProverHostIo is IOneStepProver { impl = executeLinkModule; } else if (opcode == Instructions.UNLINK_MODULE) { impl = executeUnlinkModule; - } else if (opcode == Instructions.PUSH_ERROR_GUARD) { - impl = executePushErrorGuard; - } else if (opcode == Instructions.POP_ERROR_GUARD) { - impl = executePopErrorGuard; - } else if (opcode == Instructions.NEW_COTHREAD) {} else if ( - opcode == Instructions.POP_COTHREAD - ) {} else if (opcode == Instructions.SWITCH_COTHREAD) {} else { + } else if (opcode == Instructions.NEW_COTHREAD) { + impl = executeNewCoThread; + } else if (opcode == Instructions.POP_COTHREAD) { + impl = executePopCoThread; + } else if (opcode == Instructions.SWITCH_COTHREAD) { + impl = executeSwitchCoThread; + } else { revert("INVALID_MEMORY_OPCODE"); } diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 08680f73..28a19813 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -10,7 +10,6 @@ import "./Machine.sol"; import "./MultiStack.sol"; import "./Instructions.sol"; import "./StackFrame.sol"; -import "./GuardStack.sol"; import "./MerkleProof.sol"; import "./ModuleMemoryCompact.sol"; import "./Module.sol"; @@ -202,49 +201,6 @@ library Deserialize { window = StackFrameWindow({proved: proved, remainingHash: remainingHash}); } - function errorGuard(bytes calldata proof, uint256 startOffset) - internal - pure - returns (ErrorGuard memory guard, uint256 offset) - { - offset = startOffset; - Value memory onErrorPc; - bytes32 frameStack; - bytes32 valueStackHash; - bytes32 interStack; - (frameStack, offset) = b32(proof, offset); - (valueStackHash, offset) = b32(proof, offset); - (interStack, offset) = b32(proof, offset); - (onErrorPc, offset) = value(proof, offset); - guard = ErrorGuard({ - frameStack: frameStack, - valueStack: valueStackHash, - interStack: interStack, - onErrorPc: onErrorPc - }); - } - - function guardStack(bytes calldata proof, uint256 startOffset) - internal - pure - returns (GuardStack memory window, uint256 offset) - { - offset = startOffset; - bytes32 remainingHash; - bool empty; - (empty, offset) = boolean(proof, offset); - (remainingHash, offset) = b32(proof, offset); - - ErrorGuard[] memory proved; - if (empty) { - proved = new ErrorGuard[](0); - } else { - proved = new ErrorGuard[](1); - (proved[0], offset) = errorGuard(proof, offset); - } - window = GuardStack({proved: proved, remainingHash: remainingHash}); - } - function moduleMemory(bytes calldata proof, uint256 startOffset) internal pure @@ -333,13 +289,11 @@ library Deserialize { MultiStack memory valuesMulti; StackFrameWindow memory frameStack; MultiStack memory framesMulti; - GuardStack memory guards; (values, offset) = valueStack(proof, offset); (valuesMulti, offset) = multiStack(proof, offset); (internalStack, offset) = valueStack(proof, offset); (frameStack, offset) = stackFrameWindow(proof, offset); (framesMulti, offset) = multiStack(proof, offset); - (guards, offset) = guardStack(proof, offset); mach = Machine({ status: status, valueStack: values, @@ -347,7 +301,6 @@ library Deserialize { internalStack: internalStack, frameStack: frameStack, frameMultiStack: framesMulti, - guardStack: guards, globalStateHash: bytes32(0), // filled later moduleIdx: 0, // filled later functionIdx: 0, // filled later diff --git a/src/state/GuardStack.sol b/src/state/GuardStack.sol deleted file mode 100644 index 59c75892..00000000 --- a/src/state/GuardStack.sol +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2023, Offchain Labs, Inc. -// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.0; - -import "./Value.sol"; - -struct ErrorGuard { - bytes32 frameStack; - bytes32 valueStack; - bytes32 interStack; - Value onErrorPc; -} - -struct GuardStack { - ErrorGuard[] proved; - bytes32 remainingHash; -} - -library GuardStackLib { - using ValueLib for Value; - - function newErrorGuard( - bytes32 frameStack, - bytes32 valueStack, - bytes32 interStack, - Value memory onErrorPc - ) internal pure returns (ErrorGuard memory) { - return - ErrorGuard({ - frameStack: frameStack, - valueStack: valueStack, - interStack: interStack, - onErrorPc: onErrorPc - }); - } - - function hash(ErrorGuard memory guard) internal pure returns (bytes32) { - return - keccak256( - abi.encodePacked( - "Error guard:", - guard.frameStack, - guard.valueStack, - guard.interStack, - guard.onErrorPc.hash() - ) - ); - } - - function hash(GuardStack memory guards) internal pure returns (bytes32 h) { - h = guards.remainingHash; - for (uint256 i = 0; i < guards.proved.length; i++) { - h = keccak256(abi.encodePacked("Guard stack:", hash(guards.proved[i]), h)); - } - } - - function empty(GuardStack memory guards) internal pure returns (bool) { - return guards.proved.length == 0 && guards.remainingHash == 0; - } - - function peek(GuardStack memory guards) internal pure returns (ErrorGuard memory) { - require(guards.proved.length == 1, "BAD_GUARDS_LENGTH"); - return guards.proved[0]; - } - - function pop(GuardStack memory guards) internal pure returns (ErrorGuard memory frame) { - require(guards.proved.length == 1, "BAD_GUARDS_LENGTH"); - frame = guards.proved[0]; - guards.proved = new ErrorGuard[](0); - } - - function push(GuardStack memory guards, ErrorGuard memory guard) internal pure { - ErrorGuard[] memory newProved = new ErrorGuard[](guards.proved.length + 1); - for (uint256 i = 0; i < guards.proved.length; i++) { - newProved[i] = guards.proved[i]; - } - newProved[guards.proved.length] = guard; - guards.proved = newProved; - } -} diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index 45108211..9c67d518 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -147,8 +147,6 @@ library Instructions { uint16 internal constant HALT_AND_SET_FINISHED = 0x8022; uint16 internal constant LINK_MODULE = 0x8023; uint16 internal constant UNLINK_MODULE = 0x8024; - uint16 internal constant PUSH_ERROR_GUARD = 0x8025; - uint16 internal constant POP_ERROR_GUARD = 0x8026; uint16 internal constant NEW_COTHREAD = 0x8030; uint16 internal constant POP_COTHREAD = 0x8031; diff --git a/src/state/Machine.sol b/src/state/Machine.sol index c18f1710..f36dc9d0 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -8,7 +8,6 @@ import "./ValueStack.sol"; import "./Instructions.sol"; import "./MultiStack.sol"; import "./StackFrame.sol"; -import "./GuardStack.sol"; enum MachineStatus { RUNNING, @@ -24,7 +23,6 @@ struct Machine { ValueStack internalStack; StackFrameWindow frameStack; MultiStack frameMultiStack; - GuardStack guardStack; bytes32 globalStateHash; uint32 moduleIdx; uint32 functionIdx; @@ -35,7 +33,6 @@ struct Machine { library MachineLib { using StackFrameLib for StackFrameWindow; - using GuardStackLib for GuardStack; using ValueStackLib for ValueStack; using MultiStackLib for MultiStack; @@ -61,13 +58,7 @@ library MachineLib { mach.functionPc, mach.modulesRoot ); - - if (mach.guardStack.empty() && !mach.cothread) { - return keccak256(preimage); - } else { - return - keccak256(abi.encodePacked(preimage, "With guards:", mach.guardStack.hash())); - } + return keccak256(preimage); } else if (mach.status == MachineStatus.FINISHED) { return keccak256(abi.encodePacked("Machine finished:", mach.globalStateHash)); } else if (mach.status == MachineStatus.ERRORED) { From 76d8d07161a33ebf0dbd362e55aba0399fb4ec8d Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 6 Feb 2024 19:28:37 -0700 Subject: [PATCH 055/126] fix cothread support, use recoveryPc --- src/challenge/ChallengeLib.sol | 4 +- src/osp/OneStepProofEntry.sol | 8 ++-- src/osp/OneStepProverHostIo.sol | 36 +++++++++++------ src/state/Deserialize.sol | 6 +-- src/state/Machine.sol | 69 +++++++++++++++++++++++++-------- src/state/MultiStack.sol | 10 +++-- 6 files changed, 93 insertions(+), 40 deletions(-) diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index 398e8b47..30a4fc1d 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -78,8 +78,8 @@ library ChallengeLib { moduleIdx: 0, functionIdx: 0, functionPc: 0, - modulesRoot: wasmModuleRoot, - cothread: false + recoveryPc: MachineLib.NO_RECOVERY_PC, + modulesRoot: wasmModuleRoot }); return mach.hash(); } diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index c146e4ce..4d03e455 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -132,10 +132,10 @@ contract OneStepProofEntry is IOneStepProofEntry { mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod); } - if (mach.status == MachineStatus.ERRORED && mach.cothread) { - mach.switchCoThread(); - // indicate an error and continue - mach.valueStack.push(ValueLib.newI32(1)); + if (mach.status == MachineStatus.ERRORED && mach.recoveryPc != MachineLib.NO_RECOVERY_PC) { + // capture error, recover into main thread. + mach.switchCoThreadStacks(); + mach.setPcFromRecovery(); mach.status = MachineStatus.RUNNING; } diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index ce0fc38a..db30abeb 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -426,12 +426,13 @@ contract OneStepProverHostIo is IOneStepProver { Instruction calldata, bytes calldata ) internal pure { - if (mach.cothread) { + if (mach.recoveryPc != MachineLib.NO_RECOVERY_PC) { // cannot create new cothread from inside cothread mach.status = MachineStatus.ERRORED; return; } mach.frameMultiStack.pushNew(); + mach.valueMultiStack.pushNew(); } function proovePopCothread(MultiStack memory multi, bytes calldata proof) internal pure { @@ -442,13 +443,14 @@ contract OneStepProverHostIo is IOneStepProver { (newRemaining, proofOffset) = Deserialize.b32(proof, proofOffset); if (newInactiveCoThread == MultiStackLib.NO_STACK_HASH) { require(multi.remainingHash == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); - return; + require(newRemaining == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); + } else { + require( + keccak256(abi.encodePacked("cothread:", newInactiveCoThread, newRemaining)) == + multi.remainingHash, + "WRONG_COTHREAD_POP" + ); } - require( - keccak256(abi.encodePacked("cothread: ", newInactiveCoThread, newRemaining)) == - multi.remainingHash, - "WRONG_COTHREAD_POP" - ); multi.remainingHash = newRemaining; multi.inactiveStackHash = newInactiveCoThread; return; @@ -461,7 +463,7 @@ contract OneStepProverHostIo is IOneStepProver { Instruction calldata, bytes calldata proof ) internal pure { - if (mach.cothread) { + if (mach.recoveryPc != MachineLib.NO_RECOVERY_PC) { // cannot pop cothread from inside cothread mach.status = MachineStatus.ERRORED; return; @@ -487,10 +489,22 @@ contract OneStepProverHostIo is IOneStepProver { mach.status = MachineStatus.ERRORED; return; } - bool jumpToCoThread = (inst.argumentData != 0); - if (jumpToCoThread != mach.cothread) { - mach.switchCoThread(); + if (inst.argumentData == 0) { + if (mach.recoveryPc == MachineLib.NO_RECOVERY_PC) { + // switching to main thread, from main thread + mach.status = MachineStatus.ERRORED; + return; + } + mach.recoveryPc = MachineLib.NO_RECOVERY_PC; + } else { + if (mach.recoveryPc != MachineLib.NO_RECOVERY_PC) { + // switching from cothread to cothread + mach.status = MachineStatus.ERRORED; + return; + } + mach.setRecoveryFromPc(uint32(inst.argumentData)); } + mach.switchCoThreadStacks(); } function executeOneStep( diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 28a19813..19303168 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -305,16 +305,16 @@ library Deserialize { moduleIdx: 0, // filled later functionIdx: 0, // filled later functionPc: 0, // filled later - modulesRoot: bytes32(0), // filled later - cothread: false // filled later + recoveryPc: bytes32(0), // filled later + modulesRoot: bytes32(0) // filled later }); } (mach.globalStateHash, offset) = b32(proof, offset); (mach.moduleIdx, offset) = u32(proof, offset); (mach.functionIdx, offset) = u32(proof, offset); (mach.functionPc, offset) = u32(proof, offset); + (mach.recoveryPc, offset) = b32(proof, offset); (mach.modulesRoot, offset) = b32(proof, offset); - (mach.cothread, offset) = boolean(proof, offset); } function merkleProof(bytes calldata proof, uint256 startOffset) diff --git a/src/state/Machine.sol b/src/state/Machine.sol index f36dc9d0..2a67e639 100644 --- a/src/state/Machine.sol +++ b/src/state/Machine.sol @@ -27,8 +27,8 @@ struct Machine { uint32 moduleIdx; uint32 functionIdx; uint32 functionPc; + bytes32 recoveryPc; bytes32 modulesRoot; - bool cothread; } library MachineLib { @@ -36,16 +36,18 @@ library MachineLib { using ValueStackLib for ValueStack; using MultiStackLib for MultiStack; + bytes32 internal constant NO_RECOVERY_PC = ~bytes32(0); + function hash(Machine memory mach) internal pure returns (bytes32) { // Warning: the non-running hashes are replicated in Challenge if (mach.status == MachineStatus.RUNNING) { bytes32 valueMultiHash = mach.valueMultiStack.hash( mach.valueStack.hash(), - mach.cothread + mach.recoveryPc != NO_RECOVERY_PC ); bytes32 frameMultiHash = mach.frameMultiStack.hash( mach.frameStack.hash(), - mach.cothread + mach.recoveryPc != NO_RECOVERY_PC ); bytes memory preimage = abi.encodePacked( "Machine running:", @@ -56,6 +58,7 @@ library MachineLib { mach.moduleIdx, mach.functionIdx, mach.functionPc, + mach.recoveryPc, mach.modulesRoot ); return keccak256(preimage); @@ -70,32 +73,66 @@ library MachineLib { } } - function switchCoThread(Machine memory mach) internal pure { + function switchCoThreadStacks(Machine memory mach) internal pure { bytes32 newActiveValue = mach.valueMultiStack.inactiveStackHash; bytes32 newActiveFrame = mach.frameMultiStack.inactiveStackHash; - if (newActiveFrame == bytes32(0) || newActiveValue == bytes32(0)) { + if ( + newActiveFrame == MultiStackLib.NO_STACK_HASH || + newActiveValue == MultiStackLib.NO_STACK_HASH + ) { mach.status = MachineStatus.ERRORED; return; } - mach.cothread = !mach.cothread; mach.frameMultiStack.inactiveStackHash = mach.frameStack.hash(); mach.valueMultiStack.inactiveStackHash = mach.valueStack.hash(); - mach.frameStack.overwrite(newActiveValue); - mach.valueStack.overwrite(newActiveFrame); + mach.frameStack.overwrite(newActiveFrame); + mach.valueStack.overwrite(newActiveValue); } - function setPc(Machine memory mach, Value memory pc) internal pure { - if (pc.valueType == ValueType.REF_NULL) { - mach.status = MachineStatus.ERRORED; - return; + function setPcFromData(Machine memory mach, uint256 data) internal pure returns (bool) { + if (data >> 96 != 0) { + return false; } - uint256 data = pc.contents; - require(pc.valueType == ValueType.INTERNAL_REF, "INVALID_PC_TYPE"); - require(data >> 96 == 0, "INVALID_PC_DATA"); - mach.functionPc = uint32(data); mach.functionIdx = uint32(data >> 32); mach.moduleIdx = uint32(data >> 64); + return true; + } + + function setPcFromRecovery(Machine memory mach) internal pure returns (bool) { + if (!setPcFromData(mach, uint256(mach.recoveryPc))) { + return false; + } + mach.recoveryPc = NO_RECOVERY_PC; + return true; + } + + function setRecoveryFromPc(Machine memory mach, uint32 offset) internal pure returns (bool) { + if (mach.recoveryPc != NO_RECOVERY_PC) { + return false; + } + + uint256 result; + result = uint256(mach.moduleIdx) << 64; + result = result | (uint256(mach.functionIdx) << 32); + result = result | uint256(mach.functionPc + offset - 1); + mach.recoveryPc = bytes32(result); + return true; + } + + function setPc(Machine memory mach, Value memory pc) internal pure { + if (pc.valueType == ValueType.REF_NULL) { + mach.status = MachineStatus.ERRORED; + return; + } + if (pc.valueType != ValueType.INTERNAL_REF) { + mach.status = MachineStatus.ERRORED; + return; + } + if (!setPcFromData(mach, pc.contents)) { + mach.status = MachineStatus.ERRORED; + return; + } } } diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol index cb539891..a051844f 100644 --- a/src/state/MultiStack.sol +++ b/src/state/MultiStack.sol @@ -16,12 +16,14 @@ library MultiStackLib { MultiStack memory multi, bytes32 activeStackHash, bool cothread - ) internal pure returns (bytes32 h) { + ) internal pure returns (bytes32) { + require(activeStackHash != NO_STACK_HASH, "MULTISTACK_NOSTACK_ACTIVE"); if (cothread) { + require(multi.inactiveStackHash != NO_STACK_HASH, "MULTISTACK_NOSTACK_MAIN"); return keccak256( abi.encodePacked( - "multistack: ", + "multistack:", multi.inactiveStackHash, activeStackHash, multi.remainingHash @@ -31,7 +33,7 @@ library MultiStackLib { return keccak256( abi.encodePacked( - "multistack: ", + "multistack:", activeStackHash, multi.inactiveStackHash, multi.remainingHash @@ -48,7 +50,7 @@ library MultiStackLib { function pushNew(MultiStack memory multi) internal pure { if (multi.inactiveStackHash != NO_STACK_HASH) { multi.remainingHash = keccak256( - abi.encodePacked("cothread: ", multi.inactiveStackHash, multi.remainingHash) + abi.encodePacked("cothread:", multi.inactiveStackHash, multi.remainingHash) ); } multi.inactiveStackHash = 0; From 16c0e33a2536981a753b74a78b753ab216dbe7b0 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 14 Feb 2024 13:57:45 -0700 Subject: [PATCH 056/126] multistack: 0 for empty remaining aligns with our stack hashing --- src/osp/OneStepProverHostIo.sol | 4 ++-- src/state/MultiStack.sol | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index db30abeb..b4de8872 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -442,8 +442,8 @@ contract OneStepProverHostIo is IOneStepProver { (newInactiveCoThread, proofOffset) = Deserialize.b32(proof, proofOffset); (newRemaining, proofOffset) = Deserialize.b32(proof, proofOffset); if (newInactiveCoThread == MultiStackLib.NO_STACK_HASH) { - require(multi.remainingHash == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); - require(newRemaining == MultiStackLib.NO_STACK_HASH, "WRONG_COTHREAD_EMPTY"); + require(newRemaining == bytes32(0), "WRONG_COTHREAD_EMPTY"); + require(multi.remainingHash == bytes32(0), "WRONG_COTHREAD_EMPTY"); } else { require( keccak256(abi.encodePacked("cothread:", newInactiveCoThread, newRemaining)) == diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol index a051844f..51b7f621 100644 --- a/src/state/MultiStack.sol +++ b/src/state/MultiStack.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.0; struct MultiStack { - bytes32 inactiveStackHash; - bytes32 remainingHash; + bytes32 inactiveStackHash; // NO_STACK_HASH if no stack, 0 if empty stack + bytes32 remainingHash; // 0 if less than 2 cothreads exist } library MultiStackLib { @@ -44,7 +44,7 @@ library MultiStackLib { function setEmpty(MultiStack memory multi) internal pure { multi.inactiveStackHash = NO_STACK_HASH; - multi.remainingHash = NO_STACK_HASH; + multi.remainingHash = 0; } function pushNew(MultiStack memory multi) internal pure { From efcc03da9a274147e263b58aba8c12f046ebd870 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 14 Feb 2024 15:18:05 -0700 Subject: [PATCH 057/126] lint --- src/state/MultiStack.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/state/MultiStack.sol b/src/state/MultiStack.sol index 51b7f621..45bc7e6e 100644 --- a/src/state/MultiStack.sol +++ b/src/state/MultiStack.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.0; struct MultiStack { - bytes32 inactiveStackHash; // NO_STACK_HASH if no stack, 0 if empty stack - bytes32 remainingHash; // 0 if less than 2 cothreads exist + bytes32 inactiveStackHash; // NO_STACK_HASH if no stack, 0 if empty stack + bytes32 remainingHash; // 0 if less than 2 cothreads exist } library MultiStackLib { From 5666569dc3e83903de28fb73cf558535cfe22ecb Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Fri, 8 Mar 2024 07:27:29 -0700 Subject: [PATCH 058/126] fix typo --- src/osp/OneStepProverHostIo.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index b4de8872..5cab2f21 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -1,4 +1,4 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE // SPDX-License-Identifier: BUSL-1.1 @@ -435,7 +435,7 @@ contract OneStepProverHostIo is IOneStepProver { mach.valueMultiStack.pushNew(); } - function proovePopCothread(MultiStack memory multi, bytes calldata proof) internal pure { + function provePopCothread(MultiStack memory multi, bytes calldata proof) internal pure { uint256 proofOffset = 0; bytes32 newInactiveCoThread; bytes32 newRemaining; @@ -453,7 +453,6 @@ contract OneStepProverHostIo is IOneStepProver { } multi.remainingHash = newRemaining; multi.inactiveStackHash = newInactiveCoThread; - return; } function executePopCoThread( @@ -473,8 +472,8 @@ contract OneStepProverHostIo is IOneStepProver { mach.status = MachineStatus.ERRORED; return; } - proovePopCothread(mach.valueMultiStack, proof); - proovePopCothread(mach.frameMultiStack, proof[64:]); + provePopCothread(mach.valueMultiStack, proof); + provePopCothread(mach.frameMultiStack, proof[64:]); } function executeSwitchCoThread( From 11e643e5c52cfb91a6e330894bed48e5e10b272c Mon Sep 17 00:00:00 2001 From: Ganesh Vanahalli Date: Fri, 15 Mar 2024 15:59:21 -0500 Subject: [PATCH 059/126] Add function to test if arbBlockHash on pending block returns latest --- src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol b/src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol index 423175ba..22b6868c 100644 --- a/src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol +++ b/src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol @@ -19,4 +19,12 @@ contract PendingBlkTimeAndNrAdvanceCheck { require(block.timestamp > deployedAt, "Time didn't advance"); require(ArbSys(address(100)).arbBlockNumber() > deployedAtBlock, "Block didn't advance"); } + + function checkArbBlockHashReturnsLatest(bytes32 expected) external { + bytes32 gotBlockHash = ArbSys(address(100)).arbBlockHash( + ArbSys(address(100)).arbBlockNumber() - 1 + ); + require(gotBlockHash != bytes32(0), "ZERO_BLOCK_HASH"); + require(gotBlockHash == expected, "WRONG_BLOCK_HASH"); + } } From b0c1f3170f7032cc23c836eebfb66c9b1a3288f3 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Mon, 18 Mar 2024 14:24:27 +0100 Subject: [PATCH 060/126] Add hardhat config for testnode L1 --- hardhat.config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hardhat.config.ts b/hardhat.config.ts index 401a29b3..253ac5ca 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -138,6 +138,12 @@ module.exports = { geth: { url: 'http://localhost:8545', }, + testnode_l1: { + url: 'http://localhost:8545', + accounts: process.env['DEPLOYER_PRIVKEY'] + ? [process.env['DEPLOYER_PRIVKEY']] + : [], + }, }, etherscan: { apiKey: { From 5f5d7eed216797052d90efa19b49878fa6e9414f Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Mon, 18 Mar 2024 14:26:01 +0100 Subject: [PATCH 061/126] Make verification optional --- scripts/deploymentUtils.ts | 148 +++++++++++++++++++++++++------------ 1 file changed, 100 insertions(+), 48 deletions(-) diff --git a/scripts/deploymentUtils.ts b/scripts/deploymentUtils.ts index 2df51be6..35c1f460 100644 --- a/scripts/deploymentUtils.ts +++ b/scripts/deploymentUtils.ts @@ -87,80 +87,132 @@ export async function deployUpgradeExecutor(signer: any): Promise { // Function to handle all deployments of core contracts using deployContract function export async function deployAllContracts( - signer: any + signer: any, + verify: boolean = true ): Promise> { const isOnArb = await _isRunningOnArbitrum(signer) - const ethBridge = await deployContract('Bridge', signer, []) + const ethBridge = await deployContract('Bridge', signer, [], verify) const reader4844 = isOnArb ? ethers.constants.AddressZero : (await Toolkit4844.deployReader4844(signer)).address - const ethSequencerInbox = await deployContract('SequencerInbox', signer, [ - maxDataSize, - reader4844, - false, - ]) + const ethSequencerInbox = await deployContract( + 'SequencerInbox', + signer, + [maxDataSize, reader4844, false], + verify + ) - const ethInbox = await deployContract('Inbox', signer, [maxDataSize]) + const ethInbox = await deployContract('Inbox', signer, [maxDataSize], verify) const ethRollupEventInbox = await deployContract( 'RollupEventInbox', signer, - [] + [], + verify + ) + const ethOutbox = await deployContract('Outbox', signer, [], verify) + + const erc20Bridge = await deployContract('ERC20Bridge', signer, [], verify) + const erc20SequencerInbox = await deployContract( + 'SequencerInbox', + signer, + [maxDataSize, reader4844, true], + verify + ) + const erc20Inbox = await deployContract( + 'ERC20Inbox', + signer, + [maxDataSize], + verify ) - const ethOutbox = await deployContract('Outbox', signer, []) - - const erc20Bridge = await deployContract('ERC20Bridge', signer, []) - const erc20SequencerInbox = await deployContract('SequencerInbox', signer, [ - maxDataSize, - reader4844, - true, - ]) - const erc20Inbox = await deployContract('ERC20Inbox', signer, [maxDataSize]) const erc20RollupEventInbox = await deployContract( 'ERC20RollupEventInbox', signer, - [] + [], + verify ) - const erc20Outbox = await deployContract('ERC20Outbox', signer, []) + const erc20Outbox = await deployContract('ERC20Outbox', signer, [], verify) - const bridgeCreator = await deployContract('BridgeCreator', signer, [ + const bridgeCreator = await deployContract( + 'BridgeCreator', + signer, [ - ethBridge.address, - ethSequencerInbox.address, - ethInbox.address, - ethRollupEventInbox.address, - ethOutbox.address, + [ + ethBridge.address, + ethSequencerInbox.address, + ethInbox.address, + ethRollupEventInbox.address, + ethOutbox.address, + ], + [ + erc20Bridge.address, + erc20SequencerInbox.address, + erc20Inbox.address, + erc20RollupEventInbox.address, + erc20Outbox.address, + ], ], + verify + ) + const prover0 = await deployContract('OneStepProver0', signer, [], verify) + const proverMem = await deployContract( + 'OneStepProverMemory', + signer, + [], + verify + ) + const proverMath = await deployContract( + 'OneStepProverMath', + signer, + [], + verify + ) + const proverHostIo = await deployContract( + 'OneStepProverHostIo', + signer, + [], + verify + ) + const osp: Contract = await deployContract( + 'OneStepProofEntry', + signer, [ - erc20Bridge.address, - erc20SequencerInbox.address, - erc20Inbox.address, - erc20RollupEventInbox.address, - erc20Outbox.address, + prover0.address, + proverMem.address, + proverMath.address, + proverHostIo.address, ], - ]) - const prover0 = await deployContract('OneStepProver0', signer) - const proverMem = await deployContract('OneStepProverMemory', signer) - const proverMath = await deployContract('OneStepProverMath', signer) - const proverHostIo = await deployContract('OneStepProverHostIo', signer) - const osp: Contract = await deployContract('OneStepProofEntry', signer, [ - prover0.address, - proverMem.address, - proverMath.address, - proverHostIo.address, - ]) - const challengeManager = await deployContract('ChallengeManager', signer) - const rollupAdmin = await deployContract('RollupAdminLogic', signer) - const rollupUser = await deployContract('RollupUserLogic', signer) + verify + ) + const challengeManager = await deployContract( + 'ChallengeManager', + signer, + [], + verify + ) + const rollupAdmin = await deployContract( + 'RollupAdminLogic', + signer, + [], + verify + ) + const rollupUser = await deployContract('RollupUserLogic', signer, [], verify) const upgradeExecutor = await deployUpgradeExecutor(signer) const validatorUtils = await deployContract('ValidatorUtils', signer) const validatorWalletCreator = await deployContract( 'ValidatorWalletCreator', - signer + signer, + [], + verify + ) + const rollupCreator = await deployContract( + 'RollupCreator', + signer, + [], + verify ) - const rollupCreator = await deployContract('RollupCreator', signer) - const deployHelper = await deployContract('DeployHelper', signer) + const deployHelper = await deployContract('DeployHelper', signer, [], verify) return { bridgeCreator, prover0, From de31a21534e14c0116ec9595cff26e866967d436 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Mon, 18 Mar 2024 15:06:10 +0100 Subject: [PATCH 062/126] Don't verify if testnode --- scripts/deployment.ts | 8 +++++++- scripts/deploymentUtils.ts | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/deployment.ts b/scripts/deployment.ts index fdb51877..a91aabc1 100644 --- a/scripts/deployment.ts +++ b/scripts/deployment.ts @@ -1,13 +1,19 @@ import { ethers } from 'hardhat' import '@nomiclabs/hardhat-ethers' import { deployAllContracts } from './deploymentUtils' +import hre from 'hardhat' async function main() { const [signer] = await ethers.getSigners() + let verifyContracts = true + if (hre.network.name.includes('testnode')) { + verifyContracts = false + } + try { // Deploying all contracts - const contracts = await deployAllContracts(signer) + const contracts = await deployAllContracts(signer, verifyContracts) // Call setTemplates with the deployed contract addresses console.log('Waiting for the Template to be set on the Rollup Creator') diff --git a/scripts/deploymentUtils.ts b/scripts/deploymentUtils.ts index 35c1f460..f4ffa6dc 100644 --- a/scripts/deploymentUtils.ts +++ b/scripts/deploymentUtils.ts @@ -199,7 +199,12 @@ export async function deployAllContracts( ) const rollupUser = await deployContract('RollupUserLogic', signer, [], verify) const upgradeExecutor = await deployUpgradeExecutor(signer) - const validatorUtils = await deployContract('ValidatorUtils', signer) + const validatorUtils = await deployContract( + 'ValidatorUtils', + signer, + [], + verify + ) const validatorWalletCreator = await deployContract( 'ValidatorWalletCreator', signer, From 1cab72ff3dfcfe06ceed371a9db7a54a527e3bfb Mon Sep 17 00:00:00 2001 From: Ganesh Vanahalli Date: Mon, 18 Mar 2024 10:44:16 -0500 Subject: [PATCH 063/126] address PR comments --- src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol b/src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol index 22b6868c..0676845a 100644 --- a/src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol +++ b/src/mocks/PendingBlkTimeAndNrAdvanceCheck.sol @@ -9,21 +9,20 @@ import "../precompiles/ArbSys.sol"; contract PendingBlkTimeAndNrAdvanceCheck { uint256 immutable deployedAt; uint256 immutable deployedAtBlock; + ArbSys constant ARB_SYS = ArbSys(address(100)); constructor() { deployedAt = block.timestamp; - deployedAtBlock = ArbSys(address(100)).arbBlockNumber(); + deployedAtBlock = ARB_SYS.arbBlockNumber(); } function isAdvancing() external { require(block.timestamp > deployedAt, "Time didn't advance"); - require(ArbSys(address(100)).arbBlockNumber() > deployedAtBlock, "Block didn't advance"); + require(ARB_SYS.arbBlockNumber() > deployedAtBlock, "Block didn't advance"); } function checkArbBlockHashReturnsLatest(bytes32 expected) external { - bytes32 gotBlockHash = ArbSys(address(100)).arbBlockHash( - ArbSys(address(100)).arbBlockNumber() - 1 - ); + bytes32 gotBlockHash = ARB_SYS.arbBlockHash(ARB_SYS.arbBlockNumber() - 1); require(gotBlockHash != bytes32(0), "ZERO_BLOCK_HASH"); require(gotBlockHash == expected, "WRONG_BLOCK_HASH"); } From fe129fc8647dbe993039ec19882f064a072b4748 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 19 Mar 2024 14:04:42 +0100 Subject: [PATCH 064/126] Don't verify rollup contracts --- scripts/rollupCreation.ts | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 82e40cb5..832d456c 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -6,6 +6,7 @@ import { config, maxDataSize } from './config' import { BigNumber } from 'ethers' import { IERC20__factory } from '../build/types' import { sleep } from './testSetup' +import hre from 'hardhat' // 1 gwei const MAX_FER_PER_GAS = BigNumber.from('1000000000') @@ -116,24 +117,28 @@ export async function createRollup(feeToken?: string) { console.log('RollupProxy Contract created at address:', rollupAddress) console.log('Wait a minute before starting the contract verification') await sleep(1 * 60 * 1000) - console.log( - `Attempting to verify Rollup contract at address ${rollupAddress}...` - ) - try { - await run('verify:verify', { - contract: 'src/rollup/RollupProxy.sol:RollupProxy', - address: rollupAddress, - constructorArguments: [], - }) - } catch (error: any) { - if (error.message.includes('Already Verified')) { - console.log(`Contract RollupProxy is already verified.`) - } else { - console.error( - `Verification for RollupProxy failed with the following error: ${error.message}` - ) + + if (hre.network.name.includes('testnode')) { + console.log( + `Attempting to verify Rollup contract at address ${rollupAddress}...` + ) + try { + await run('verify:verify', { + contract: 'src/rollup/RollupProxy.sol:RollupProxy', + address: rollupAddress, + constructorArguments: [], + }) + } catch (error: any) { + if (error.message.includes('Already Verified')) { + console.log(`Contract RollupProxy is already verified.`) + } else { + console.error( + `Verification for RollupProxy failed with the following error: ${error.message}` + ) + } } } + console.log('Inbox (proxy) Contract created at address:', inboxAddress) console.log('Outbox (proxy) Contract created at address:', outbox) console.log( From b15881b7f329eceb1ff91a2bdf65f76b5e954dac Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 19 Mar 2024 17:11:47 +0100 Subject: [PATCH 065/126] Set up rollup config to be equivalent to nitro's deploy.go --- scripts/rollupCreation.ts | 163 +++++++++++++++++++++++++++++++++++--- 1 file changed, 153 insertions(+), 10 deletions(-) diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 832d456c..87b9a17b 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -7,10 +7,13 @@ import { BigNumber } from 'ethers' import { IERC20__factory } from '../build/types' import { sleep } from './testSetup' import hre from 'hardhat' +import { promises as fs } from 'fs' // 1 gwei const MAX_FER_PER_GAS = BigNumber.from('1000000000') +const isDevDeployment = hre.network.name.includes('testnode') + interface RollupCreatedEvent { event: string address: string @@ -79,15 +82,18 @@ export async function createRollup(feeToken?: string) { // Call the createRollup function console.log('Calling createRollup to generate a new rollup ...') - const deployParams = { - config: config.rollupConfig, - batchPoster: config.batchPoster, - validators: config.validators, - maxDataSize: maxDataSize, - nativeToken: feeToken, - deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FER_PER_GAS, - } + const deployParams = isDevDeployment + ? await _getDevRollupConfig(feeToken) + : { + config: config.rollupConfig, + validators: config.validators, + maxDataSize: maxDataSize, + nativeToken: feeToken, + deployFactoriesToL2: true, + maxFeePerGasForRetryables: MAX_FER_PER_GAS, + batchPosters: [config.batchPoster], + batchPosterManager: signer.address, + } const createRollupTx = await rollupCreator.createRollup(deployParams, { value: feeCost, }) @@ -118,7 +124,7 @@ export async function createRollup(feeToken?: string) { console.log('Wait a minute before starting the contract verification') await sleep(1 * 60 * 1000) - if (hre.network.name.includes('testnode')) { + if (isDevDeployment) { console.log( `Attempting to verify Rollup contract at address ${rollupAddress}...` ) @@ -170,3 +176,140 @@ export async function createRollup(feeToken?: string) { ) } } + +async function _getDevRollupConfig(feeToken: string) { + console.log('getting dev rollup config') + + // set up owner address + const ownerAddress = + process.env.OWNER_ADDRESS !== undefined ? process.env.OWNER_ADDRESS : '' + + // set up max data size + const _maxDataSize = + process.env.MAX_DATA_SIZE !== undefined + ? ethers.BigNumber.from(process.env.MAX_DATA_SIZE) + : 117964 + + // set up validators + const authorizeValidators = + process.env.AUTHORIZE_VALIDATORS !== undefined + ? parseInt(process.env.AUTHORIZE_VALIDATORS, 0) + : 0 + const validators: string[] = [] + for (let i = 1; i <= authorizeValidators; i++) { + validators.push(ethers.Wallet.createRandom().address) + } + + // get chain config + const childChainConfigPath = + process.env.CHILD_CHAIN_CONFIG_PATH !== undefined + ? process.env.CHILD_CHAIN_CONFIG_PATH + : 'l2_chain_config.json' + const chainConfig = await fs.readFile(childChainConfigPath, { + encoding: 'utf8', + }) + + // get wasmModuleRoot + const wasmModuleRoot = + process.env.WASM_MODULE_ROOT !== undefined + ? process.env.WASM_MODULE_ROOT + : '' + + // set up batch posters + const sequencerAddress = + process.env.SEQUENCER_ADDRESS !== undefined + ? process.env.SEQUENCER_ADDRESS + : '' + const batchPostersString = + process.env.BATCH_POSTERS !== undefined ? process.env.BATCH_POSTERS : '' + let batchPosters: string[] = [] + if (batchPostersString.length == 0) { + batchPosters.push(sequencerAddress) + } else { + const batchPostesArr = batchPostersString.split(',') + for (let i = 0; i < batchPostesArr.length; i++) { + if (ethers.utils.isAddress(batchPostesArr[i])) { + batchPosters.push(batchPostesArr[i]) + } else { + throw new Error('Invalid address in batch posters array') + } + } + } + + // set up batch poster manager + const batchPosterManagerEnv = + process.env.BATCH_POSTER_MANAGER !== undefined + ? process.env.BATCH_POSTER_MANAGER + : '' + let batchPosterManager = '' + if (ethers.utils.isAddress(batchPosterManagerEnv)) { + batchPosterManager = batchPosterManagerEnv + } else { + if (batchPosterManagerEnv.length == 0) { + batchPosterManager = ownerAddress + } else { + throw new Error('Invalid address for batch poster manager') + } + } + + // set up parent chain id + let parentChainId = + process.env.L1_CHAIN_ID !== undefined + ? ethers.BigNumber.from(process.env.L1_CHAIN_ID) + : 1337 + + console.log('dev rollup config:', { + config: { + confirmPeriodBlocks: ethers.BigNumber.from('20'), + extraChallengeTimeBlocks: ethers.BigNumber.from('200'), + stakeToken: ethers.constants.AddressZero, + baseStake: ethers.utils.parseEther('1'), + wasmModuleRoot: wasmModuleRoot, + owner: ownerAddress, + loserStakeEscrow: ethers.constants.AddressZero, + chainId: parentChainId, + chainConfig: chainConfig, + sequencerInboxMaxTimeVariation: { + delayBlocks: ethers.BigNumber.from('5760'), + futureBlocks: ethers.BigNumber.from('12'), + delaySeconds: ethers.BigNumber.from('86400'), + futureSeconds: ethers.BigNumber.from('3600'), + }, + }, + validators: validators, + maxDataSize: _maxDataSize, + nativeToken: feeToken, + deployFactoriesToL2: true, + maxFeePerGasForRetryables: MAX_FER_PER_GAS, + batchPosters: batchPosters, + batchPosterManager: batchPosterManager, + }) + + return { + config: { + confirmPeriodBlocks: ethers.BigNumber.from('20'), + extraChallengeTimeBlocks: ethers.BigNumber.from('200'), + stakeToken: ethers.constants.AddressZero, + baseStake: ethers.utils.parseEther('1'), + wasmModuleRoot: wasmModuleRoot, + owner: ownerAddress, + loserStakeEscrow: ethers.constants.AddressZero, + chainId: parentChainId, + chainConfig: chainConfig, + genesisBlockNum: 0, + sequencerInboxMaxTimeVariation: { + delayBlocks: ethers.BigNumber.from('5760'), + futureBlocks: ethers.BigNumber.from('12'), + delaySeconds: ethers.BigNumber.from('86400'), + futureSeconds: ethers.BigNumber.from('3600'), + }, + }, + validators: validators, + maxDataSize: _maxDataSize, + nativeToken: feeToken, + deployFactoriesToL2: true, + maxFeePerGasForRetryables: MAX_FER_PER_GAS, + batchPosters: batchPosters, + batchPosterManager: batchPosterManager, + } +} From 819bb1445d52dc0b44e57de9670f02f3c4ba3b23 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 19 Mar 2024 17:49:39 +0100 Subject: [PATCH 066/126] Move sleep --- scripts/rollupCreation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 87b9a17b..4f349956 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -121,10 +121,10 @@ export async function createRollup(feeToken?: string) { console.log("Congratulations! 🎉🎉🎉 All DONE! Here's your addresses:") console.log('RollupProxy Contract created at address:', rollupAddress) - console.log('Wait a minute before starting the contract verification') - await sleep(1 * 60 * 1000) if (isDevDeployment) { + console.log('Wait a minute before starting the contract verification') + await sleep(1 * 60 * 1000) console.log( `Attempting to verify Rollup contract at address ${rollupAddress}...` ) From b53e7baddfc92519aa2edacdb9ab6dd4d098f90e Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 19 Mar 2024 17:49:55 +0100 Subject: [PATCH 067/126] Update config.ts example to match API --- scripts/config.ts.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/config.ts.example b/scripts/config.ts.example index cf5d8704..78d020e0 100644 --- a/scripts/config.ts.example +++ b/scripts/config.ts.example @@ -29,5 +29,6 @@ export const config = { '0x1234123412341234123412341234123412341234', '0x1234512345123451234512345123451234512345', ], - batchPoster: '0x1234123412341234123412341234123412341234', + batchPosters: ['0x1234123412341234123412341234123412341234'], + batchPosterManager: '0x1234123412341234123412341234123412341234' } From 76b0f66e8a6fcd8b8133fbb32bf6b03bc9bee525 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Wed, 20 Mar 2024 14:19:04 +0100 Subject: [PATCH 068/126] Update RPC --- hardhat.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 253ac5ca..420ce87a 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -139,7 +139,7 @@ module.exports = { url: 'http://localhost:8545', }, testnode_l1: { - url: 'http://localhost:8545', + url: 'http://geth:8545', accounts: process.env['DEPLOYER_PRIVKEY'] ? [process.env['DEPLOYER_PRIVKEY']] : [], From 5b10528e89f5ed5318fc12914c730f721ffb8566 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Mon, 25 Mar 2024 08:32:18 +0100 Subject: [PATCH 069/126] Read maxDataSize from env --- scripts/deploymentUtils.ts | 26 +++++++++++++++++++++----- scripts/rollupCreation.ts | 6 ++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/scripts/deploymentUtils.ts b/scripts/deploymentUtils.ts index f4ffa6dc..83f6ca66 100644 --- a/scripts/deploymentUtils.ts +++ b/scripts/deploymentUtils.ts @@ -1,5 +1,5 @@ import { ethers } from 'hardhat' -import { ContractFactory, Contract, Overrides } from 'ethers' +import { ContractFactory, Contract, Overrides, BigNumber } from 'ethers' import '@nomiclabs/hardhat-ethers' import { run } from 'hardhat' import { @@ -10,6 +10,9 @@ import { maxDataSize } from './config' import { Toolkit4844 } from '../test/contract/toolkit4844' import { ArbSys__factory } from '../build/types' import { ARB_SYS_ADDRESS } from '@arbitrum/sdk/dist/lib/dataEntities/constants' +import hre from 'hardhat' + +const isDevDeployment = hre.network.name.includes('testnode') // Define a verification function export async function verifyContract( @@ -97,14 +100,15 @@ export async function deployAllContracts( ? ethers.constants.AddressZero : (await Toolkit4844.deployReader4844(signer)).address + const _maxDataSize = _getMaxDataSize() const ethSequencerInbox = await deployContract( 'SequencerInbox', signer, - [maxDataSize, reader4844, false], + [_maxDataSize, reader4844, false], verify ) - const ethInbox = await deployContract('Inbox', signer, [maxDataSize], verify) + const ethInbox = await deployContract('Inbox', signer, [_maxDataSize], verify) const ethRollupEventInbox = await deployContract( 'RollupEventInbox', signer, @@ -117,13 +121,13 @@ export async function deployAllContracts( const erc20SequencerInbox = await deployContract( 'SequencerInbox', signer, - [maxDataSize, reader4844, true], + [_maxDataSize, reader4844, true], verify ) const erc20Inbox = await deployContract( 'ERC20Inbox', signer, - [maxDataSize], + [_maxDataSize], verify ) const erc20RollupEventInbox = await deployContract( @@ -246,3 +250,15 @@ async function _isRunningOnArbitrum(signer: any): Promise { return false } } + +function _getMaxDataSize(): BigNumber { + if (isDevDeployment) { + const _maxDataSizeDev = + process.env.MAX_DATA_SIZE !== undefined + ? ethers.BigNumber.from(process.env.MAX_DATA_SIZE) + : ethers.BigNumber.from(117964) + return _maxDataSizeDev + } + + return maxDataSize +} diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 4f349956..e02f45a8 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -178,8 +178,6 @@ export async function createRollup(feeToken?: string) { } async function _getDevRollupConfig(feeToken: string) { - console.log('getting dev rollup config') - // set up owner address const ownerAddress = process.env.OWNER_ADDRESS !== undefined ? process.env.OWNER_ADDRESS : '' @@ -188,7 +186,7 @@ async function _getDevRollupConfig(feeToken: string) { const _maxDataSize = process.env.MAX_DATA_SIZE !== undefined ? ethers.BigNumber.from(process.env.MAX_DATA_SIZE) - : 117964 + : ethers.BigNumber.from(117964) // set up validators const authorizeValidators = @@ -256,7 +254,7 @@ async function _getDevRollupConfig(feeToken: string) { let parentChainId = process.env.L1_CHAIN_ID !== undefined ? ethers.BigNumber.from(process.env.L1_CHAIN_ID) - : 1337 + : ethers.BigNumber.from(1337) console.log('dev rollup config:', { config: { From a0f0c43ee00b088605e90eea59d86f781689bf83 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 26 Mar 2024 11:51:42 +0100 Subject: [PATCH 070/126] Refactor and add separate script for deploying rollup to testnode --- package.json | 3 +- scripts/createERC20Rollup.ts | 12 ++- scripts/createEthRollup.ts | 11 ++- scripts/deployment.ts | 13 ++- scripts/deploymentUtils.ts | 26 ++---- .../deployCreatorAndCreateRollup.ts | 81 +++++++++++++++++++ scripts/rollupCreation.ts | 59 +++----------- 7 files changed, 127 insertions(+), 78 deletions(-) create mode 100644 scripts/local-deployment/deployCreatorAndCreateRollup.ts diff --git a/package.json b/package.json index 475926f8..a882d554 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "postinstall": "patch-package", "deploy-factory": "hardhat run scripts/deployment.ts", "deploy-eth-rollup": "hardhat run scripts/createEthRollup.ts", - "deploy-erc20-rollup": "hardhat run scripts/createERC20Rollup.ts" + "deploy-erc20-rollup": "hardhat run scripts/createERC20Rollup.ts", + "create-rollup-testnode": "hardhat run scripts/local-deployment/deployCreatorAndCreateRollup.ts" }, "dependencies": { "@offchainlabs/upgrade-executor": "1.1.0-beta.0", diff --git a/scripts/createERC20Rollup.ts b/scripts/createERC20Rollup.ts index 604fa17e..49947b27 100644 --- a/scripts/createERC20Rollup.ts +++ b/scripts/createERC20Rollup.ts @@ -29,8 +29,18 @@ async function main() { ) } + const rollupCreatorAddress = process.env.ROLLUP_CREATOR_ADDRESS + if (!rollupCreatorAddress) { + throw new Error('ROLLUP_CREATOR_ADDRESS not set') + } + console.log('Creating new rollup with', customFeeTokenAddress, 'as fee token') - await createRollup(customFeeTokenAddress) + await createRollup( + deployer, + false, + rollupCreatorAddress, + customFeeTokenAddress + ) } main() diff --git a/scripts/createEthRollup.ts b/scripts/createEthRollup.ts index eb11e83d..4248ac21 100644 --- a/scripts/createEthRollup.ts +++ b/scripts/createEthRollup.ts @@ -1,8 +1,17 @@ +import { ethers } from 'hardhat' import '@nomiclabs/hardhat-ethers' import { createRollup } from './rollupCreation' async function main() { - await createRollup() + const feeToken = undefined + const rollupCreatorAddress = process.env.ROLLUP_CREATOR_ADDRESS + if (!rollupCreatorAddress) { + throw new Error('ROLLUP_CREATOR_ADDRESS not set') + } + + const [signer] = await ethers.getSigners() + + await createRollup(signer, false, rollupCreatorAddress, feeToken) } main() diff --git a/scripts/deployment.ts b/scripts/deployment.ts index a91aabc1..8da3acc2 100644 --- a/scripts/deployment.ts +++ b/scripts/deployment.ts @@ -1,19 +1,18 @@ import { ethers } from 'hardhat' import '@nomiclabs/hardhat-ethers' import { deployAllContracts } from './deploymentUtils' -import hre from 'hardhat' +import { maxDataSize } from './config' async function main() { const [signer] = await ethers.getSigners() - let verifyContracts = true - if (hre.network.name.includes('testnode')) { - verifyContracts = false - } - try { // Deploying all contracts - const contracts = await deployAllContracts(signer, verifyContracts) + const contracts = await deployAllContracts( + signer, + ethers.BigNumber.from(maxDataSize), + true + ) // Call setTemplates with the deployed contract addresses console.log('Waiting for the Template to be set on the Rollup Creator') diff --git a/scripts/deploymentUtils.ts b/scripts/deploymentUtils.ts index 83f6ca66..43276a83 100644 --- a/scripts/deploymentUtils.ts +++ b/scripts/deploymentUtils.ts @@ -6,13 +6,9 @@ import { abi as UpgradeExecutorABI, bytecode as UpgradeExecutorBytecode, } from '@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json' -import { maxDataSize } from './config' import { Toolkit4844 } from '../test/contract/toolkit4844' import { ArbSys__factory } from '../build/types' import { ARB_SYS_ADDRESS } from '@arbitrum/sdk/dist/lib/dataEntities/constants' -import hre from 'hardhat' - -const isDevDeployment = hre.network.name.includes('testnode') // Define a verification function export async function verifyContract( @@ -91,6 +87,7 @@ export async function deployUpgradeExecutor(signer: any): Promise { // Function to handle all deployments of core contracts using deployContract function export async function deployAllContracts( signer: any, + maxDataSize: BigNumber, verify: boolean = true ): Promise> { const isOnArb = await _isRunningOnArbitrum(signer) @@ -100,15 +97,14 @@ export async function deployAllContracts( ? ethers.constants.AddressZero : (await Toolkit4844.deployReader4844(signer)).address - const _maxDataSize = _getMaxDataSize() const ethSequencerInbox = await deployContract( 'SequencerInbox', signer, - [_maxDataSize, reader4844, false], + [maxDataSize, reader4844, false], verify ) - const ethInbox = await deployContract('Inbox', signer, [_maxDataSize], verify) + const ethInbox = await deployContract('Inbox', signer, [maxDataSize], verify) const ethRollupEventInbox = await deployContract( 'RollupEventInbox', signer, @@ -121,13 +117,13 @@ export async function deployAllContracts( const erc20SequencerInbox = await deployContract( 'SequencerInbox', signer, - [_maxDataSize, reader4844, true], + [maxDataSize, reader4844, true], verify ) const erc20Inbox = await deployContract( 'ERC20Inbox', signer, - [_maxDataSize], + [maxDataSize], verify ) const erc20RollupEventInbox = await deployContract( @@ -250,15 +246,3 @@ async function _isRunningOnArbitrum(signer: any): Promise { return false } } - -function _getMaxDataSize(): BigNumber { - if (isDevDeployment) { - const _maxDataSizeDev = - process.env.MAX_DATA_SIZE !== undefined - ? ethers.BigNumber.from(process.env.MAX_DATA_SIZE) - : ethers.BigNumber.from(117964) - return _maxDataSizeDev - } - - return maxDataSize -} diff --git a/scripts/local-deployment/deployCreatorAndCreateRollup.ts b/scripts/local-deployment/deployCreatorAndCreateRollup.ts new file mode 100644 index 00000000..dc77f123 --- /dev/null +++ b/scripts/local-deployment/deployCreatorAndCreateRollup.ts @@ -0,0 +1,81 @@ +import { ethers } from 'hardhat' +import '@nomiclabs/hardhat-ethers' +import { deployAllContracts } from '../deploymentUtils' +import { createRollup } from '../rollupCreation' +import { sleep } from '../testSetup' +import { BigNumber } from 'ethers' + +async function main() { + let deployerPrivKey = process.env.DEPLOYER_PRIVKEY as string + if (!deployerPrivKey) { + throw new Error('DEPLOYER_PRIVKEY not set') + } + + let parentChainRpc = process.env.PARENT_CHAIN_RPC as string + if (!parentChainRpc) { + throw new Error('PARENT_CHAIN_RPC not set') + } + + const deployerWallet = new ethers.Wallet( + deployerPrivKey, + new ethers.providers.JsonRpcProvider(parentChainRpc) + ) + + const maxDataSize = + process.env.MAX_DATA_SIZE !== undefined + ? ethers.BigNumber.from(process.env.MAX_DATA_SIZE) + : ethers.BigNumber.from(117964) + + try { + console.log('Deploy RollupCreator and templates') + const contracts = await deployAllContracts( + deployerWallet, + maxDataSize, + false + ) + + console.log('Set templates on the Rollup Creator') + await ( + await contracts.rollupCreator.setTemplates( + contracts.bridgeCreator.address, + contracts.osp.address, + contracts.challengeManager.address, + contracts.rollupAdmin.address, + contracts.rollupUser.address, + contracts.upgradeExecutor.address, + contracts.validatorUtils.address, + contracts.validatorWalletCreator.address, + contracts.deployHelper.address, + { gasLimit: BigNumber.from('300000') } + ) + ).wait() + + // Create rollup + console.log( + `Create rollup on top of chain ${ + (await deployerWallet.provider.getNetwork()).chainId + } using RollupCreator ${contracts.rollupCreator.address}` + ) + + const feeToken = undefined + await createRollup( + deployerWallet, + true, + contracts.rollupCreator.address, + feeToken + ) + console.log('Rollup created') + } catch (error) { + console.error( + 'Deployment failed:', + error instanceof Error ? error.message : error + ) + } +} + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index e02f45a8..3b78bae4 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -3,17 +3,14 @@ import '@nomiclabs/hardhat-ethers' import { run } from 'hardhat' import { abi as rollupCreatorAbi } from '../build/contracts/src/rollup/RollupCreator.sol/RollupCreator.json' import { config, maxDataSize } from './config' -import { BigNumber } from 'ethers' +import { BigNumber, Signer } from 'ethers' import { IERC20__factory } from '../build/types' import { sleep } from './testSetup' -import hre from 'hardhat' import { promises as fs } from 'fs' // 1 gwei const MAX_FER_PER_GAS = BigNumber.from('1000000000') -const isDevDeployment = hre.network.name.includes('testnode') - interface RollupCreatedEvent { event: string address: string @@ -31,24 +28,18 @@ interface RollupCreatedEvent { } } -export async function createRollup(feeToken?: string) { - const rollupCreatorAddress = process.env.ROLLUP_CREATOR_ADDRESS - - if (!rollupCreatorAddress) { - console.error( - 'Please provide ROLLUP_CREATOR_ADDRESS as an environment variable.' - ) - process.exit(1) - } - +export async function createRollup( + signer: Signer, + isDevDeployment: boolean, + rollupCreatorAddress: string, + feeToken?: string +) { if (!rollupCreatorAbi) { throw new Error( 'You need to first run script to deploy and compile the contracts first' ) } - const [signer] = await ethers.getSigners() - const rollupCreator = new ethers.Contract( rollupCreatorAddress, rollupCreatorAbi, @@ -87,13 +78,14 @@ export async function createRollup(feeToken?: string) { : { config: config.rollupConfig, validators: config.validators, - maxDataSize: maxDataSize, + maxDataSize: ethers.BigNumber.from(maxDataSize), nativeToken: feeToken, deployFactoriesToL2: true, maxFeePerGasForRetryables: MAX_FER_PER_GAS, - batchPosters: [config.batchPoster], - batchPosterManager: signer.address, + batchPosters: config.batchPosters, + batchPosterManager: config.batchPosterManager, } + const createRollupTx = await rollupCreator.createRollup(deployParams, { value: feeCost, }) @@ -122,7 +114,7 @@ export async function createRollup(feeToken?: string) { console.log("Congratulations! 🎉🎉🎉 All DONE! Here's your addresses:") console.log('RollupProxy Contract created at address:', rollupAddress) - if (isDevDeployment) { + if (!isDevDeployment) { console.log('Wait a minute before starting the contract verification') await sleep(1 * 60 * 1000) console.log( @@ -256,33 +248,6 @@ async function _getDevRollupConfig(feeToken: string) { ? ethers.BigNumber.from(process.env.L1_CHAIN_ID) : ethers.BigNumber.from(1337) - console.log('dev rollup config:', { - config: { - confirmPeriodBlocks: ethers.BigNumber.from('20'), - extraChallengeTimeBlocks: ethers.BigNumber.from('200'), - stakeToken: ethers.constants.AddressZero, - baseStake: ethers.utils.parseEther('1'), - wasmModuleRoot: wasmModuleRoot, - owner: ownerAddress, - loserStakeEscrow: ethers.constants.AddressZero, - chainId: parentChainId, - chainConfig: chainConfig, - sequencerInboxMaxTimeVariation: { - delayBlocks: ethers.BigNumber.from('5760'), - futureBlocks: ethers.BigNumber.from('12'), - delaySeconds: ethers.BigNumber.from('86400'), - futureSeconds: ethers.BigNumber.from('3600'), - }, - }, - validators: validators, - maxDataSize: _maxDataSize, - nativeToken: feeToken, - deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FER_PER_GAS, - batchPosters: batchPosters, - batchPosterManager: batchPosterManager, - }) - return { config: { confirmPeriodBlocks: ethers.BigNumber.from('20'), From 221633557bc1f8192d88d2e3071acaf864ad643f Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 26 Mar 2024 12:34:49 +0100 Subject: [PATCH 071/126] Remove unused array --- scripts/rollupCreation.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 3b78bae4..fb8933c6 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -51,13 +51,7 @@ export async function createRollup( } try { - let vals: boolean[] = [] - for (let i = 0; i < config.validators.length; i++) { - vals.push(true) - } - //// funds for deploying L2 factories - // 0.13 ETH is enough to deploy L2 factories via retryables. Excess is refunded let feeCost = ethers.utils.parseEther('0.13') if (feeToken != ethers.constants.AddressZero) { @@ -181,10 +175,8 @@ async function _getDevRollupConfig(feeToken: string) { : ethers.BigNumber.from(117964) // set up validators - const authorizeValidators = - process.env.AUTHORIZE_VALIDATORS !== undefined - ? parseInt(process.env.AUTHORIZE_VALIDATORS, 0) - : 0 + const authorizeValidators: number = + parseInt(process.env.AUTHORIZE_VALIDATORS as string, 0) || 0 const validators: string[] = [] for (let i = 1; i <= authorizeValidators; i++) { validators.push(ethers.Wallet.createRandom().address) From d0e0f4f1ed64a2c9f19c2b94c64b114b7f316d2a Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 26 Mar 2024 13:40:06 +0100 Subject: [PATCH 072/126] Store deployment addresses to a file --- .../deployCreatorAndCreateRollup.ts | 94 +++++++++++-------- scripts/rollupCreation.ts | 36 ++++++- 2 files changed, 86 insertions(+), 44 deletions(-) diff --git a/scripts/local-deployment/deployCreatorAndCreateRollup.ts b/scripts/local-deployment/deployCreatorAndCreateRollup.ts index dc77f123..59b9607f 100644 --- a/scripts/local-deployment/deployCreatorAndCreateRollup.ts +++ b/scripts/local-deployment/deployCreatorAndCreateRollup.ts @@ -2,7 +2,7 @@ import { ethers } from 'hardhat' import '@nomiclabs/hardhat-ethers' import { deployAllContracts } from '../deploymentUtils' import { createRollup } from '../rollupCreation' -import { sleep } from '../testSetup' +import { promises as fs } from 'fs' import { BigNumber } from 'ethers' async function main() { @@ -26,51 +26,63 @@ async function main() { ? ethers.BigNumber.from(process.env.MAX_DATA_SIZE) : ethers.BigNumber.from(117964) - try { - console.log('Deploy RollupCreator and templates') - const contracts = await deployAllContracts( - deployerWallet, - maxDataSize, - false + console.log('Deploy RollupCreator') + const contracts = await deployAllContracts(deployerWallet, maxDataSize, false) + + console.log('Set templates on the Rollup Creator') + await ( + await contracts.rollupCreator.setTemplates( + contracts.bridgeCreator.address, + contracts.osp.address, + contracts.challengeManager.address, + contracts.rollupAdmin.address, + contracts.rollupUser.address, + contracts.upgradeExecutor.address, + contracts.validatorUtils.address, + contracts.validatorWalletCreator.address, + contracts.deployHelper.address, + { gasLimit: BigNumber.from('300000') } ) + ).wait() - console.log('Set templates on the Rollup Creator') - await ( - await contracts.rollupCreator.setTemplates( - contracts.bridgeCreator.address, - contracts.osp.address, - contracts.challengeManager.address, - contracts.rollupAdmin.address, - contracts.rollupUser.address, - contracts.upgradeExecutor.address, - contracts.validatorUtils.address, - contracts.validatorWalletCreator.address, - contracts.deployHelper.address, - { gasLimit: BigNumber.from('300000') } - ) - ).wait() + // Create rollup + const chainId = (await deployerWallet.provider.getNetwork()).chainId + const feeToken = undefined - // Create rollup - console.log( - `Create rollup on top of chain ${ - (await deployerWallet.provider.getNetwork()).chainId - } using RollupCreator ${contracts.rollupCreator.address}` - ) + console.log( + 'Create rollup on top of chain', + chainId, + 'using RollupCreator', + contracts.rollupCreator.address + ) + const rollupCreationResult = await createRollup( + deployerWallet, + true, + contracts.rollupCreator.address, + feeToken + ) - const feeToken = undefined - await createRollup( - deployerWallet, - true, - contracts.rollupCreator.address, - feeToken - ) - console.log('Rollup created') - } catch (error) { - console.error( - 'Deployment failed:', - error instanceof Error ? error.message : error - ) + if (!rollupCreationResult) { + throw new Error('Rollup creation failed') } + + /// store deployment address + // parent deployment info + const parentDeploymentInfo = + process.env.PARENT_DEPLOYMENT_INFO !== undefined + ? process.env.PARENT_DEPLOYMENT_INFO + : 'deploy.json' + await fs.writeFile( + parentDeploymentInfo, + JSON.stringify(rollupCreationResult, null, 2), + 'utf8' + ) + + // get child deployment info + const childDeploymentInfo = + process.env.CHILD_DEPLOYMENT_INFO !== undefined + ? process.env.CHILD_DEPLOYMENT_INFO + : 'l2_chain_info.json' } main() diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index fb8933c6..e46308e0 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -16,6 +16,7 @@ interface RollupCreatedEvent { address: string args?: { rollupAddress: string + nativeToken: string inboxAddress: string outbox: string rollupEventInbox: string @@ -23,17 +24,30 @@ interface RollupCreatedEvent { adminProxy: string sequencerInbox: string bridge: string + upgradeExecutor: string validatorUtils: string validatorWalletCreator: string } } +interface RollupCreationResult { + Bridge: string + Inbox: string + SequencerInbox: string + DeployedAt: number + Rollup: string + NativeToken: string + UpgradeExecutor: string + ValidatorUtils: string + ValidatorWalletCreator: string +} + export async function createRollup( signer: Signer, isDevDeployment: boolean, rollupCreatorAddress: string, feeToken?: string -) { +): Promise { if (!rollupCreatorAbi) { throw new Error( 'You need to first run script to deploy and compile the contracts first' @@ -94,6 +108,7 @@ export async function createRollup( // Checking for RollupCreated event for new rollup address if (rollupCreatedEvent) { const rollupAddress = rollupCreatedEvent.args?.rollupAddress + const nativeToken = rollupCreatedEvent.args?.nativeToken const inboxAddress = rollupCreatedEvent.args?.inboxAddress const outbox = rollupCreatedEvent.args?.outbox const rollupEventInbox = rollupCreatedEvent.args?.rollupEventInbox @@ -101,6 +116,7 @@ export async function createRollup( const adminProxy = rollupCreatedEvent.args?.adminProxy const sequencerInbox = rollupCreatedEvent.args?.sequencerInbox const bridge = rollupCreatedEvent.args?.bridge + const upgradeExecutor = rollupCreatedEvent.args?.upgradeExecutor const validatorUtils = rollupCreatedEvent.args?.validatorUtils const validatorWalletCreator = rollupCreatedEvent.args?.validatorWalletCreator @@ -152,6 +168,18 @@ export async function createRollup( const blockNumber = createRollupReceipt.blockNumber console.log('All deployed at block number:', blockNumber) + + return { + Bridge: bridge, + Inbox: inboxAddress, + SequencerInbox: sequencerInbox, + DeployedAt: blockNumber, + Rollup: rollupAddress, + NativeToken: nativeToken, + UpgradeExecutor: upgradeExecutor, + ValidatorUtils: validatorUtils, + ValidatorWalletCreator: validatorWalletCreator, + } } else { console.error('RollupCreated event not found') } @@ -161,6 +189,8 @@ export async function createRollup( error instanceof Error ? error.message : error ) } + + return null } async function _getDevRollupConfig(feeToken: string) { @@ -236,8 +266,8 @@ async function _getDevRollupConfig(feeToken: string) { // set up parent chain id let parentChainId = - process.env.L1_CHAIN_ID !== undefined - ? ethers.BigNumber.from(process.env.L1_CHAIN_ID) + process.env.PARENT_CHAIN_ID !== undefined + ? ethers.BigNumber.from(process.env.PARENT_CHAIN_ID) : ethers.BigNumber.from(1337) return { From 2aa74dbcd5bdd24aa266ae0070de5412ae5ce806 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 26 Mar 2024 14:35:27 +0100 Subject: [PATCH 073/126] Add missing vars --- scripts/deploymentUtils.ts | 2 +- .../deployCreatorAndCreateRollup.ts | 31 ++++++++++++------- scripts/rollupCreation.ts | 28 +++++++++++++++-- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/scripts/deploymentUtils.ts b/scripts/deploymentUtils.ts index 43276a83..4c521270 100644 --- a/scripts/deploymentUtils.ts +++ b/scripts/deploymentUtils.ts @@ -237,7 +237,7 @@ export async function deployAllContracts( } // Check if we're deploying to an Arbitrum chain -async function _isRunningOnArbitrum(signer: any): Promise { +export async function _isRunningOnArbitrum(signer: any): Promise { const arbSys = ArbSys__factory.connect(ARB_SYS_ADDRESS, signer) try { await arbSys.arbOSVersion() diff --git a/scripts/local-deployment/deployCreatorAndCreateRollup.ts b/scripts/local-deployment/deployCreatorAndCreateRollup.ts index 59b9607f..6c0b4f13 100644 --- a/scripts/local-deployment/deployCreatorAndCreateRollup.ts +++ b/scripts/local-deployment/deployCreatorAndCreateRollup.ts @@ -6,6 +6,12 @@ import { promises as fs } from 'fs' import { BigNumber } from 'ethers' async function main() { + /// read env vars needed for deployment + let childChainName = process.env.CHILD_CHAIN_NAME as string + if (!childChainName) { + throw new Error('CHILD_CHAIN_NAME not set') + } + let deployerPrivKey = process.env.DEPLOYER_PRIVKEY as string if (!deployerPrivKey) { throw new Error('DEPLOYER_PRIVKEY not set') @@ -45,7 +51,7 @@ async function main() { ) ).wait() - // Create rollup + /// Create rollup const chainId = (await deployerWallet.provider.getNetwork()).chainId const feeToken = undefined @@ -55,34 +61,37 @@ async function main() { 'using RollupCreator', contracts.rollupCreator.address ) - const rollupCreationResult = await createRollup( + const result = await createRollup( deployerWallet, true, contracts.rollupCreator.address, feeToken ) - if (!rollupCreationResult) { + if (!result) { throw new Error('Rollup creation failed') } + const { rollupCreationResult, chainInfo } = result + /// store deployment address - // parent deployment info - const parentDeploymentInfo = - process.env.PARENT_DEPLOYMENT_INFO !== undefined - ? process.env.PARENT_DEPLOYMENT_INFO + // chain deployment info + const chainDeploymentInfo = + process.env.CHAIN_DEPLOYMENT_INFO !== undefined + ? process.env.CHAIN_DEPLOYMENT_INFO : 'deploy.json' await fs.writeFile( - parentDeploymentInfo, + chainDeploymentInfo, JSON.stringify(rollupCreationResult, null, 2), 'utf8' ) // get child deployment info - const childDeploymentInfo = - process.env.CHILD_DEPLOYMENT_INFO !== undefined - ? process.env.CHILD_DEPLOYMENT_INFO + const childChainInfo = + process.env.CHILD_CHAIN_INFO !== undefined + ? process.env.CHILD_CHAIN_INFO : 'l2_chain_info.json' + await fs.writeFile(childChainInfo, JSON.stringify(chainInfo, null, 2), 'utf8') } main() diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index e46308e0..17485fc8 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -7,6 +7,8 @@ import { BigNumber, Signer } from 'ethers' import { IERC20__factory } from '../build/types' import { sleep } from './testSetup' import { promises as fs } from 'fs' +import { Provider } from '@ethersproject/providers' +import { _isRunningOnArbitrum } from './deploymentUtils' // 1 gwei const MAX_FER_PER_GAS = BigNumber.from('1000000000') @@ -42,12 +44,23 @@ interface RollupCreationResult { ValidatorWalletCreator: string } +interface ChainInfo { + ChainName: string + ParentChainId: number + ParentChainIsArbitrum: boolean + ChainConfig: any + RollupAddresses: RollupCreationResult +} + export async function createRollup( signer: Signer, isDevDeployment: boolean, rollupCreatorAddress: string, feeToken?: string -): Promise { +): Promise<{ + rollupCreationResult: RollupCreationResult + chainInfo: ChainInfo +} | null> { if (!rollupCreatorAbi) { throw new Error( 'You need to first run script to deploy and compile the contracts first' @@ -169,7 +182,7 @@ export async function createRollup( const blockNumber = createRollupReceipt.blockNumber console.log('All deployed at block number:', blockNumber) - return { + const rollupCreationResult: RollupCreationResult = { Bridge: bridge, Inbox: inboxAddress, SequencerInbox: sequencerInbox, @@ -180,6 +193,17 @@ export async function createRollup( ValidatorUtils: validatorUtils, ValidatorWalletCreator: validatorWalletCreator, } + + + const chainInfo: ChainInfo = { + ChainName: 'dev-chain', + ParentChainId: deployParams.config.chainId.toNumber(), + ParentChainIsArbitrum: await _isRunningOnArbitrum(signer), + ChainConfig: deployParams.config, + RollupAddresses: rollupCreationResult, + } + + return { rollupCreationResult, chainInfo } } else { console.error('RollupCreated event not found') } From 46d901c7f3b7212c8cfbcf405019ed81a01d0646 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Wed, 27 Mar 2024 10:35:31 +0100 Subject: [PATCH 074/126] Make ouput JSONs schema identical to legacy one --- .../deployCreatorAndCreateRollup.ts | 3 +- scripts/rollupCreation.ts | 71 +++++++++++-------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/scripts/local-deployment/deployCreatorAndCreateRollup.ts b/scripts/local-deployment/deployCreatorAndCreateRollup.ts index 6c0b4f13..80ecf6ce 100644 --- a/scripts/local-deployment/deployCreatorAndCreateRollup.ts +++ b/scripts/local-deployment/deployCreatorAndCreateRollup.ts @@ -86,7 +86,8 @@ async function main() { 'utf8' ) - // get child deployment info + // child chain info + chainInfo['chain-name'] = childChainName const childChainInfo = process.env.CHILD_CHAIN_INFO !== undefined ? process.env.CHILD_CHAIN_INFO diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 17485fc8..e549a21b 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -7,7 +7,6 @@ import { BigNumber, Signer } from 'ethers' import { IERC20__factory } from '../build/types' import { sleep } from './testSetup' import { promises as fs } from 'fs' -import { Provider } from '@ethersproject/providers' import { _isRunningOnArbitrum } from './deploymentUtils' // 1 gwei @@ -33,23 +32,29 @@ interface RollupCreatedEvent { } interface RollupCreationResult { - Bridge: string - Inbox: string - SequencerInbox: string - DeployedAt: number - Rollup: string - NativeToken: string - UpgradeExecutor: string - ValidatorUtils: string - ValidatorWalletCreator: string + bridge: string + inbox: string + 'sequencer-inbox': string + 'deployed-at': number + rollup: string + 'native-token': string + 'upgrade-executor': string + 'validator-utils': string + 'validator-wallet-creator': string } interface ChainInfo { - ChainName: string - ParentChainId: number - ParentChainIsArbitrum: boolean - ChainConfig: any - RollupAddresses: RollupCreationResult + 'chain-name': string + 'parent-chain-id': number + 'parent-chain-is-arbitrum': boolean + 'chain-config': any + rollup: RollupCreationResult + 'sequencer-url': string + 'secondary-forwarding-target': string + 'feed-url': string + 'secondary-feed-url': string + 'das-index-url': string + 'has-genesis-state': boolean } export async function createRollup( @@ -183,24 +188,29 @@ export async function createRollup( console.log('All deployed at block number:', blockNumber) const rollupCreationResult: RollupCreationResult = { - Bridge: bridge, - Inbox: inboxAddress, - SequencerInbox: sequencerInbox, - DeployedAt: blockNumber, - Rollup: rollupAddress, - NativeToken: nativeToken, - UpgradeExecutor: upgradeExecutor, - ValidatorUtils: validatorUtils, - ValidatorWalletCreator: validatorWalletCreator, + bridge: bridge, + inbox: inboxAddress, + 'sequencer-inbox': sequencerInbox, + 'deployed-at': blockNumber, + rollup: rollupAddress, + 'native-token': nativeToken, + 'upgrade-executor': upgradeExecutor, + 'validator-utils': validatorUtils, + 'validator-wallet-creator': validatorWalletCreator, } - const chainInfo: ChainInfo = { - ChainName: 'dev-chain', - ParentChainId: deployParams.config.chainId.toNumber(), - ParentChainIsArbitrum: await _isRunningOnArbitrum(signer), - ChainConfig: deployParams.config, - RollupAddresses: rollupCreationResult, + 'chain-name': 'dev-chain', + 'parent-chain-id': deployParams.config.chainId.toNumber(), + 'parent-chain-is-arbitrum': await _isRunningOnArbitrum(signer), + 'sequencer-url': '', + 'secondary-forwarding-target': '', + 'feed-url': '', + 'secondary-feed-url': '', + 'das-index-url': '', + 'has-genesis-state': false, + 'chain-config': JSON.parse(deployParams.config.chainConfig), + rollup: rollupCreationResult, } return { rollupCreationResult, chainInfo } @@ -241,6 +251,7 @@ async function _getDevRollupConfig(feeToken: string) { process.env.CHILD_CHAIN_CONFIG_PATH !== undefined ? process.env.CHILD_CHAIN_CONFIG_PATH : 'l2_chain_config.json' + const chainConfig = await fs.readFile(childChainConfigPath, { encoding: 'utf8', }) From 668fc09111ed30c27cae4adea315f84e2e7b7755 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Wed, 27 Mar 2024 10:55:37 +0100 Subject: [PATCH 075/126] Make it array --- scripts/local-deployment/deployCreatorAndCreateRollup.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/local-deployment/deployCreatorAndCreateRollup.ts b/scripts/local-deployment/deployCreatorAndCreateRollup.ts index 80ecf6ce..31cf74e3 100644 --- a/scripts/local-deployment/deployCreatorAndCreateRollup.ts +++ b/scripts/local-deployment/deployCreatorAndCreateRollup.ts @@ -92,7 +92,11 @@ async function main() { process.env.CHILD_CHAIN_INFO !== undefined ? process.env.CHILD_CHAIN_INFO : 'l2_chain_info.json' - await fs.writeFile(childChainInfo, JSON.stringify(chainInfo, null, 2), 'utf8') + await fs.writeFile( + childChainInfo, + JSON.stringify([chainInfo], null, 2), + 'utf8' + ) } main() From 03c3793379c9f6514784de7e11f7f289f2938f7f Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Wed, 27 Mar 2024 14:15:13 +0100 Subject: [PATCH 076/126] Fix storing the chain ids --- .../local-deployment/deployCreatorAndCreateRollup.ts | 4 ++++ scripts/rollupCreation.ts | 10 ++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/local-deployment/deployCreatorAndCreateRollup.ts b/scripts/local-deployment/deployCreatorAndCreateRollup.ts index 31cf74e3..91bce7ac 100644 --- a/scripts/local-deployment/deployCreatorAndCreateRollup.ts +++ b/scripts/local-deployment/deployCreatorAndCreateRollup.ts @@ -22,6 +22,10 @@ async function main() { throw new Error('PARENT_CHAIN_RPC not set') } + if (!process.env.PARENT_CHAIN_ID) { + throw new Error('PARENT_CHAIN_ID not set') + } + const deployerWallet = new ethers.Wallet( deployerPrivKey, new ethers.providers.JsonRpcProvider(parentChainRpc) diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index e549a21b..4237c0b9 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -201,7 +201,7 @@ export async function createRollup( const chainInfo: ChainInfo = { 'chain-name': 'dev-chain', - 'parent-chain-id': deployParams.config.chainId.toNumber(), + 'parent-chain-id': +process.env.PARENT_CHAIN_ID!, 'parent-chain-is-arbitrum': await _isRunningOnArbitrum(signer), 'sequencer-url': '', 'secondary-forwarding-target': '', @@ -299,12 +299,6 @@ async function _getDevRollupConfig(feeToken: string) { } } - // set up parent chain id - let parentChainId = - process.env.PARENT_CHAIN_ID !== undefined - ? ethers.BigNumber.from(process.env.PARENT_CHAIN_ID) - : ethers.BigNumber.from(1337) - return { config: { confirmPeriodBlocks: ethers.BigNumber.from('20'), @@ -314,7 +308,7 @@ async function _getDevRollupConfig(feeToken: string) { wasmModuleRoot: wasmModuleRoot, owner: ownerAddress, loserStakeEscrow: ethers.constants.AddressZero, - chainId: parentChainId, + chainId: JSON.parse(chainConfig)['chainId'], chainConfig: chainConfig, genesisBlockNum: 0, sequencerInboxMaxTimeVariation: { From 3e27da844f16ee244e1862184044a806629b331f Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Thu, 28 Mar 2024 12:19:12 +0100 Subject: [PATCH 077/126] Read fee token address from env --- .../local-deployment/deployCreatorAndCreateRollup.ts | 10 ++++++++-- scripts/rollupCreation.ts | 6 +----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/local-deployment/deployCreatorAndCreateRollup.ts b/scripts/local-deployment/deployCreatorAndCreateRollup.ts index 91bce7ac..0b7f7367 100644 --- a/scripts/local-deployment/deployCreatorAndCreateRollup.ts +++ b/scripts/local-deployment/deployCreatorAndCreateRollup.ts @@ -36,6 +36,14 @@ async function main() { ? ethers.BigNumber.from(process.env.MAX_DATA_SIZE) : ethers.BigNumber.from(117964) + /// get fee token address, if undefined use address(0) to have ETH as fee token + let feeToken = process.env.FEE_TOKEN_ADDRESS as string + if (!feeToken) { + feeToken = ethers.constants.AddressZero + } + console.log('Fee token address:', feeToken) + + /// deploy templates and rollup creator console.log('Deploy RollupCreator') const contracts = await deployAllContracts(deployerWallet, maxDataSize, false) @@ -57,8 +65,6 @@ async function main() { /// Create rollup const chainId = (await deployerWallet.provider.getNetwork()).chainId - const feeToken = undefined - console.log( 'Create rollup on top of chain', chainId, diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 4237c0b9..3e1d300c 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -61,7 +61,7 @@ export async function createRollup( signer: Signer, isDevDeployment: boolean, rollupCreatorAddress: string, - feeToken?: string + feeToken: string ): Promise<{ rollupCreationResult: RollupCreationResult chainInfo: ChainInfo @@ -78,10 +78,6 @@ export async function createRollup( signer ) - if (!feeToken) { - feeToken = ethers.constants.AddressZero - } - try { //// funds for deploying L2 factories // 0.13 ETH is enough to deploy L2 factories via retryables. Excess is refunded From 5a40dc749eda27df0346e374199c7dc5bc3c8b81 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Thu, 28 Mar 2024 18:41:31 +0100 Subject: [PATCH 078/126] Not needed anymore --- hardhat.config.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 420ce87a..401a29b3 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -138,12 +138,6 @@ module.exports = { geth: { url: 'http://localhost:8545', }, - testnode_l1: { - url: 'http://geth:8545', - accounts: process.env['DEPLOYER_PRIVKEY'] - ? [process.env['DEPLOYER_PRIVKEY']] - : [], - }, }, etherscan: { apiKey: { From d4020011f8982828d320706cf967ec01116fc3f7 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 4 Apr 2024 02:48:12 -0600 Subject: [PATCH 079/126] cached init gas --- src/precompiles/ArbWasm.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 38972bfa..5bc49beb 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -36,7 +36,11 @@ interface ArbWasm { /// @notice gets the cost to invoke the program (not including minInitGas) /// @return gas the amount of gas - function programInitGas(address program) external view returns (uint32 gas); + /// @return gasWhenCached the amount of gas if the program was recently used + function programInitGas(address program) + external + view + returns (uint16 gas, uint16 gasWhenCached); /// @notice gets the memory footprint of the program at the given address in pages /// @return footprint the memory footprint of program in pages (reverts for EVM contracts) From b3c16ec191c4c098dc40e5b60059eaa7548b3efa Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 6 Apr 2024 17:08:19 -0600 Subject: [PATCH 080/126] min cached init gas --- src/precompiles/ArbOwner.sol | 6 ++++-- src/precompiles/ArbWasm.sol | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 7bfa3c35..54f15c3b 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -111,8 +111,10 @@ interface ArbOwner { /// @notice sets the maximum number of pages a wasm may allocate function setWasmPageLimit(uint16 limit) external; - /// @notice sets the minimum cost to invoke a program - function setWasmMinInitGas(uint16 gas) external; + /// @notice sets the minimum costs to invoke a program + /// @param gas amount of gas paid in increments of 256 when not the program is not cached + /// @param cached amount of gas paid in increments of 64 when the program is cached + function setWasmMinInitGas(uint8 gas, uint16 cached) external; /// @notice sets the number of days after which programs deactivate function setWasmExpiryDays(uint16 _days) external; diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 5bc49beb..01cbdb12 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -74,9 +74,10 @@ interface ArbWasm { /// @return limit the number of pages function pageLimit() external view returns (uint16 limit); - /// @notice gets the minimum cost to invoke a program - /// @return gas amount of gas - function minInitGas() external view returns (uint16 gas); + /// @notice gets the minimum costs to invoke a program + /// @return gas amount of gas in increments of 256 when not cached + /// @return cached amount of gas in increments of 64 when cached + function minInitGas() external view returns (uint8 gas, uint8 cached); /// @notice gets the number of days after which programs deactivate /// @return _days the number of days From 18e2e7ff72d8aa572c669ba18818f53b114eaca4 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 6 Apr 2024 21:50:41 -0600 Subject: [PATCH 081/126] ArbWasmCache --- src/precompiles/ArbOwner.sol | 24 +++++++----- src/precompiles/ArbWasm.sol | 2 +- src/precompiles/ArbWasmCache.sol | 63 ++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 src/precompiles/ArbWasmCache.sol diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 54f15c3b..7dffe017 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -92,36 +92,42 @@ interface ArbOwner { /// @notice Releases surplus funds from L1PricerFundsPoolAddress for use function releaseL1PricerSurplusFunds(uint256 maxWeiToRelease) external returns (uint256); - /// @notice sets the amount of ink 1 gas buys + /// @notice Sets the amount of ink 1 gas buys /// @param price the conversion rate (must fit in a uint24) function setInkPrice(uint32 price) external; - /// @notice sets the maximum depth (in wasm words) a wasm stack may grow + /// @notice Sets the maximum depth (in wasm words) a wasm stack may grow function setWasmMaxStackDepth(uint32 depth) external; - /// @notice sets the number of free wasm pages a tx gets + /// @notice Sets the number of free wasm pages a tx gets function setWasmFreePages(uint16 pages) external; - /// @notice sets the base cost of each additional wasm page + /// @notice Sets the base cost of each additional wasm page function setWasmPageGas(uint16 gas) external; - /// @notice sets the ramp that drives exponential wasm memory costs + /// @notice Sets the ramp that drives exponential wasm memory costs function setWasmPageRamp(uint64 ramp) external; - /// @notice sets the maximum number of pages a wasm may allocate + /// @notice Sets the maximum number of pages a wasm may allocate function setWasmPageLimit(uint16 limit) external; - /// @notice sets the minimum costs to invoke a program + /// @notice Sets the minimum costs to invoke a program /// @param gas amount of gas paid in increments of 256 when not the program is not cached /// @param cached amount of gas paid in increments of 64 when the program is cached function setWasmMinInitGas(uint8 gas, uint16 cached) external; - /// @notice sets the number of days after which programs deactivate + /// @notice Sets the number of days after which programs deactivate function setWasmExpiryDays(uint16 _days) external; - /// @notice sets the age a program must be to perform a keepalive + /// @notice Sets the age a program must be to perform a keepalive function setWasmKeepaliveDays(uint16 _days) external; + /// @notice Adds account as a wasm cache manager + function addWasmCacheManager(address manager) external; + + /// @notice Removes account from the list of wasm cache managers + function removeWasmCacheManager(address manager) external; + /// @notice Sets serialized chain config in ArbOS state function setChainConfig(string calldata chainConfig) external; diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 01cbdb12..d1222eb7 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -1,5 +1,5 @@ // Copyright 2022-2024, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.4.21 <0.9.0; diff --git a/src/precompiles/ArbWasmCache.sol b/src/precompiles/ArbWasmCache.sol new file mode 100644 index 00000000..127a8f2f --- /dev/null +++ b/src/precompiles/ArbWasmCache.sol @@ -0,0 +1,63 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity >=0.4.21 <0.9.0; + +/** + * @title Methods for managing Stylus caches + * @notice Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000072. + */ +interface ArbWasmCache { + /// @notice See if the user is a cache manager. + function isCacheManager(address manager) external view returns (bool); + + /// @notice Gets the trie table params. + /// @return bits size of the cache as a power of 2. + /// @return reads the number of items to read when determining inclusion. + function trieTableParams() external view returns (uint8 bits, uint8 reads); + + /// @notice Configures the trie table. + /// @notice Caller must be a cache manager or chain owner. + /// @param bits size of the cache as a power of 2. + /// @param reads the number of items to read when determining inclusion. + function setTrieTableParams(uint8 bits, uint8 reads) external; + + /// @notice Caches all programs with the given codehash. + /// @notice Caller must be a cache manager or chain owner. + /// @notice If you're looking for how to bid for position, interact with the chain's cache manager contract. + function cacheCodehash(bytes32 codehash) external; + + /// @notice Evicts all programs with the given codehash. + /// @notice Caller must be a cache manager or chain owner. + function evictCodehash(bytes32 codehash) external; + + /// @notice Reads the trie table record at the given offset. + /// @notice Caller must be a cache manager or chain owner. + /// @param offset the record's offset. + /// @return slot the cached slot. + /// @return program the slot's account. + /// @return next the next record to read when determining inclusion. + function ReadTrieTableRecord(uint64 offset) + external + view + returns ( + uint256 slot, + address program, + uint64 next + ); + + /// @notice Writes a trie table record. + /// @notice Caller must be a cache manager or chain owner. + /// @notice If you're looking for how to bid for position, interact with the chain's cache manager contract. + /// @param slot the slot to cache. + /// @param program the slot's account. + /// @param next the next record to read when determining inclusion. + /// @param offset the record's offset. + function WriteTrieTableRecord( + uint256 slot, + address program, + uint64 next, + uint64 offset + ) external; +} From 65e805a3b16a2eeb5a5d9d7d9a368cb8dd98cf79 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 6 Apr 2024 22:13:14 -0600 Subject: [PATCH 082/126] codehashIsCached precompile method --- src/precompiles/ArbWasmCache.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/precompiles/ArbWasmCache.sol b/src/precompiles/ArbWasmCache.sol index 127a8f2f..71be008d 100644 --- a/src/precompiles/ArbWasmCache.sol +++ b/src/precompiles/ArbWasmCache.sol @@ -24,6 +24,7 @@ interface ArbWasmCache { function setTrieTableParams(uint8 bits, uint8 reads) external; /// @notice Caches all programs with the given codehash. + /// @notice Reverts if the programs have expired. /// @notice Caller must be a cache manager or chain owner. /// @notice If you're looking for how to bid for position, interact with the chain's cache manager contract. function cacheCodehash(bytes32 codehash) external; @@ -32,6 +33,9 @@ interface ArbWasmCache { /// @notice Caller must be a cache manager or chain owner. function evictCodehash(bytes32 codehash) external; + /// @notice Gets whether a program is cached. Note that the program may be expired. + function codehashIsCached(bytes32 codehash) external view returns (bool); + /// @notice Reads the trie table record at the given offset. /// @notice Caller must be a cache manager or chain owner. /// @param offset the record's offset. From 70e203bc1274915b4d7886ebd2265d5db762b14e Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 7 Apr 2024 23:53:53 -0600 Subject: [PATCH 083/126] simple cache manager --- src/mocks/SimpleCacheManager.sol | 26 ++++++++++++++++++++++++++ src/precompiles/ArbWasm.sol | 1 + 2 files changed, 27 insertions(+) create mode 100644 src/mocks/SimpleCacheManager.sol diff --git a/src/mocks/SimpleCacheManager.sol b/src/mocks/SimpleCacheManager.sol new file mode 100644 index 00000000..ed429e8e --- /dev/null +++ b/src/mocks/SimpleCacheManager.sol @@ -0,0 +1,26 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.0; +import "../precompiles/ArbWasmCache.sol"; + +contract SimpleCacheManager { + function cacheProgram(address program) external { + ArbWasmCache(address(0x72)).cacheCodehash(codehash(program)); + } + + function evictProgram(address program) external { + ArbWasmCache(address(0x72)).evictCodehash(codehash(program)); + } + + function setParams(uint8 bits, uint8 reads) external { + ArbWasmCache(address(0x72)).setTrieTableParams(bits, reads); + } + + function codehash(address program) internal returns (bytes32 codehash) { + assembly { + codehash := extcodehash(program) + } + } +} diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index d1222eb7..be53758a 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -96,6 +96,7 @@ interface ArbWasm { ); event ProgramLifetimeExtended(bytes32 indexed codehash, uint256 dataFee); + error ProgramNotWasm(); error ProgramNotActivated(); error ProgramNeedsUpgrade(uint16 version, uint16 stylusVersion); error ProgramExpired(uint64 ageInSeconds); From b00d78188f874c3a8ef53077072608b07939800a Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 8 Apr 2024 02:55:13 -0600 Subject: [PATCH 084/126] precompile list method & events --- src/precompiles/ArbWasmCache.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/precompiles/ArbWasmCache.sol b/src/precompiles/ArbWasmCache.sol index 71be008d..15557067 100644 --- a/src/precompiles/ArbWasmCache.sol +++ b/src/precompiles/ArbWasmCache.sol @@ -12,6 +12,10 @@ interface ArbWasmCache { /// @notice See if the user is a cache manager. function isCacheManager(address manager) external view returns (bool); + /// @notice Retrieve all address managers. + /// @return managers the list of managers. + function allCacheManagers() external view returns (address[] memory managers); + /// @notice Gets the trie table params. /// @return bits size of the cache as a power of 2. /// @return reads the number of items to read when determining inclusion. @@ -64,4 +68,13 @@ interface ArbWasmCache { uint64 next, uint64 offset ) external; + + event UpdateProgramCache(address indexed manager, bytes32 indexed codehash, bool cached); + event UpdateTrieTable( + address indexed manager, + uint256 indexed slot, + address indexed program, + bool cached + ); + event UpdateTrieTableParams(address indexed manager, uint8 bits, uint8 reads); } From f16d0963fddaac77deecce364737a57a2beea023 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 8 Apr 2024 21:30:26 -0600 Subject: [PATCH 085/126] remove trie table --- src/mocks/SimpleCacheManager.sol | 4 --- src/precompiles/ArbWasmCache.sol | 47 -------------------------------- 2 files changed, 51 deletions(-) diff --git a/src/mocks/SimpleCacheManager.sol b/src/mocks/SimpleCacheManager.sol index ed429e8e..897d3aee 100644 --- a/src/mocks/SimpleCacheManager.sol +++ b/src/mocks/SimpleCacheManager.sol @@ -14,10 +14,6 @@ contract SimpleCacheManager { ArbWasmCache(address(0x72)).evictCodehash(codehash(program)); } - function setParams(uint8 bits, uint8 reads) external { - ArbWasmCache(address(0x72)).setTrieTableParams(bits, reads); - } - function codehash(address program) internal returns (bytes32 codehash) { assembly { codehash := extcodehash(program) diff --git a/src/precompiles/ArbWasmCache.sol b/src/precompiles/ArbWasmCache.sol index 15557067..7d1b8380 100644 --- a/src/precompiles/ArbWasmCache.sol +++ b/src/precompiles/ArbWasmCache.sol @@ -16,17 +16,6 @@ interface ArbWasmCache { /// @return managers the list of managers. function allCacheManagers() external view returns (address[] memory managers); - /// @notice Gets the trie table params. - /// @return bits size of the cache as a power of 2. - /// @return reads the number of items to read when determining inclusion. - function trieTableParams() external view returns (uint8 bits, uint8 reads); - - /// @notice Configures the trie table. - /// @notice Caller must be a cache manager or chain owner. - /// @param bits size of the cache as a power of 2. - /// @param reads the number of items to read when determining inclusion. - function setTrieTableParams(uint8 bits, uint8 reads) external; - /// @notice Caches all programs with the given codehash. /// @notice Reverts if the programs have expired. /// @notice Caller must be a cache manager or chain owner. @@ -40,41 +29,5 @@ interface ArbWasmCache { /// @notice Gets whether a program is cached. Note that the program may be expired. function codehashIsCached(bytes32 codehash) external view returns (bool); - /// @notice Reads the trie table record at the given offset. - /// @notice Caller must be a cache manager or chain owner. - /// @param offset the record's offset. - /// @return slot the cached slot. - /// @return program the slot's account. - /// @return next the next record to read when determining inclusion. - function ReadTrieTableRecord(uint64 offset) - external - view - returns ( - uint256 slot, - address program, - uint64 next - ); - - /// @notice Writes a trie table record. - /// @notice Caller must be a cache manager or chain owner. - /// @notice If you're looking for how to bid for position, interact with the chain's cache manager contract. - /// @param slot the slot to cache. - /// @param program the slot's account. - /// @param next the next record to read when determining inclusion. - /// @param offset the record's offset. - function WriteTrieTableRecord( - uint256 slot, - address program, - uint64 next, - uint64 offset - ) external; - event UpdateProgramCache(address indexed manager, bytes32 indexed codehash, bool cached); - event UpdateTrieTable( - address indexed manager, - uint256 indexed slot, - address indexed program, - bool cached - ); - event UpdateTrieTableParams(address indexed manager, uint8 bits, uint8 reads); } From 7ef4c03aee318ab3ea9caf8aed5cb5c52817b434 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 8 Apr 2024 23:55:40 -0600 Subject: [PATCH 086/126] add actual impl --- package.json | 3 +- src/mocks/CacheManager.sol | 139 +++++++++++++++++++++++++++++++ src/mocks/SimpleCacheManager.sol | 6 +- src/precompiles/ArbWasm.sol | 7 +- yarn.lock | 5 ++ 5 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 src/mocks/CacheManager.sol diff --git a/package.json b/package.json index 6f649956..539190fc 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "@offchainlabs/upgrade-executor": "1.1.0-beta.0", "@openzeppelin/contracts": "4.5.0", "@openzeppelin/contracts-upgradeable": "4.5.2", - "patch-package": "^6.4.7" + "patch-package": "^6.4.7", + "solady": "^0.0.182" }, "private": false, "devDependencies": { diff --git a/src/mocks/CacheManager.sol b/src/mocks/CacheManager.sol new file mode 100644 index 00000000..30c2cbca --- /dev/null +++ b/src/mocks/CacheManager.sol @@ -0,0 +1,139 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.0; +import "../precompiles/ArbOwnerPublic.sol"; +import "../precompiles/ArbWasm.sol"; +import "../precompiles/ArbWasmCache.sol"; +import "solady/src/utils/MinHeapLib.sol"; + +contract CacheManager { + using MinHeapLib for MinHeapLib.Heap; + + MinHeapLib.Heap bids; + Entry[] entries; + + uint64 cacheSize; + uint64 queueSize; + uint64 decay; + + struct Entry { + bytes32 code; + uint256 paid; + uint64 size; + address payable bidder; + } + + constructor(uint64 initCacheSize, uint64 initDecay) { + cacheSize = initCacheSize; + decay = initDecay; + } + + /// Sets the intended cache size. Note that the queue may temporarily be larger. + function setCacheSize(uint64 newSize) external { + _requireOwner(); + cacheSize = newSize; + } + + /// Evicts all programs in the cache and returns all payments. + function evictAll() external { + _requireOwner(); + + while (bids.length() != 0) { + uint64 index = _getIndex(bids.pop()); + Entry memory entry = entries[index]; + _evict(entry.code); + + // return funds to user + entry.bidder.call{value: entry.paid, gas: 0}; + delete entries[index]; + } + queueSize = 0; + } + + function placeBid(bytes32 codehash) external payable { + require(!_isCached(codehash), "ALREADY_CACHED"); + + // discount historical bids by the number of seconds + uint bid = msg.value + block.timestamp * uint(decay); + uint64 asm = _asmSize(codehash); + + Entry memory candidate = Entry({ + size: asm, + code: codehash, + paid: msg.value, + bidder: payable(msg.sender) + }); + + uint64 index; + + // if there's space, append to the end + if (queueSize + asm < cacheSize) { + index = uint64(entries.length); + bid = _setIndex(bid, index); + + bids.push(bid); + queueSize += asm; + _cache(codehash); + entries[index] = candidate; + return; + } + + // pop entries until we have enough space + while (true) { + uint min = bids.root(); + index = _getIndex(min); + bid = _setIndex(bid, index); // make both have same index + + require(bid > min, "BID_TOO_SMALL"); + + // evict the entry + Entry memory entry = entries[index]; + _evict(entry.code); + queueSize -= entry.size; + bids.pop(); + delete entries[index]; + + if (queueSize + asm < cacheSize) { + break; + } + } + + // replace the min with the new bid + _cache(codehash); + entries[index] = candidate; + bids.push(bid); + } + + function _requireOwner() internal view { + bool owner = ArbOwnerPublic(address(0x6b)).isChainOwner(address(msg.sender)); + require(owner, "NOT_OWNER"); + } + + function _getIndex(uint info) internal pure returns(uint64) { + return uint64(info >> 192); + } + + function _setIndex(uint info, uint64 index) internal pure returns(uint) { + uint mask = 0xffffffffffffffffffffffffffffffffffffffffffffffff; + return (info & mask) | (uint(index) << 192); + } + + function _asmSize(bytes32 codehash) internal view returns(uint64) { + uint64 size = ArbWasm(address(0x71)).codehashAsmSize(codehash); + return size >= 4096 ? size : 4096; // pretend it's at least 4Kb + } + + function _isCached(bytes32 codehash) internal view returns(bool) { + return ArbWasmCache(address(0x72)).codehashIsCached(codehash); + } + + function _cache(bytes32 codehash) internal { + ArbWasmCache(address(0x72)).cacheCodehash(codehash); + } + + function _evict(bytes32 codehash) internal { + ArbWasmCache(address(0x72)).evictCodehash(codehash); + } +} diff --git a/src/mocks/SimpleCacheManager.sol b/src/mocks/SimpleCacheManager.sol index 897d3aee..22b99533 100644 --- a/src/mocks/SimpleCacheManager.sol +++ b/src/mocks/SimpleCacheManager.sol @@ -1,4 +1,4 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 @@ -14,9 +14,9 @@ contract SimpleCacheManager { ArbWasmCache(address(0x72)).evictCodehash(codehash(program)); } - function codehash(address program) internal returns (bytes32 codehash) { + function codehash(address program) internal view returns (bytes32 hash) { assembly { - codehash := extcodehash(program) + hash := extcodehash(program) } } } diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index be53758a..30346ce2 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -30,6 +30,11 @@ interface ArbWasm { /// Reverts if too soon or if the program is not up to date. function codehashKeepalive(bytes32 codehash) external payable; + /// @notice gets a program's asm size. + /// Reverts if program is not active. + /// @return size the size in bytes + function codehashAsmSize(bytes32 codehash) external view returns (uint64 size); + /// @notice gets the stylus version the program was most recently activated against /// @return version the program version (reverts for EVM contracts) function programVersion(address program) external view returns (uint16 version); @@ -41,7 +46,7 @@ interface ArbWasm { external view returns (uint16 gas, uint16 gasWhenCached); - + /// @notice gets the memory footprint of the program at the given address in pages /// @return footprint the memory footprint of program in pages (reverts for EVM contracts) function programMemoryFootprint(address program) external view returns (uint16 footprint); diff --git a/yarn.lock b/yarn.lock index 960d60e7..d613d77a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10227,6 +10227,11 @@ sol2uml@2.2.0: js-graph-algorithms "^1.0.18" klaw "^4.0.1" +solady@^0.0.182: + version "0.0.182" + resolved "https://registry.yarnpkg.com/solady/-/solady-0.0.182.tgz#bd8c47f128a3a752358ad052782773966d74c400" + integrity sha512-FW6xo1akJoYpkXMzu58/56FcNU3HYYNamEbnFO3iSibXk0nSHo0DV2Gu/zI3FPg3So5CCX6IYli1TT1IWATnvg== + solc@0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/solc/-/solc-0.5.7.tgz#d84697ac5cc63d9b2139bfb349cec64b64861cdc" From bf58412286355d07da431d71fb1e5b1518dfc515 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 8 Apr 2024 23:57:24 -0600 Subject: [PATCH 087/126] format --- src/mocks/CacheManager.sol | 36 ++++++++++++++++++------------------ src/precompiles/ArbWasm.sol | 4 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/mocks/CacheManager.sol b/src/mocks/CacheManager.sol index 30c2cbca..50501cf3 100644 --- a/src/mocks/CacheManager.sol +++ b/src/mocks/CacheManager.sol @@ -17,14 +17,14 @@ contract CacheManager { uint64 cacheSize; uint64 queueSize; uint64 decay; - + struct Entry { bytes32 code; uint256 paid; uint64 size; address payable bidder; } - + constructor(uint64 initCacheSize, uint64 initDecay) { cacheSize = initCacheSize; decay = initDecay; @@ -39,7 +39,7 @@ contract CacheManager { /// Evicts all programs in the cache and returns all payments. function evictAll() external { _requireOwner(); - + while (bids.length() != 0) { uint64 index = _getIndex(bids.pop()); Entry memory entry = entries[index]; @@ -51,12 +51,12 @@ contract CacheManager { } queueSize = 0; } - + function placeBid(bytes32 codehash) external payable { require(!_isCached(codehash), "ALREADY_CACHED"); // discount historical bids by the number of seconds - uint bid = msg.value + block.timestamp * uint(decay); + uint256 bid = msg.value + block.timestamp * uint256(decay); uint64 asm = _asmSize(codehash); Entry memory candidate = Entry({ @@ -82,7 +82,7 @@ contract CacheManager { // pop entries until we have enough space while (true) { - uint min = bids.root(); + uint256 min = bids.root(); index = _getIndex(min); bid = _setIndex(bid, index); // make both have same index @@ -101,7 +101,7 @@ contract CacheManager { } // replace the min with the new bid - _cache(codehash); + _cache(codehash); entries[index] = candidate; bids.push(bid); } @@ -110,29 +110,29 @@ contract CacheManager { bool owner = ArbOwnerPublic(address(0x6b)).isChainOwner(address(msg.sender)); require(owner, "NOT_OWNER"); } - - function _getIndex(uint info) internal pure returns(uint64) { + + function _getIndex(uint256 info) internal pure returns (uint64) { return uint64(info >> 192); } - function _setIndex(uint info, uint64 index) internal pure returns(uint) { - uint mask = 0xffffffffffffffffffffffffffffffffffffffffffffffff; - return (info & mask) | (uint(index) << 192); + function _setIndex(uint256 info, uint64 index) internal pure returns (uint256) { + uint256 mask = 0xffffffffffffffffffffffffffffffffffffffffffffffff; + return (info & mask) | (uint256(index) << 192); } - - function _asmSize(bytes32 codehash) internal view returns(uint64) { + + function _asmSize(bytes32 codehash) internal view returns (uint64) { uint64 size = ArbWasm(address(0x71)).codehashAsmSize(codehash); return size >= 4096 ? size : 4096; // pretend it's at least 4Kb } - - function _isCached(bytes32 codehash) internal view returns(bool) { + + function _isCached(bytes32 codehash) internal view returns (bool) { return ArbWasmCache(address(0x72)).codehashIsCached(codehash); } - + function _cache(bytes32 codehash) internal { ArbWasmCache(address(0x72)).cacheCodehash(codehash); } - + function _evict(bytes32 codehash) internal { ArbWasmCache(address(0x72)).evictCodehash(codehash); } diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 30346ce2..646d8e96 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -34,7 +34,7 @@ interface ArbWasm { /// Reverts if program is not active. /// @return size the size in bytes function codehashAsmSize(bytes32 codehash) external view returns (uint64 size); - + /// @notice gets the stylus version the program was most recently activated against /// @return version the program version (reverts for EVM contracts) function programVersion(address program) external view returns (uint16 version); @@ -46,7 +46,7 @@ interface ArbWasm { external view returns (uint16 gas, uint16 gasWhenCached); - + /// @notice gets the memory footprint of the program at the given address in pages /// @return footprint the memory footprint of program in pages (reverts for EVM contracts) function programMemoryFootprint(address program) external view returns (uint16 footprint); From 52bd0075e65cd77742826a63ad96edfe3d057a2c Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 9 Apr 2024 00:10:08 -0600 Subject: [PATCH 088/126] 32-bit asm size --- src/mocks/CacheManager.sol | 4 ++-- src/precompiles/ArbWasm.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mocks/CacheManager.sol b/src/mocks/CacheManager.sol index 50501cf3..71fb5a43 100644 --- a/src/mocks/CacheManager.sol +++ b/src/mocks/CacheManager.sol @@ -121,8 +121,8 @@ contract CacheManager { } function _asmSize(bytes32 codehash) internal view returns (uint64) { - uint64 size = ArbWasm(address(0x71)).codehashAsmSize(codehash); - return size >= 4096 ? size : 4096; // pretend it's at least 4Kb + uint32 size = ArbWasm(address(0x71)).codehashAsmSize(codehash); + return uint64(size >= 4096 ? size : 4096); // pretend it's at least 4Kb } function _isCached(bytes32 codehash) internal view returns (bool) { diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 646d8e96..5871fcb7 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -33,7 +33,7 @@ interface ArbWasm { /// @notice gets a program's asm size. /// Reverts if program is not active. /// @return size the size in bytes - function codehashAsmSize(bytes32 codehash) external view returns (uint64 size); + function codehashAsmSize(bytes32 codehash) external view returns (uint32 size); /// @notice gets the stylus version the program was most recently activated against /// @return version the program version (reverts for EVM contracts) From 191062a4b4edb694e3f8ef69c68676bccb755b6a Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 9 Apr 2024 01:09:23 -0600 Subject: [PATCH 089/126] better idioms & API --- src/mocks/CacheManager.sol | 87 +++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/src/mocks/CacheManager.sol b/src/mocks/CacheManager.sol index 71fb5a43..4ed5351f 100644 --- a/src/mocks/CacheManager.sol +++ b/src/mocks/CacheManager.sol @@ -11,18 +11,25 @@ import "solady/src/utils/MinHeapLib.sol"; contract CacheManager { using MinHeapLib for MinHeapLib.Heap; - MinHeapLib.Heap bids; - Entry[] entries; + ArbOwnerPublic internal constant ARB_OWNER_PUBLIC = ArbOwnerPublic(address(0x6b)); + ArbWasm internal constant ARB_WASM = ArbWasm(address(0x71)); + ArbWasmCache internal constant ARB_WASM_CACHE = ArbWasmCache(address(0x72)); - uint64 cacheSize; - uint64 queueSize; - uint64 decay; + MinHeapLib.Heap internal bids; + Entry[] public entries; + + uint64 public cacheSize; + uint64 public queueSize; + uint64 public decay; + + error NotChainOwner(address sender); + error AsmTooLarge(uint256 asm, uint256 cacheSize); + error AlreadyCached(bytes32 codehash); + error BidTooSmall(uint256 bid, uint256 min); struct Entry { bytes32 code; - uint256 paid; uint64 size; - address payable bidder; } constructor(uint64 initCacheSize, uint64 initDecay) { @@ -30,41 +37,53 @@ contract CacheManager { decay = initDecay; } + modifier onlyOwner() { + if (!ARB_OWNER_PUBLIC.isChainOwner(msg.sender)) { + revert NotChainOwner(msg.sender); + } + _; + } + /// Sets the intended cache size. Note that the queue may temporarily be larger. - function setCacheSize(uint64 newSize) external { - _requireOwner(); + function setCacheSize(uint64 newSize) external onlyOwner { cacheSize = newSize; } - /// Evicts all programs in the cache and returns all payments. - function evictAll() external { - _requireOwner(); - + /// Evicts all programs in the cache. + function evictAll() external onlyOwner { while (bids.length() != 0) { uint64 index = _getIndex(bids.pop()); Entry memory entry = entries[index]; _evict(entry.code); - - // return funds to user - entry.bidder.call{value: entry.paid, gas: 0}; delete entries[index]; } queueSize = 0; } + function sweepFunds() external { + (bool success, bytes memory data) = ARB_OWNER_PUBLIC.getNetworkFeeAccount().call{ + value: address(this).balance + }(""); + if (!success) { + assembly { + revert(add(data, 32), mload(data)) + } + } + } + function placeBid(bytes32 codehash) external payable { - require(!_isCached(codehash), "ALREADY_CACHED"); + if (_isCached(codehash)) { + revert AlreadyCached(codehash); + } // discount historical bids by the number of seconds uint256 bid = msg.value + block.timestamp * uint256(decay); uint64 asm = _asmSize(codehash); + if (asm > cacheSize) { + revert AsmTooLarge(asm, cacheSize); + } - Entry memory candidate = Entry({ - size: asm, - code: codehash, - paid: msg.value, - bidder: payable(msg.sender) - }); + Entry memory candidate = Entry({size: asm, code: codehash}); uint64 index; @@ -85,8 +104,9 @@ contract CacheManager { uint256 min = bids.root(); index = _getIndex(min); bid = _setIndex(bid, index); // make both have same index - - require(bid > min, "BID_TOO_SMALL"); + if (bid > min) { + revert BidTooSmall(_clearIndex(bid), _clearIndex(min)); + } // evict the entry Entry memory entry = entries[index]; @@ -106,11 +126,6 @@ contract CacheManager { bids.push(bid); } - function _requireOwner() internal view { - bool owner = ArbOwnerPublic(address(0x6b)).isChainOwner(address(msg.sender)); - require(owner, "NOT_OWNER"); - } - function _getIndex(uint256 info) internal pure returns (uint64) { return uint64(info >> 192); } @@ -120,20 +135,24 @@ contract CacheManager { return (info & mask) | (uint256(index) << 192); } + function _clearIndex(uint256 info) internal pure returns (uint256) { + return _setIndex(info, 0); + } + function _asmSize(bytes32 codehash) internal view returns (uint64) { - uint32 size = ArbWasm(address(0x71)).codehashAsmSize(codehash); + uint32 size = ARB_WASM.codehashAsmSize(codehash); return uint64(size >= 4096 ? size : 4096); // pretend it's at least 4Kb } function _isCached(bytes32 codehash) internal view returns (bool) { - return ArbWasmCache(address(0x72)).codehashIsCached(codehash); + return ARB_WASM_CACHE.codehashIsCached(codehash); } function _cache(bytes32 codehash) internal { - ArbWasmCache(address(0x72)).cacheCodehash(codehash); + ARB_WASM_CACHE.cacheCodehash(codehash); } function _evict(bytes32 codehash) internal { - ArbWasmCache(address(0x72)).evictCodehash(codehash); + ARB_WASM_CACHE.evictCodehash(codehash); } } From 1b9f0dca45aa8bc5c43292e02da3336d9939f7fc Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 9 Apr 2024 17:29:59 -0600 Subject: [PATCH 090/126] add events and simplify --- src/mocks/CacheManager.sol | 146 ++++++++++++++++++++++--------------- 1 file changed, 87 insertions(+), 59 deletions(-) diff --git a/src/mocks/CacheManager.sol b/src/mocks/CacheManager.sol index 4ed5351f..17f1edac 100644 --- a/src/mocks/CacheManager.sol +++ b/src/mocks/CacheManager.sol @@ -21,11 +21,18 @@ contract CacheManager { uint64 public cacheSize; uint64 public queueSize; uint64 public decay; + bool public isPaused; error NotChainOwner(address sender); - error AsmTooLarge(uint256 asm, uint256 cacheSize); + error AsmTooLarge(uint256 asm, uint256 queueSize, uint256 cacheSize); error AlreadyCached(bytes32 codehash); error BidTooSmall(uint256 bid, uint256 min); + error BidsArePaused(); + + event InsertBid(uint256 bid, bytes32 indexed codehash, uint64 size); + event DeleteBid(uint256 bid, bytes32 indexed codehash, uint64 size); + event Pause(); + event Unpause(); struct Entry { bytes32 code; @@ -49,17 +56,38 @@ contract CacheManager { cacheSize = newSize; } + /// Sets the intended decay factor. Does not modify existing bids. + function setDecayRate(uint64 newDecay) external onlyOwner { + decay = newDecay; + } + + /// Disable new bids. + function paused() external onlyOwner { + isPaused = true; + emit Pause(); + } + + /// Enable new bids. + function unpause() external onlyOwner { + isPaused = false; + emit Unpause(); + } + /// Evicts all programs in the cache. function evictAll() external onlyOwner { - while (bids.length() != 0) { - uint64 index = _getIndex(bids.pop()); - Entry memory entry = entries[index]; - _evict(entry.code); - delete entries[index]; + evictPrograms(type(uint256).max); + } + + /// Evicts up to `count` programs from the cache. + function evictPrograms(uint256 count) public onlyOwner { + while (bids.length() != 0 && count > 0) { + (uint256 bid, uint64 index) = _getBid(bids.pop()); + _deleteEntry(bid, index); + count -= 1; } - queueSize = 0; } + /// Sends all revenue to the network fee account. function sweepFunds() external { (bool success, bytes memory data) = ARB_OWNER_PUBLIC.getNetworkFeeAccount().call{ value: address(this).balance @@ -71,7 +99,18 @@ contract CacheManager { } } + /// Gets the minimum bid needed to cache the given program. + /// Call this function with the program's codehash. + function getMinBid(bytes32) external view returns (uint256) { + (uint256 bid, ) = _getBid(bids.root()); + return bid; + } + + /// Places a bid, reverting if payment is insufficient. function placeBid(bytes32 codehash) external payable { + if (isPaused) { + revert BidsArePaused(); + } if (_isCached(codehash)) { revert AlreadyCached(codehash); } @@ -79,80 +118,69 @@ contract CacheManager { // discount historical bids by the number of seconds uint256 bid = msg.value + block.timestamp * uint256(decay); uint64 asm = _asmSize(codehash); - if (asm > cacheSize) { - revert AsmTooLarge(asm, cacheSize); - } - - Entry memory candidate = Entry({size: asm, code: codehash}); - - uint64 index; - - // if there's space, append to the end - if (queueSize + asm < cacheSize) { - index = uint64(entries.length); - bid = _setIndex(bid, index); - - bids.push(bid); - queueSize += asm; - _cache(codehash); - entries[index] = candidate; - return; - } + uint64 index = uint64(entries.length); // pop entries until we have enough space - while (true) { - uint256 min = bids.root(); - index = _getIndex(min); - bid = _setIndex(bid, index); // make both have same index + while (queueSize + asm > cacheSize) { + uint256 min; + (min, index) = _getBid(bids.pop()); if (bid > min) { - revert BidTooSmall(_clearIndex(bid), _clearIndex(min)); + revert BidTooSmall(bid, min); } + _deleteEntry(min, index); + } - // evict the entry - Entry memory entry = entries[index]; - _evict(entry.code); - queueSize -= entry.size; - bids.pop(); - delete entries[index]; + return _addBid(bid, codehash, asm, index); + } - if (queueSize + asm < cacheSize) { - break; - } + /// Adds a bid + function _addBid( + uint256 bid, + bytes32 code, + uint64 size, + uint64 index + ) internal { + if (queueSize + size < cacheSize) { + revert AsmTooLarge(size, queueSize, cacheSize); } - // replace the min with the new bid - _cache(codehash); - entries[index] = candidate; - bids.push(bid); + Entry memory entry = Entry({size: size, code: code}); + ARB_WASM_CACHE.cacheCodehash(code); + bids.push(_packBid(bid, index)); + queueSize += size; + entries[index] = entry; + emit InsertBid(bid, code, size); } - function _getIndex(uint256 info) internal pure returns (uint64) { - return uint64(info >> 192); + /// Clears the entry at the given index + function _deleteEntry(uint256 bid, uint64 index) internal { + Entry memory entry = entries[index]; + ARB_WASM_CACHE.evictCodehash(entry.code); + queueSize -= entry.size; + emit DeleteBid(bid, entry.code, entry.size); + delete entries[index]; } - function _setIndex(uint256 info, uint64 index) internal pure returns (uint256) { - uint256 mask = 0xffffffffffffffffffffffffffffffffffffffffffffffff; - return (info & mask) | (uint256(index) << 192); + /// Gets the bid and index from a packed bid item + function _getBid(uint256 info) internal pure returns (uint256 bid, uint64 index) { + bid = _packBid(info, 0); + index = uint64(info >> 192); } - function _clearIndex(uint256 info) internal pure returns (uint256) { - return _setIndex(info, 0); + /// Creates a packed bid item + function _packBid(uint256 bid, uint64 index) internal pure returns (uint256) { + uint256 mask = 0xffffffffffffffffffffffffffffffffffffffffffffffff; + return (bid & mask) | (uint256(index) << 192); } + /// Gets the size of the given program in bytes function _asmSize(bytes32 codehash) internal view returns (uint64) { uint32 size = ARB_WASM.codehashAsmSize(codehash); return uint64(size >= 4096 ? size : 4096); // pretend it's at least 4Kb } + /// Determines whether a program is cached function _isCached(bytes32 codehash) internal view returns (bool) { return ARB_WASM_CACHE.codehashIsCached(codehash); } - - function _cache(bytes32 codehash) internal { - ARB_WASM_CACHE.cacheCodehash(codehash); - } - - function _evict(bytes32 codehash) internal { - ARB_WASM_CACHE.evictCodehash(codehash); - } } From 0b675b6401aafae6bee71a6937418dc5ecdfa6a0 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 9 Apr 2024 18:27:30 -0600 Subject: [PATCH 091/126] fix condition --- src/mocks/CacheManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mocks/CacheManager.sol b/src/mocks/CacheManager.sol index 17f1edac..10baff31 100644 --- a/src/mocks/CacheManager.sol +++ b/src/mocks/CacheManager.sol @@ -124,7 +124,7 @@ contract CacheManager { while (queueSize + asm > cacheSize) { uint256 min; (min, index) = _getBid(bids.pop()); - if (bid > min) { + if (bid < min) { revert BidTooSmall(bid, min); } _deleteEntry(min, index); From dd08a3e32043cf42c623edcd81c97a26e23c7f3a Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 9 Apr 2024 18:48:02 -0600 Subject: [PATCH 092/126] fix comparison --- src/mocks/CacheManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mocks/CacheManager.sol b/src/mocks/CacheManager.sol index 10baff31..c78eb864 100644 --- a/src/mocks/CacheManager.sol +++ b/src/mocks/CacheManager.sol @@ -140,7 +140,7 @@ contract CacheManager { uint64 size, uint64 index ) internal { - if (queueSize + size < cacheSize) { + if (queueSize + size > cacheSize) { revert AsmTooLarge(size, queueSize, cacheSize); } From c1a66f975299fd3d2b65bfe8c78076519c8c651b Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 9 Apr 2024 19:25:21 -0600 Subject: [PATCH 093/126] txCacheSize methods --- src/precompiles/ArbOwner.sol | 3 +++ src/precompiles/ArbWasm.sol | 40 ++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 7dffe017..1c7dcd11 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -122,6 +122,9 @@ interface ArbOwner { /// @notice Sets the age a program must be to perform a keepalive function setWasmKeepaliveDays(uint16 _days) external; + /// @notice Sets the number of extra programs ArbOS caches during a given tx + function setWasmTxCacheSize(uint8 count) external; + /// @notice Adds account as a wasm cache manager function addWasmCacheManager(address manager) external; diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 5871fcb7..cc0e6023 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -9,7 +9,7 @@ pragma solidity >=0.4.21 <0.9.0; * @notice Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000071. */ interface ArbWasm { - /// @notice activate a wasm program + /// @notice Activate a wasm program /// @param program the program to activate /// @return version the stylus version the program was activated against /// @return dataFee the data fee paid to store the activated program @@ -18,28 +18,28 @@ interface ArbWasm { payable returns (uint16 version, uint256 dataFee); - /// @notice gets the latest stylus version + /// @notice Gets the latest stylus version /// @return version the stylus version function stylusVersion() external view returns (uint16 version); - /// @notice gets the stylus version the program with codehash was most recently activated against + /// @notice Gets the stylus version the program with codehash was most recently activated against /// @return version the program version (reverts for EVM contracts) function codehashVersion(bytes32 codehash) external view returns (uint16 version); - /// @notice extends a program's expiration date. + /// @notice Extends a program's expiration date. /// Reverts if too soon or if the program is not up to date. function codehashKeepalive(bytes32 codehash) external payable; - /// @notice gets a program's asm size. + /// @notice Gets a program's asm size. /// Reverts if program is not active. /// @return size the size in bytes function codehashAsmSize(bytes32 codehash) external view returns (uint32 size); - /// @notice gets the stylus version the program was most recently activated against + /// @notice Gets the stylus version the program was most recently activated against /// @return version the program version (reverts for EVM contracts) function programVersion(address program) external view returns (uint16 version); - /// @notice gets the cost to invoke the program (not including minInitGas) + /// @notice Gets the cost to invoke the program (not including minInitGas) /// @return gas the amount of gas /// @return gasWhenCached the amount of gas if the program was recently used function programInitGas(address program) @@ -47,51 +47,55 @@ interface ArbWasm { view returns (uint16 gas, uint16 gasWhenCached); - /// @notice gets the memory footprint of the program at the given address in pages + /// @notice Gets the memory footprint of the program at the given address in pages /// @return footprint the memory footprint of program in pages (reverts for EVM contracts) function programMemoryFootprint(address program) external view returns (uint16 footprint); - /// @notice gets the amount of time remaining until the program expires + /// @notice Gets the amount of time remaining until the program expires /// @return _secs the time left in seconds (reverts for EVM contracts) function programTimeLeft(address program) external view returns (uint64 _secs); - /// @notice gets the conversion rate between gas and ink + /// @notice Gets the conversion rate between gas and ink /// @return price the amount of ink 1 gas buys function inkPrice() external view returns (uint32 price); - /// @notice gets the wasm stack size limit + /// @notice Gets the wasm stack size limit /// @return depth the maximum depth (in wasm words) a wasm stack may grow function maxStackDepth() external view returns (uint32 depth); - /// @notice gets the number of free wasm pages a program gets + /// @notice Gets the number of free wasm pages a program gets /// @return pages the number of wasm pages (2^16 bytes) function freePages() external view returns (uint16 pages); - /// @notice gets the base cost of each additional wasm page (2^16 bytes) + /// @notice Gets the base cost of each additional wasm page (2^16 bytes) /// @return gas base amount of gas needed to grow another wasm page function pageGas() external view returns (uint16 gas); - /// @notice gets the ramp that drives exponential memory costs + /// @notice Gets the ramp that drives exponential memory costs /// @return ramp bits representing the floating point value function pageRamp() external view returns (uint64 ramp); - /// @notice gets the maximum number of pages a wasm may allocate + /// @notice Gets the maximum number of pages a wasm may allocate /// @return limit the number of pages function pageLimit() external view returns (uint16 limit); - /// @notice gets the minimum costs to invoke a program + /// @notice Gets the minimum costs to invoke a program /// @return gas amount of gas in increments of 256 when not cached /// @return cached amount of gas in increments of 64 when cached function minInitGas() external view returns (uint8 gas, uint8 cached); - /// @notice gets the number of days after which programs deactivate + /// @notice Gets the number of days after which programs deactivate /// @return _days the number of days function expiryDays() external view returns (uint16 _days); - /// @notice gets the age a program must be to perform a keepalive + /// @notice Gets the age a program must be to perform a keepalive /// @return _days the number of days function keepaliveDays() external view returns (uint16 _days); + /// @notice Gets the number of extra programs ArbOS caches during a given tx. + /// @return count the number of same-tx programs. + function txCacheSize() external view returns (uint8 count); + event ProgramActivated( bytes32 indexed codehash, bytes32 moduleHash, From 8ca0b8745e1c4bd0c8aa949550d5d28972601305 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 10 Apr 2024 23:52:38 -0600 Subject: [PATCH 094/126] add test case --- src/{mocks => chain}/CacheManager.sol | 30 ++--- src/precompiles/ArbOwner.sol | 4 +- src/precompiles/ArbWasm.sol | 6 +- test/foundry/CacheManager.t.sol | 167 ++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 22 deletions(-) rename src/{mocks => chain}/CacheManager.sol (89%) create mode 100644 test/foundry/CacheManager.t.sol diff --git a/src/mocks/CacheManager.sol b/src/chain/CacheManager.sol similarity index 89% rename from src/mocks/CacheManager.sol rename to src/chain/CacheManager.sol index c78eb864..f302a504 100644 --- a/src/mocks/CacheManager.sol +++ b/src/chain/CacheManager.sol @@ -76,6 +76,7 @@ contract CacheManager { /// Evicts all programs in the cache. function evictAll() external onlyOwner { evictPrograms(type(uint256).max); + delete entries; } /// Evicts up to `count` programs from the cache. @@ -99,13 +100,6 @@ contract CacheManager { } } - /// Gets the minimum bid needed to cache the given program. - /// Call this function with the program's codehash. - function getMinBid(bytes32) external view returns (uint256) { - (uint256 bid, ) = _getBid(bids.root()); - return bid; - } - /// Places a bid, reverting if payment is insufficient. function placeBid(bytes32 codehash) external payable { if (isPaused) { @@ -119,17 +113,16 @@ contract CacheManager { uint256 bid = msg.value + block.timestamp * uint256(decay); uint64 asm = _asmSize(codehash); uint64 index = uint64(entries.length); + uint256 min; // pop entries until we have enough space while (queueSize + asm > cacheSize) { - uint256 min; (min, index) = _getBid(bids.pop()); - if (bid < min) { - revert BidTooSmall(bid, min); - } _deleteEntry(min, index); } - + if (bid < min) { + revert BidTooSmall(bid, min); + } return _addBid(bid, codehash, asm, index); } @@ -148,7 +141,11 @@ contract CacheManager { ARB_WASM_CACHE.cacheCodehash(code); bids.push(_packBid(bid, index)); queueSize += size; - entries[index] = entry; + if (index == entries.length) { + entries.push(entry); + } else { + entries[index] = entry; + } emit InsertBid(bid, code, size); } @@ -163,14 +160,13 @@ contract CacheManager { /// Gets the bid and index from a packed bid item function _getBid(uint256 info) internal pure returns (uint256 bid, uint64 index) { - bid = _packBid(info, 0); - index = uint64(info >> 192); + bid = info >> 64; + index = uint64(info); } /// Creates a packed bid item function _packBid(uint256 bid, uint64 index) internal pure returns (uint256) { - uint256 mask = 0xffffffffffffffffffffffffffffffffffffffffffffffff; - return (bid & mask) | (uint256(index) << 192); + return (bid << 64) | uint256(index); } /// Gets the size of the given program in bytes diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 1c7dcd11..2999480d 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -122,8 +122,8 @@ interface ArbOwner { /// @notice Sets the age a program must be to perform a keepalive function setWasmKeepaliveDays(uint16 _days) external; - /// @notice Sets the number of extra programs ArbOS caches during a given tx - function setWasmTxCacheSize(uint8 count) external; + /// @notice Sets the number of extra programs ArbOS caches during a given block + function setWasmBlockCacheSize(uint16 count) external; /// @notice Adds account as a wasm cache manager function addWasmCacheManager(address manager) external; diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index cc0e6023..7d43b847 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -92,9 +92,9 @@ interface ArbWasm { /// @return _days the number of days function keepaliveDays() external view returns (uint16 _days); - /// @notice Gets the number of extra programs ArbOS caches during a given tx. - /// @return count the number of same-tx programs. - function txCacheSize() external view returns (uint8 count); + /// @notice Gets the number of extra programs ArbOS caches during a given block. + /// @return count the number of same-block programs. + function blockCacheSize() external view returns (uint16 count); event ProgramActivated( bytes32 indexed codehash, diff --git a/test/foundry/CacheManager.t.sol b/test/foundry/CacheManager.t.sol new file mode 100644 index 00000000..abb3bdf9 --- /dev/null +++ b/test/foundry/CacheManager.t.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +import "forge-std/Test.sol"; +import "../../src/chain/CacheManager.sol"; + +contract CacheManagerTest is Test { + CacheManager public cacheManager; + CachedItem[] public expectedCache; + + uint256 internal constant MAX_PAY = 100_000_000 ether; + + ArbWasmMock internal constant ARB_WASM = ArbWasmMock(address(0x71)); + ArbWasmCacheMock internal constant ARB_WASM_CACHE = ArbWasmCacheMock(address(0x72)); + + constructor() { + uint64 cacheSize = 1_000_000; + uint64 decay = 0.1 ether; + cacheManager = new CacheManager(cacheSize, decay); + require(cacheManager.cacheSize() == cacheSize, "wrong cache size"); + require(cacheManager.decay() == decay, "wrong decay rate"); + + vm.etch(address(0x6b), type(ArbOwnerPublicMock).runtimeCode); + vm.etch(address(0x71), type(ArbWasmMock).runtimeCode); + vm.etch(address(0x72), type(ArbWasmCacheMock).runtimeCode); + } + + struct CachedItem { + bytes32 codehash; + uint256 bid; + uint256 size; + } + + function test_randomBids() external { + for (uint256 epoch = 0; epoch < 4; epoch++) { + for (uint256 round = 0; round < 512; round++) { + // roll one of 256 random codehashes + bytes32 codehash = keccak256(abi.encodePacked("code", epoch, round)); + codehash = keccak256(abi.encodePacked(uint256(codehash) % 256)); + + // roll a random bid + uint256 pay = uint256(keccak256(abi.encodePacked("value", epoch, round))) % MAX_PAY; + uint256 bid = pay + block.timestamp * uint256(cacheManager.decay()); + + // determine the expected insertion index on success and the bid needed + uint256 index; + uint256 asmSize = ARB_WASM.codehashAsmSize(codehash); + uint256 cumulativeCacheSize = asmSize; + uint256 neededBid; + for (; index < expectedCache.length; index++) { + if (bid >= expectedCache[index].bid) { + break; + } + cumulativeCacheSize += expectedCache[index].size; + if (cumulativeCacheSize > cacheManager.cacheSize()) { + neededBid = expectedCache[index].bid; + break; + } + } + + if (ARB_WASM_CACHE.codehashIsCached(codehash)) { + vm.expectRevert( + abi.encodeWithSelector(CacheManager.AlreadyCached.selector, codehash) + ); + } else if (neededBid > 0) { + vm.expectRevert( + abi.encodeWithSelector(CacheManager.BidTooSmall.selector, bid, neededBid) + ); + } else { + // insert the item by moving over those to the right + expectedCache.push(CachedItem(bytes32(0), 0, 0)); + for (uint256 j = expectedCache.length - 1; j > index; j--) { + expectedCache[j] = expectedCache[j - 1]; + } + expectedCache[index] = CachedItem(codehash, bid, asmSize); + + // pop any excess cache elements + for (index++; index < expectedCache.length; index++) { + cumulativeCacheSize += expectedCache[index].size; + if (cumulativeCacheSize > cacheManager.cacheSize()) { + break; + } + } + while (index < expectedCache.length) { + expectedCache.pop(); + } + } + + cacheManager.placeBid{value: pay}(codehash); + + require( + ARB_WASM_CACHE.numCached() == expectedCache.length, + "wrong number of cached items" + ); + for (uint256 j = 0; j < expectedCache.length; j++) { + require( + ARB_WASM_CACHE.codehashIsCached(expectedCache[j].codehash), + "codehash not cached" + ); + } + + if (round == 768) { + uint256 newCacheSize = 500_000 + + (uint256(keccak256(abi.encodePacked("cacheSize", epoch))) % 1_000_000); + cacheManager.setCacheSize(uint64(newCacheSize)); + } + } + + cacheManager.evictAll(); + require(ARB_WASM_CACHE.numCached() == 0, "cached items after evictAll"); + delete expectedCache; + } + require(ARB_WASM_CACHE.uselessCalls() == 0, "useless ArbWasmCache calls"); + } +} + +contract ArbOwnerPublicMock { + address payable constant NETWORK_FEE_ACCOUNT = payable(address(0xba5eba11)); + + function getNetworkFeeAccount() external pure returns (address payable) { + return NETWORK_FEE_ACCOUNT; + } + + // pretend all smart contracts are chain owners + function isChainOwner(address addr) external view returns (bool) { + uint256 codeSize; + assembly { + codeSize := extcodesize(addr) + } + return codeSize > 0; + } +} + +contract ArbWasmMock { + // returns a non-uniform distribution of mock code sizes + function codehashAsmSize(bytes32 codehash) external pure returns (uint64) { + uint256 size; + for (uint256 i = 0; i < 3; i++) { + size += uint256(keccak256(abi.encodePacked(codehash, i))) % 65536; + } + return uint64(size); + } +} + +contract ArbWasmCacheMock { + mapping(bytes32 => bool) public codehashIsCached; + uint256 public numCached; + uint256 public uselessCalls; + + function cacheCodehash(bytes32 codehash) external { + if (codehashIsCached[codehash]) { + uselessCalls++; + return; + } + codehashIsCached[codehash] = true; + numCached++; + } + + function evictCodehash(bytes32 codehash) external { + if (!codehashIsCached[codehash]) { + uselessCalls++; + return; + } + codehashIsCached[codehash] = false; + numCached--; + } +} From 447582a3acd17500b8019eca966a34fc6e6428e4 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 11 Apr 2024 13:45:42 -0600 Subject: [PATCH 095/126] add makeSpace --- src/chain/CacheManager.sol | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/chain/CacheManager.sol b/src/chain/CacheManager.sol index f302a504..28e009bc 100644 --- a/src/chain/CacheManager.sol +++ b/src/chain/CacheManager.sol @@ -109,21 +109,31 @@ contract CacheManager { revert AlreadyCached(codehash); } - // discount historical bids by the number of seconds - uint256 bid = msg.value + block.timestamp * uint256(decay); uint64 asm = _asmSize(codehash); - uint64 index = uint64(entries.length); - uint256 min; + (uint256 bid, uint64 index) = _makeSpace(asm); + return _addBid(bid, codehash, asm, index); + } + + /// Evicts entries until enough space exists in the cache, reverting if payment is insufficient. + function makeSpace(uint64 size) external payable { + _makeSpace(size); + } + + /// Evicts entries until enough space exists in the cache, reverting if payment is insufficient. + /// Returns the bid and the index to use for insertion. + function _makeSpace(uint64 size) internal returns (uint256 bid, uint64 index) { + // discount historical bids by the number of seconds + bid = msg.value + block.timestamp * uint256(decay); + index = uint64(entries.length); - // pop entries until we have enough space - while (queueSize + asm > cacheSize) { + uint256 min; + while (queueSize + size > cacheSize) { (min, index) = _getBid(bids.pop()); _deleteEntry(min, index); } if (bid < min) { revert BidTooSmall(bid, min); } - return _addBid(bid, codehash, asm, index); } /// Adds a bid From 9b198f3803ef57bb3f5ab2459c9a03c89ec0e82c Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Fri, 12 Apr 2024 01:25:52 -0600 Subject: [PATCH 096/126] math hostio test --- src/mocks/Program.sol | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/mocks/Program.sol b/src/mocks/Program.sol index 2ff15f43..3966196d 100644 --- a/src/mocks/Program.sol +++ b/src/mocks/Program.sol @@ -52,6 +52,7 @@ contract ProgramTest { bytes calldata data ) external view returns (bytes memory) { (bool success, bytes memory result) = address(program).staticcall{gas: gas}(data); + require(success, "call failed"); address arbPrecompile = address(0x69); address ethPrecompile = address(0x01); @@ -99,4 +100,27 @@ contract ProgramTest { } return result; } + + function mathTest(address program) external { + uint256 value = 0xeddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786; + value = mulmod( + value, + 0xc6178c2de1078cd36c3bd302cde755340d7f17fcb3fcc0b9c333ba03b217029f, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f + ); + value = addmod( + value, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, + 0xc6178c2de1078cd36c3bd302cde755340d7f17fcb3fcc0b9c333ba03b217029f + ); + unchecked { + value /= 0xeddecf107b5740ce; + value = value**0xfffffffefffffc2f; + value = value % 0xc6178c2de1078cd3; + } + + (bool success, bytes memory result) = address(program).call(""); + require(success, "call failed"); + require(keccak256(result) == keccak256(abi.encodePacked(value))); + } } From 1600221058044b676c02e1356f240a8af2e8f64c Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 14 Apr 2024 22:18:53 -0600 Subject: [PATCH 097/126] limit makeSpace --- src/chain/CacheManager.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/chain/CacheManager.sol b/src/chain/CacheManager.sol index 28e009bc..462608f3 100644 --- a/src/chain/CacheManager.sol +++ b/src/chain/CacheManager.sol @@ -14,6 +14,7 @@ contract CacheManager { ArbOwnerPublic internal constant ARB_OWNER_PUBLIC = ArbOwnerPublic(address(0x6b)); ArbWasm internal constant ARB_WASM = ArbWasm(address(0x71)); ArbWasmCache internal constant ARB_WASM_CACHE = ArbWasmCache(address(0x72)); + uint64 internal constant MAX_MAKE_SPACE = 5 * 1024 * 1024; MinHeapLib.Heap internal bids; Entry[] public entries; @@ -115,7 +116,11 @@ contract CacheManager { } /// Evicts entries until enough space exists in the cache, reverting if payment is insufficient. + /// Note: will only make up to 5Mb of space. Call repeatedly for more. function makeSpace(uint64 size) external payable { + if (size > MAX_MAKE_SPACE) { + size = MAX_MAKE_SPACE; + } _makeSpace(size); } From f3445c042a09cbe493aafae3c5136b7f54411298 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 14 Apr 2024 22:21:46 -0600 Subject: [PATCH 098/126] return amount of space left --- src/chain/CacheManager.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/chain/CacheManager.sol b/src/chain/CacheManager.sol index 462608f3..42dfca3a 100644 --- a/src/chain/CacheManager.sol +++ b/src/chain/CacheManager.sol @@ -116,12 +116,14 @@ contract CacheManager { } /// Evicts entries until enough space exists in the cache, reverting if payment is insufficient. + /// Returns the new amount of space available on success. /// Note: will only make up to 5Mb of space. Call repeatedly for more. - function makeSpace(uint64 size) external payable { + function makeSpace(uint64 size) external payable returns (uint64 space) { if (size > MAX_MAKE_SPACE) { size = MAX_MAKE_SPACE; } _makeSpace(size); + return cacheSize - queueSize; } /// Evicts entries until enough space exists in the cache, reverting if payment is insufficient. From eda0ab908db0ab8795a62862cf5eeea15e5c124d Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 15 Apr 2024 02:07:51 -0600 Subject: [PATCH 099/126] optimize func merkle --- src/osp/OneStepProofEntry.sol | 13 +++++++++---- src/osp/OneStepProver0.sol | 2 +- src/state/Deserialize.sol | 20 +++++++++++++------- src/state/Instructions.sol | 9 +++++++-- src/state/MerkleProof.sol | 6 +++--- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index 4d03e455..cd99a469 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -68,17 +68,22 @@ contract OneStepProofEntry is IOneStepProofEntry { ); { - MerkleProof memory instProof; + Instruction[] memory codeChunk; + MerkleProof memory codeProof; MerkleProof memory funcProof; - (inst, offset) = Deserialize.instruction(proof, offset); - (instProof, offset) = Deserialize.merkleProof(proof, offset); + (codeChunk, offset) = Deserialize.instructions(proof, offset); + (codeProof, offset) = Deserialize.merkleProof(proof, offset); (funcProof, offset) = Deserialize.merkleProof(proof, offset); - bytes32 codeHash = instProof.computeRootFromInstruction(mach.functionPc, inst); + bytes32 codeHash = codeProof.computeRootFromInstructions( + mach.functionPc, + codeChunk + ); bytes32 recomputedRoot = funcProof.computeRootFromFunction( mach.functionIdx, codeHash ); require(recomputedRoot == mod.functionsMerkleRoot, "BAD_FUNCTIONS_ROOT"); + inst = codeChunk[mach.functionPc % 64]; } proof = proof[offset:]; } diff --git a/src/osp/OneStepProver0.sol b/src/osp/OneStepProver0.sol index 421cec63..671ff074 100644 --- a/src/osp/OneStepProver0.sol +++ b/src/osp/OneStepProver0.sol @@ -143,7 +143,7 @@ contract OneStepProver0 is IOneStepProver { function executeCrossModuleForward( Machine memory mach, - Module memory mod, + Module memory, Instruction calldata inst, bytes calldata ) internal pure { diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 19303168..e9ce91e2 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -146,17 +146,23 @@ library Deserialize { }); } - function instruction(bytes calldata proof, uint256 startOffset) + function instructions(bytes calldata proof, uint256 startOffset) internal pure - returns (Instruction memory inst, uint256 offset) + returns (Instruction[] memory code, uint256 offset) { offset = startOffset; - uint16 opcode; - uint256 data; - (opcode, offset) = u16(proof, offset); - (data, offset) = u256(proof, offset); - inst = Instruction({opcode: opcode, argumentData: data}); + uint8 count; + (count, offset) = u8(proof, offset); + code = new Instruction[](count); + + for (uint256 i = 0; i < uint256(count); i++) { + uint16 opcode; + uint256 data; + (opcode, offset) = u16(proof, offset); + (data, offset) = u64(proof, offset); + code[i] = Instruction({opcode: opcode, argumentData: data}); + } } function stackFrame(bytes calldata proof, uint256 startOffset) diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index 9c67d518..35988594 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -155,7 +155,12 @@ library Instructions { uint256 internal constant INBOX_INDEX_SEQUENCER = 0; uint256 internal constant INBOX_INDEX_DELAYED = 1; - function hash(Instruction memory inst) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("Instruction:", inst.opcode, inst.argumentData)); + function hash(Instruction[] memory code) internal pure returns (bytes32) { + bytes memory data = "Instructions:"; + data = abi.encodePacked(data, uint8(code.length)); + for (uint256 i = 0; i < code.length; i++) { + data = abi.encodePacked(data, code[i].opcode, code[i].argumentData); + } + return keccak256(data); } } diff --git a/src/state/MerkleProof.sol b/src/state/MerkleProof.sol index 645c9b64..26d979e0 100644 --- a/src/state/MerkleProof.sol +++ b/src/state/MerkleProof.sol @@ -24,12 +24,12 @@ library MerkleProofLib { return computeRootUnsafe(proof, index, leaf.hash(), "Value merkle tree:"); } - function computeRootFromInstruction( + function computeRootFromInstructions( MerkleProof memory proof, uint256 index, - Instruction memory inst + Instruction[] memory code ) internal pure returns (bytes32) { - return computeRootUnsafe(proof, index, Instructions.hash(inst), "Instruction merkle tree:"); + return computeRootUnsafe(proof, index, Instructions.hash(code), "Instruction merkle tree:"); } function computeRootFromFunction( From c8dbbd4cae371508fc95d50e3ab8e7f6514019db Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 15 Apr 2024 02:54:54 -0600 Subject: [PATCH 100/126] fix root proof --- src/osp/OneStepProofEntry.sol | 2 +- src/state/Deserialize.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index cd99a469..ac1cc9ee 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -75,7 +75,7 @@ contract OneStepProofEntry is IOneStepProofEntry { (codeProof, offset) = Deserialize.merkleProof(proof, offset); (funcProof, offset) = Deserialize.merkleProof(proof, offset); bytes32 codeHash = codeProof.computeRootFromInstructions( - mach.functionPc, + mach.functionPc / 64, codeChunk ); bytes32 recomputedRoot = funcProof.computeRootFromFunction( diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index e9ce91e2..04596a4f 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -160,7 +160,7 @@ library Deserialize { uint16 opcode; uint256 data; (opcode, offset) = u16(proof, offset); - (data, offset) = u64(proof, offset); + (data, offset) = u256(proof, offset); code[i] = Instruction({opcode: opcode, argumentData: data}); } } From 4e427881e8a768d182f1ce5fbe39944d7311ec90 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 15 Apr 2024 03:28:00 -0600 Subject: [PATCH 101/126] O(n) instruction hashing --- src/state/Instructions.sol | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/state/Instructions.sol b/src/state/Instructions.sol index 35988594..c9accdb4 100644 --- a/src/state/Instructions.sol +++ b/src/state/Instructions.sol @@ -156,10 +156,32 @@ library Instructions { uint256 internal constant INBOX_INDEX_DELAYED = 1; function hash(Instruction[] memory code) internal pure returns (bytes32) { - bytes memory data = "Instructions:"; - data = abi.encodePacked(data, uint8(code.length)); + // To avoid quadratic expense, we declare a `bytes` early and populate its contents. + bytes memory data = new bytes(13 + 1 + 34 * code.length); + assembly { + // Represents the string "Instructions:", which we place after the length word. + mstore( + add(data, 32), + 0x496e737472756374696f6e733a00000000000000000000000000000000000000 + ) + } + + // write the instruction count + uint256 offset = 13; + data[offset] = bytes1(uint8(code.length)); + offset++; + + // write each instruction for (uint256 i = 0; i < code.length; i++) { - data = abi.encodePacked(data, code[i].opcode, code[i].argumentData); + Instruction memory inst = code[i]; + data[offset] = bytes1(uint8(inst.opcode >> 8)); + data[offset + 1] = bytes1(uint8(inst.opcode)); + offset += 2; + uint256 argumentData = inst.argumentData; + assembly { + mstore(add(add(data, 32), offset), argumentData) + } + offset += 32; } return keccak256(data); } From dc6327cbf007235621520d39baed926a0c30e3d1 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 16 Apr 2024 11:09:06 +0800 Subject: [PATCH 102/126] feat: conditional osp --- src/challenge/ChallengeManager.sol | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/challenge/ChallengeManager.sol b/src/challenge/ChallengeManager.sol index c5427e7e..14acba8f 100644 --- a/src/challenge/ChallengeManager.sol +++ b/src/challenge/ChallengeManager.sol @@ -35,6 +35,7 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { ISequencerInbox public sequencerInbox; IBridge public bridge; IOneStepProofEntry public osp; + mapping(bytes32 => IOneStepProofEntry) public ospCond; function challengeInfo(uint64 challengeIndex) external @@ -116,6 +117,20 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { osp = osp_; } + function setConditionalOsp(bytes32 wasmModuleRoot, IOneStepProofEntry osp_) external onlyDelegated onlyProxyOwner { + emit ConditonalOSPSet(wasmModuleRoot, osp_); + ospCond[wasmModuleRoot] = osp_; + } + + function getOSP(bytes32 wasmModuleRoot) internal view returns (IOneStepProofEntry) { + IOneStepProofEntry t = ospCond[wasmModuleRoot]; + if (address(t) == address(0)){ + return osp; + } else { + return t; + } + } + function createChallenge( bytes32 wasmModuleRoot_, MachineStatus[2] calldata startAndEndMachineStatuses_, @@ -259,7 +274,7 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { require(challengeLength == 1, "TOO_LONG"); } - bytes32 afterHash = osp.proveOneStep( + bytes32 afterHash = getOSP(challenge.wasmModuleRoot).proveOneStep( ExecutionContext({maxInboxMessagesRead: challenge.maxInboxMessages, bridge: bridge}), challengeStart, selection.oldSegments[selection.challengePosition], From b6d60e67ce3b0da1d4401d9046f6194841c47459 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 16 Apr 2024 11:10:34 +0800 Subject: [PATCH 103/126] feat: make getter public --- src/challenge/ChallengeManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/challenge/ChallengeManager.sol b/src/challenge/ChallengeManager.sol index 14acba8f..5bd01f65 100644 --- a/src/challenge/ChallengeManager.sol +++ b/src/challenge/ChallengeManager.sol @@ -122,7 +122,7 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { ospCond[wasmModuleRoot] = osp_; } - function getOSP(bytes32 wasmModuleRoot) internal view returns (IOneStepProofEntry) { + function getOSP(bytes32 wasmModuleRoot) public view returns (IOneStepProofEntry) { IOneStepProofEntry t = ospCond[wasmModuleRoot]; if (address(t) == address(0)){ return osp; From f05cd14d7b6f76ed8e16d0bdeb429f9759f8ed31 Mon Sep 17 00:00:00 2001 From: gzeon Date: Wed, 17 Apr 2024 02:10:03 +0800 Subject: [PATCH 104/126] fix: interface --- src/challenge/IChallengeManager.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/challenge/IChallengeManager.sol b/src/challenge/IChallengeManager.sol index b6f63d67..8770b2cb 100644 --- a/src/challenge/IChallengeManager.sol +++ b/src/challenge/IChallengeManager.sol @@ -39,6 +39,7 @@ interface IChallengeManager { event OneStepProofCompleted(uint64 indexed challengeIndex); event ChallengeEnded(uint64 indexed challengeIndex, ChallengeTerminationType kind); + event ConditonalOSPSet(bytes32 indexed wasmModuleRoot, IOneStepProofEntry osp_); function initialize( IChallengeResultReceiver resultReceiver_, @@ -47,6 +48,10 @@ interface IChallengeManager { IOneStepProofEntry osp_ ) external; + function setConditionalOsp(bytes32 wasmModuleRoot, IOneStepProofEntry osp_) external; + + function getOSP(bytes32 wasmModuleRoot) external view returns (IOneStepProofEntry); + function createChallenge( bytes32 wasmModuleRoot_, MachineStatus[2] calldata startAndEndMachineStatuses_, From e5ba1771b0b0bd5635e06f69da253fcf31a96d21 Mon Sep 17 00:00:00 2001 From: gzeon Date: Wed, 17 Apr 2024 02:27:52 +0800 Subject: [PATCH 105/126] fix: set condOsp with postUpgradeInit --- src/challenge/ChallengeManager.sol | 21 ++++++++++----------- src/challenge/IChallengeManager.sol | 13 +++++++++++-- test/foundry/ChallengeManager.t.sol | 17 +++++++++++++---- test/signatures/ChallengeManager | 4 +++- test/storage/ChallengeManager | 1 + 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/challenge/ChallengeManager.sol b/src/challenge/ChallengeManager.sol index 5bd01f65..a77856a4 100644 --- a/src/challenge/ChallengeManager.sol +++ b/src/challenge/ChallengeManager.sol @@ -111,20 +111,19 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { osp = osp_; } - function postUpgradeInit(IOneStepProofEntry osp_) external onlyDelegated onlyProxyOwner { - // when updating to 4844 we need to create new osp contracts and set them here - // on the challenge manager + function postUpgradeInit( + IOneStepProofEntry osp_, + bytes32 condRoot, + IOneStepProofEntry condOsp + ) external onlyDelegated onlyProxyOwner { + emit ConditonalOSPSet(condRoot, condOsp); + ospCond[condRoot] = condOsp; osp = osp_; } - function setConditionalOsp(bytes32 wasmModuleRoot, IOneStepProofEntry osp_) external onlyDelegated onlyProxyOwner { - emit ConditonalOSPSet(wasmModuleRoot, osp_); - ospCond[wasmModuleRoot] = osp_; - } - - function getOSP(bytes32 wasmModuleRoot) public view returns (IOneStepProofEntry) { + function getOsp(bytes32 wasmModuleRoot) public view returns (IOneStepProofEntry) { IOneStepProofEntry t = ospCond[wasmModuleRoot]; - if (address(t) == address(0)){ + if (address(t) == address(0)) { return osp; } else { return t; @@ -274,7 +273,7 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { require(challengeLength == 1, "TOO_LONG"); } - bytes32 afterHash = getOSP(challenge.wasmModuleRoot).proveOneStep( + bytes32 afterHash = getOsp(challenge.wasmModuleRoot).proveOneStep( ExecutionContext({maxInboxMessagesRead: challenge.maxInboxMessages, bridge: bridge}), challengeStart, selection.oldSegments[selection.challengePosition], diff --git a/src/challenge/IChallengeManager.sol b/src/challenge/IChallengeManager.sol index 8770b2cb..60f44c5d 100644 --- a/src/challenge/IChallengeManager.sol +++ b/src/challenge/IChallengeManager.sol @@ -48,9 +48,18 @@ interface IChallengeManager { IOneStepProofEntry osp_ ) external; - function setConditionalOsp(bytes32 wasmModuleRoot, IOneStepProofEntry osp_) external; + function postUpgradeInit( + IOneStepProofEntry osp_, + bytes32 condRoot, + IOneStepProofEntry condOsp + ) external; + + /// @notice Get the default osp, which is used for all wasm module roots that don't have a conditional OSP set + /// Use getOsp(wasmModuleRoot) to get the OSP for a specific wasm module root + function osp() external view returns (IOneStepProofEntry); - function getOSP(bytes32 wasmModuleRoot) external view returns (IOneStepProofEntry); + /// @notice Get the OSP for a given wasm module root + function getOsp(bytes32 wasmModuleRoot) external view returns (IOneStepProofEntry); function createChallenge( bytes32 wasmModuleRoot_, diff --git a/test/foundry/ChallengeManager.t.sol b/test/foundry/ChallengeManager.t.sol index 0a46b8af..9339812b 100644 --- a/test/foundry/ChallengeManager.t.sol +++ b/test/foundry/ChallengeManager.t.sol @@ -11,9 +11,12 @@ contract ChallengeManagerTest is Test { IBridge bridge = IBridge(address(139)); IOneStepProofEntry osp = IOneStepProofEntry(address(140)); IOneStepProofEntry newOsp = IOneStepProofEntry(address(141)); + IOneStepProofEntry condOsp = IOneStepProofEntry(address(142)); address proxyAdmin = address(141); ChallengeManager chalmanImpl = new ChallengeManager(); + bytes32 randomRoot = keccak256(abi.encodePacked("randomRoot")); + function deploy() public returns (ChallengeManager) { ChallengeManager chalman = ChallengeManager( address(new TransparentUpgradeableProxy(address(chalmanImpl), proxyAdmin, "")) @@ -40,10 +43,16 @@ contract ChallengeManagerTest is Test { vm.prank(proxyAdmin); TransparentUpgradeableProxy(payable(address(chalman))).upgradeToAndCall( address(chalmanImpl), - abi.encodeWithSelector(ChallengeManager.postUpgradeInit.selector, newOsp) + abi.encodeWithSelector( + ChallengeManager.postUpgradeInit.selector, + newOsp, + randomRoot, + condOsp + ) ); - assertEq(address(chalman.osp()), address(newOsp), "New osp not set"); + assertEq(address(chalman.getOsp(bytes32(0))), address(newOsp), "New osp not set"); + assertEq(address(chalman.getOsp(randomRoot)), address(condOsp), "Cond osp not set"); } function testPostUpgradeInitFailsNotAdmin() public { @@ -51,12 +60,12 @@ contract ChallengeManagerTest is Test { vm.expectRevert(abi.encodeWithSelector(NotOwner.selector, address(151), proxyAdmin)); vm.prank(address(151)); - chalman.postUpgradeInit(osp); + chalman.postUpgradeInit(newOsp, randomRoot, condOsp); } function testPostUpgradeInitFailsNotDelCall() public { vm.expectRevert(bytes("Function must be called through delegatecall")); vm.prank(proxyAdmin); - chalmanImpl.postUpgradeInit(osp); + chalmanImpl.postUpgradeInit(newOsp, randomRoot, condOsp); } } diff --git a/test/signatures/ChallengeManager b/test/signatures/ChallengeManager index 3807c4ff..1d582320 100644 --- a/test/signatures/ChallengeManager +++ b/test/signatures/ChallengeManager @@ -7,11 +7,13 @@ "clearChallenge(uint64)": "56e9df97", "createChallenge(bytes32,uint8[2],(bytes32[2],uint64[2])[2],uint64,address,address,uint256,uint256)": "14eab5e7", "currentResponder(uint64)": "23a9ef23", + "getOsp(bytes32)": "3690b011", "initialize(address,address,address,address)": "f8c8765e", "isTimedOut(uint64)": "9ede42b9", "oneStepProveExecution(uint64,(uint256,uint256,bytes32[],uint256),bytes)": "d248d124", "osp()": "f26a62c6", - "postUpgradeInit(address)": "c474d2c5", + "ospCond(bytes32)": "dc74bf8b", + "postUpgradeInit(address,bytes32,address)": "5038934d", "resultReceiver()": "3504f1d7", "sequencerInbox()": "ee35f327", "timeout(uint64)": "1b45c86a", diff --git a/test/storage/ChallengeManager b/test/storage/ChallengeManager index 85c7f035..15c3f116 100644 --- a/test/storage/ChallengeManager +++ b/test/storage/ChallengeManager @@ -6,3 +6,4 @@ | sequencerInbox | contract ISequencerInbox | 3 | 0 | 20 | src/challenge/ChallengeManager.sol:ChallengeManager | | bridge | contract IBridge | 4 | 0 | 20 | src/challenge/ChallengeManager.sol:ChallengeManager | | osp | contract IOneStepProofEntry | 5 | 0 | 20 | src/challenge/ChallengeManager.sol:ChallengeManager | +| ospCond | mapping(bytes32 => contract IOneStepProofEntry) | 6 | 0 | 32 | src/challenge/ChallengeManager.sol:ChallengeManager | From 337f763783a91ba07f19beb9831f96240438c0b6 Mon Sep 17 00:00:00 2001 From: gzeon Date: Wed, 17 Apr 2024 02:28:44 +0800 Subject: [PATCH 106/126] chore: remove event --- src/challenge/ChallengeManager.sol | 1 - src/challenge/IChallengeManager.sol | 1 - 2 files changed, 2 deletions(-) diff --git a/src/challenge/ChallengeManager.sol b/src/challenge/ChallengeManager.sol index a77856a4..133ab1e2 100644 --- a/src/challenge/ChallengeManager.sol +++ b/src/challenge/ChallengeManager.sol @@ -116,7 +116,6 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { bytes32 condRoot, IOneStepProofEntry condOsp ) external onlyDelegated onlyProxyOwner { - emit ConditonalOSPSet(condRoot, condOsp); ospCond[condRoot] = condOsp; osp = osp_; } diff --git a/src/challenge/IChallengeManager.sol b/src/challenge/IChallengeManager.sol index 60f44c5d..236829b1 100644 --- a/src/challenge/IChallengeManager.sol +++ b/src/challenge/IChallengeManager.sol @@ -39,7 +39,6 @@ interface IChallengeManager { event OneStepProofCompleted(uint64 indexed challengeIndex); event ChallengeEnded(uint64 indexed challengeIndex, ChallengeTerminationType kind); - event ConditonalOSPSet(bytes32 indexed wasmModuleRoot, IOneStepProofEntry osp_); function initialize( IChallengeResultReceiver resultReceiver_, From 96774561636ceac0d3f4e5f915492993830c46db Mon Sep 17 00:00:00 2001 From: gzeon Date: Wed, 17 Apr 2024 02:36:26 +0800 Subject: [PATCH 107/126] docs: explain --- src/challenge/ChallengeManager.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/challenge/ChallengeManager.sol b/src/challenge/ChallengeManager.sol index 133ab1e2..9ff53dc8 100644 --- a/src/challenge/ChallengeManager.sol +++ b/src/challenge/ChallengeManager.sol @@ -111,6 +111,10 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { osp = osp_; } + /// @dev A osp breaking change is introduced as part of Stylus upgrade, where the new osp would not support + /// pre-Stylus legacy wasmModuleRoot. To ensure that the new osp is not used for legacy wasmModuleRoot, + /// we introduce a conditional OSP where condRoot should be set to the pre-Stylus root and condOsp should + /// be set to the pre-Stylus osp. The correct value should be handled by the upgrade action contract. function postUpgradeInit( IOneStepProofEntry osp_, bytes32 condRoot, From e3725f7dfe625248be2824e0e92aaf7b5d4164d5 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 16 Apr 2024 14:43:25 -0600 Subject: [PATCH 108/126] address review comments --- package.json | 2 +- src/chain/CacheManager.sol | 47 +++++++++++++++++++++++--------------- yarn.lock | 2 +- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 778a9679..9e56e19c 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@openzeppelin/contracts": "4.5.0", "@openzeppelin/contracts-upgradeable": "4.5.2", "patch-package": "^6.4.7", - "solady": "^0.0.182" + "solady": "=0.0.182" }, "private": false, "devDependencies": { diff --git a/src/chain/CacheManager.sol b/src/chain/CacheManager.sol index 42dfca3a..434560a8 100644 --- a/src/chain/CacheManager.sol +++ b/src/chain/CacheManager.sol @@ -27,11 +27,14 @@ contract CacheManager { error NotChainOwner(address sender); error AsmTooLarge(uint256 asm, uint256 queueSize, uint256 cacheSize); error AlreadyCached(bytes32 codehash); - error BidTooSmall(uint256 bid, uint256 min); + error BidTooSmall(uint192 bid, uint192 min); error BidsArePaused(); + error MakeSpaceTooLarge(uint64 size, uint64 limit); - event InsertBid(uint256 bid, bytes32 indexed codehash, uint64 size); - event DeleteBid(uint256 bid, bytes32 indexed codehash, uint64 size); + event InsertBid(bytes32 indexed codehash, uint192 bid, uint64 size); + event DeleteBid(bytes32 indexed codehash, uint192 bid, uint64 size); + event SetCacheSize(uint64 size); + event SetDecayRate(uint64 decay); event Pause(); event Unpause(); @@ -55,11 +58,13 @@ contract CacheManager { /// Sets the intended cache size. Note that the queue may temporarily be larger. function setCacheSize(uint64 newSize) external onlyOwner { cacheSize = newSize; + emit SetCacheSize(newSize); } /// Sets the intended decay factor. Does not modify existing bids. function setDecayRate(uint64 newDecay) external onlyOwner { decay = newDecay; + emit SetDecayRate(newDecay); } /// Disable new bids. @@ -83,7 +88,7 @@ contract CacheManager { /// Evicts up to `count` programs from the cache. function evictPrograms(uint256 count) public onlyOwner { while (bids.length() != 0 && count > 0) { - (uint256 bid, uint64 index) = _getBid(bids.pop()); + (uint192 bid, uint64 index) = _getBid(bids.pop()); _deleteEntry(bid, index); count -= 1; } @@ -111,16 +116,19 @@ contract CacheManager { } uint64 asm = _asmSize(codehash); - (uint256 bid, uint64 index) = _makeSpace(asm); + (uint192 bid, uint64 index) = _makeSpace(asm); return _addBid(bid, codehash, asm, index); } /// Evicts entries until enough space exists in the cache, reverting if payment is insufficient. /// Returns the new amount of space available on success. - /// Note: will only make up to 5Mb of space. Call repeatedly for more. + /// Note: will revert for requests larger than 5Mb. Call repeatedly for more. function makeSpace(uint64 size) external payable returns (uint64 space) { + if (isPaused) { + revert BidsArePaused(); + } if (size > MAX_MAKE_SPACE) { - size = MAX_MAKE_SPACE; + revert MakeSpaceTooLarge(size, MAX_MAKE_SPACE); } _makeSpace(size); return cacheSize - queueSize; @@ -128,13 +136,14 @@ contract CacheManager { /// Evicts entries until enough space exists in the cache, reverting if payment is insufficient. /// Returns the bid and the index to use for insertion. - function _makeSpace(uint64 size) internal returns (uint256 bid, uint64 index) { + function _makeSpace(uint64 size) internal returns (uint192 bid, uint64 index) { // discount historical bids by the number of seconds - bid = msg.value + block.timestamp * uint256(decay); + bid = uint192(msg.value + block.timestamp * uint256(decay)); index = uint64(entries.length); - uint256 min; - while (queueSize + size > cacheSize) { + uint192 min; + uint64 limit = cacheSize; + while (queueSize + size > limit) { (min, index) = _getBid(bids.pop()); _deleteEntry(min, index); } @@ -145,7 +154,7 @@ contract CacheManager { /// Adds a bid function _addBid( - uint256 bid, + uint192 bid, bytes32 code, uint64 size, uint64 index @@ -163,27 +172,27 @@ contract CacheManager { } else { entries[index] = entry; } - emit InsertBid(bid, code, size); + emit InsertBid(code, bid, size); } /// Clears the entry at the given index - function _deleteEntry(uint256 bid, uint64 index) internal { + function _deleteEntry(uint192 bid, uint64 index) internal { Entry memory entry = entries[index]; ARB_WASM_CACHE.evictCodehash(entry.code); queueSize -= entry.size; - emit DeleteBid(bid, entry.code, entry.size); + emit DeleteBid(entry.code, bid, entry.size); delete entries[index]; } /// Gets the bid and index from a packed bid item - function _getBid(uint256 info) internal pure returns (uint256 bid, uint64 index) { - bid = info >> 64; + function _getBid(uint256 info) internal pure returns (uint192 bid, uint64 index) { + bid = uint192(info >> 64); index = uint64(info); } /// Creates a packed bid item - function _packBid(uint256 bid, uint64 index) internal pure returns (uint256) { - return (bid << 64) | uint256(index); + function _packBid(uint192 bid, uint64 index) internal pure returns (uint256) { + return (uint256(bid) << 64) | uint256(index); } /// Gets the size of the given program in bytes diff --git a/yarn.lock b/yarn.lock index f0bb0c69..0c28d401 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6931,7 +6931,7 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -solady@^0.0.182: +solady@=0.0.182: version "0.0.182" resolved "https://registry.yarnpkg.com/solady/-/solady-0.0.182.tgz#bd8c47f128a3a752358ad052782773966d74c400" integrity sha512-FW6xo1akJoYpkXMzu58/56FcNU3HYYNamEbnFO3iSibXk0nSHo0DV2Gu/zI3FPg3So5CCX6IYli1TT1IWATnvg== From 1e64e2ad4aeb5b4f37476f6df5843d336cf3b9aa Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 17 Apr 2024 17:17:02 -0600 Subject: [PATCH 109/126] remove '=' in pin --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9e56e19c..6db0b0c4 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@openzeppelin/contracts": "4.5.0", "@openzeppelin/contracts-upgradeable": "4.5.2", "patch-package": "^6.4.7", - "solady": "=0.0.182" + "solady": "0.0.182" }, "private": false, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 0c28d401..117dabfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6931,7 +6931,7 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -solady@=0.0.182: +solady@0.0.182: version "0.0.182" resolved "https://registry.yarnpkg.com/solady/-/solady-0.0.182.tgz#bd8c47f128a3a752358ad052782773966d74c400" integrity sha512-FW6xo1akJoYpkXMzu58/56FcNU3HYYNamEbnFO3iSibXk0nSHo0DV2Gu/zI3FPg3So5CCX6IYli1TT1IWATnvg== From 8c22b28b1bec3eb5a6c143ff6bdeb4322e25f9bf Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 25 Apr 2024 00:11:32 +0800 Subject: [PATCH 110/126] refactor: move machine hash logic to osp --- src/challenge/ChallengeLib.sol | 45 ---------------------------- src/challenge/ChallengeManager.sol | 7 ++--- src/osp/IOneStepProofEntry.sol | 10 +++++++ src/osp/OneStepProofEntry.sol | 47 ++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/challenge/ChallengeLib.sol b/src/challenge/ChallengeLib.sol index 25ff894d..6aaa3579 100644 --- a/src/challenge/ChallengeLib.sol +++ b/src/challenge/ChallengeLib.sol @@ -48,51 +48,6 @@ library ChallengeLib { return challenge.timeUsedSinceLastMove() > challenge.current.timeLeft; } - function getStartMachineHash(bytes32 globalStateHash, bytes32 wasmModuleRoot) - internal - pure - returns (bytes32) - { - // Start the value stack with the function call ABI for the entrypoint - Value[] memory startingValues = new Value[](3); - startingValues[0] = ValueLib.newRefNull(); - startingValues[1] = ValueLib.newI32(0); - startingValues[2] = ValueLib.newI32(0); - ValueArray memory valuesArray = ValueArray({inner: startingValues}); - ValueStack memory values = ValueStack({proved: valuesArray, remainingHash: 0}); - ValueStack memory internalStack; - StackFrameWindow memory frameStack; - - Machine memory mach = Machine({ - status: MachineStatus.RUNNING, - valueStack: values, - internalStack: internalStack, - frameStack: frameStack, - globalStateHash: globalStateHash, - moduleIdx: 0, - functionIdx: 0, - functionPc: 0, - modulesRoot: wasmModuleRoot - }); - return mach.hash(); - } - - function getEndMachineHash(MachineStatus status, bytes32 globalStateHash) - internal - pure - returns (bytes32) - { - if (status == MachineStatus.FINISHED) { - return keccak256(abi.encodePacked("Machine finished:", globalStateHash)); - } else if (status == MachineStatus.ERRORED) { - return keccak256(abi.encodePacked("Machine errored:")); - } else if (status == MachineStatus.TOO_FAR) { - return keccak256(abi.encodePacked("Machine too far:")); - } else { - revert("BAD_BLOCK_STATUS"); - } - } - function extractChallengeSegment(SegmentSelection calldata selection) internal pure diff --git a/src/challenge/ChallengeManager.sol b/src/challenge/ChallengeManager.sol index c5427e7e..99a81f54 100644 --- a/src/challenge/ChallengeManager.sol +++ b/src/challenge/ChallengeManager.sol @@ -233,11 +233,8 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { } bytes32[] memory segments = new bytes32[](2); - segments[0] = ChallengeLib.getStartMachineHash( - globalStateHashes[0], - challenge.wasmModuleRoot - ); - segments[1] = ChallengeLib.getEndMachineHash(machineStatuses[1], globalStateHashes[1]); + segments[0] = osp.getStartMachineHash(globalStateHashes[0], challenge.wasmModuleRoot); + segments[1] = osp.getEndMachineHash(machineStatuses[1], globalStateHashes[1]); challenge.mode = ChallengeLib.ChallengeMode.EXECUTION; diff --git a/src/osp/IOneStepProofEntry.sol b/src/osp/IOneStepProofEntry.sol index fb00b74f..552819cf 100644 --- a/src/osp/IOneStepProofEntry.sol +++ b/src/osp/IOneStepProofEntry.sol @@ -11,6 +11,16 @@ library OneStepProofEntryLib { } interface IOneStepProofEntry { + function getStartMachineHash(bytes32 globalStateHash, bytes32 wasmModuleRoot) + external + pure + returns (bytes32); + + function getEndMachineHash(MachineStatus status, bytes32 globalStateHash) + external + pure + returns (bytes32); + function proveOneStep( ExecutionContext calldata execCtx, uint256 machineStep, diff --git a/src/osp/OneStepProofEntry.sol b/src/osp/OneStepProofEntry.sol index 390727c3..4ccc438c 100644 --- a/src/osp/OneStepProofEntry.sol +++ b/src/osp/OneStepProofEntry.sol @@ -31,6 +31,53 @@ contract OneStepProofEntry is IOneStepProofEntry { proverHostIo = proverHostIo_; } + // Copied from ChallengeLib.sol + function getStartMachineHash(bytes32 globalStateHash, bytes32 wasmModuleRoot) + external + pure + returns (bytes32) + { + // Start the value stack with the function call ABI for the entrypoint + Value[] memory startingValues = new Value[](3); + startingValues[0] = ValueLib.newRefNull(); + startingValues[1] = ValueLib.newI32(0); + startingValues[2] = ValueLib.newI32(0); + ValueArray memory valuesArray = ValueArray({inner: startingValues}); + ValueStack memory values = ValueStack({proved: valuesArray, remainingHash: 0}); + ValueStack memory internalStack; + StackFrameWindow memory frameStack; + + Machine memory mach = Machine({ + status: MachineStatus.RUNNING, + valueStack: values, + internalStack: internalStack, + frameStack: frameStack, + globalStateHash: globalStateHash, + moduleIdx: 0, + functionIdx: 0, + functionPc: 0, + modulesRoot: wasmModuleRoot + }); + return mach.hash(); + } + + // Copied from ChallengeLib.sol + function getEndMachineHash(MachineStatus status, bytes32 globalStateHash) + external + pure + returns (bytes32) + { + if (status == MachineStatus.FINISHED) { + return keccak256(abi.encodePacked("Machine finished:", globalStateHash)); + } else if (status == MachineStatus.ERRORED) { + return keccak256(abi.encodePacked("Machine errored:")); + } else if (status == MachineStatus.TOO_FAR) { + return keccak256(abi.encodePacked("Machine too far:")); + } else { + revert("BAD_BLOCK_STATUS"); + } + } + function proveOneStep( ExecutionContext calldata execCtx, uint256 machineStep, From 46615835aaa92a7c534c2a96a67b636410a5ba25 Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 25 Apr 2024 00:14:05 +0800 Subject: [PATCH 111/126] chore: add OneStepProofEntry to test --- test/signatures/OneStepProofEntry | 9 +++++++++ test/signatures/test-sigs.bash | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 test/signatures/OneStepProofEntry diff --git a/test/signatures/OneStepProofEntry b/test/signatures/OneStepProofEntry new file mode 100644 index 00000000..404d208c --- /dev/null +++ b/test/signatures/OneStepProofEntry @@ -0,0 +1,9 @@ +{ + "getEndMachineHash(uint8,bytes32)": "d8558b87", + "getStartMachineHash(bytes32,bytes32)": "04997be4", + "proveOneStep((uint256,address),uint256,bytes32,bytes)": "5d3adcfb", + "prover0()": "30a5509f", + "proverHostIo()": "5f52fd7c", + "proverMath()": "66e5d9c3", + "proverMem()": "1f128bc0" +} diff --git a/test/signatures/test-sigs.bash b/test/signatures/test-sigs.bash index 4ff16cc3..70193d7f 100755 --- a/test/signatures/test-sigs.bash +++ b/test/signatures/test-sigs.bash @@ -1,6 +1,6 @@ #!/bin/bash output_dir="./test/signatures" -for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox ChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox BridgeCreator DeployHelper RollupCreator +for CONTRACTNAME in Bridge Inbox Outbox RollupCore RollupUserLogic RollupAdminLogic SequencerInbox ChallengeManager ERC20Bridge ERC20Inbox ERC20Outbox BridgeCreator DeployHelper RollupCreator OneStepProofEntry do echo "Checking for signature changes in $CONTRACTNAME" [ -f "$output_dir/$CONTRACTNAME" ] && mv "$output_dir/$CONTRACTNAME" "$output_dir/$CONTRACTNAME-old" From 82e9425212beae99bb6e54d3ab771252f1d7b9d1 Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 25 Apr 2024 00:49:56 +0800 Subject: [PATCH 112/126] fix: use machine hash from osp --- src/challenge/ChallengeManager.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/challenge/ChallengeManager.sol b/src/challenge/ChallengeManager.sol index b55d671f..fc57b5eb 100644 --- a/src/challenge/ChallengeManager.sol +++ b/src/challenge/ChallengeManager.sol @@ -250,8 +250,9 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { } bytes32[] memory segments = new bytes32[](2); - segments[0] = osp.getStartMachineHash(globalStateHashes[0], challenge.wasmModuleRoot); - segments[1] = osp.getEndMachineHash(machineStatuses[1], globalStateHashes[1]); + IOneStepProofEntry _osp = getOsp(challenge.wasmModuleRoot); + segments[0] = _osp.getStartMachineHash(globalStateHashes[0], challenge.wasmModuleRoot); + segments[1] = _osp.getEndMachineHash(machineStatuses[1], globalStateHashes[1]); challenge.mode = ChallengeLib.ChallengeMode.EXECUTION; From 988618b76bd22824e64a71f9d4940773a80722f6 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Thu, 25 Apr 2024 10:21:07 +0200 Subject: [PATCH 113/126] Properly calculate and configure validator addresses --- scripts/rollupCreation.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 3e1d300c..752b480a 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -77,6 +77,7 @@ export async function createRollup( rollupCreatorAbi, signer ) + const validatorWalletCreator = await rollupCreator.validatorWalletCreator() try { //// funds for deploying L2 factories @@ -96,7 +97,7 @@ export async function createRollup( // Call the createRollup function console.log('Calling createRollup to generate a new rollup ...') const deployParams = isDevDeployment - ? await _getDevRollupConfig(feeToken) + ? await _getDevRollupConfig(feeToken, validatorWalletCreator) : { config: config.rollupConfig, validators: config.validators, @@ -223,7 +224,10 @@ export async function createRollup( return null } -async function _getDevRollupConfig(feeToken: string) { +async function _getDevRollupConfig( + feeToken: string, + validatorWalletCreator: string +) { // set up owner address const ownerAddress = process.env.OWNER_ADDRESS !== undefined ? process.env.OWNER_ADDRESS : '' @@ -239,7 +243,7 @@ async function _getDevRollupConfig(feeToken: string) { parseInt(process.env.AUTHORIZE_VALIDATORS as string, 0) || 0 const validators: string[] = [] for (let i = 1; i <= authorizeValidators; i++) { - validators.push(ethers.Wallet.createRandom().address) + validators.push(_createValidatorAddress(validatorWalletCreator, i)) } // get chain config @@ -322,4 +326,15 @@ async function _getDevRollupConfig(feeToken: string) { batchPosters: batchPosters, batchPosterManager: batchPosterManager, } + + function _createValidatorAddress( + deployerAddress: string, + nonce: number + ): string { + const nonceHex = BigNumber.from(nonce).toHexString() + return ethers.utils.getContractAddress({ + from: deployerAddress, + nonce: nonceHex, + }) + } } From 0ab2b78d89f31db9966d181e6d524b3a3843af43 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Thu, 25 Apr 2024 17:40:52 +0200 Subject: [PATCH 114/126] Add unit test to test conditional OSP --- test/foundry/ChallengeManager.t.sol | 105 +++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 10 deletions(-) diff --git a/test/foundry/ChallengeManager.t.sol b/test/foundry/ChallengeManager.t.sol index 9339812b..c4f127f9 100644 --- a/test/foundry/ChallengeManager.t.sol +++ b/test/foundry/ChallengeManager.t.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.4; import "forge-std/Test.sol"; import "./util/TestUtil.sol"; import "../../src/challenge/ChallengeManager.sol"; +import "../../src/osp/OneStepProofEntry.sol"; +import "forge-std/console.sol"; contract ChallengeManagerTest is Test { IChallengeResultReceiver resultReceiver = IChallengeResultReceiver(address(137)); @@ -23,20 +25,106 @@ contract ChallengeManagerTest is Test { ); chalman.initialize(resultReceiver, sequencerInbox, bridge, osp); assertEq( - address(chalman.resultReceiver()), - address(resultReceiver), - "Result receiver not set" + address(chalman.resultReceiver()), address(resultReceiver), "Result receiver not set" ); assertEq( - address(chalman.sequencerInbox()), - address(sequencerInbox), - "Sequencer inbox not set" + address(chalman.sequencerInbox()), address(sequencerInbox), "Sequencer inbox not set" ); assertEq(address(chalman.bridge()), address(bridge), "Bridge not set"); assertEq(address(chalman.osp()), address(osp), "OSP not set"); return chalman; } + function testCondOsp() public { + ChallengeManager chalman = deploy(); + + /// legacy root and OSP that will be used as conditional + IOneStepProofEntry legacyOSP = IOneStepProofEntry( + address( + new OneStepProofEntry( + IOneStepProver(makeAddr("0")), + IOneStepProver(makeAddr("mem")), + IOneStepProver(makeAddr("math")), + IOneStepProver(makeAddr("hostio")) + ) + ) + ); + bytes32 legacyRoot = keccak256(abi.encodePacked("legacyRoot")); + + // legacy hashes + bytes32 legacySegment0 = legacyOSP.getStartMachineHash( + keccak256(abi.encodePacked("globalStateHash[0]")), legacyRoot + ); + bytes32 legacySegment1 = legacyOSP.getEndMachineHash( + MachineStatus.FINISHED, keccak256(abi.encodePacked("globalStateHashes[1]")) + ); + + /// new OSP + IOneStepProofEntry _newOSP = IOneStepProofEntry( + address( + new OneStepProofEntry( + IOneStepProver(makeAddr("0")), + IOneStepProver(makeAddr("mem")), + IOneStepProver(makeAddr("math")), + IOneStepProver(makeAddr("hostio")) + ) + ) + ); + + // new hashes + bytes32 newSegment0 = _newOSP.getStartMachineHash( + keccak256(abi.encodePacked("globalStateHash[0]")), randomRoot + ); + bytes32 newSegment1 = _newOSP.getEndMachineHash( + MachineStatus.FINISHED, keccak256(abi.encodePacked("new_globalStateHashes[1]")) + ); + + /// do upgrade + vm.prank(proxyAdmin); + TransparentUpgradeableProxy(payable(address(chalman))).upgradeToAndCall( + address(chalmanImpl), + abi.encodeWithSelector( + ChallengeManager.postUpgradeInit.selector, _newOSP, legacyRoot, legacyOSP + ) + ); + + /// check cond osp + IOneStepProofEntry _condOsp = chalman.getOsp(legacyRoot); + assertEq(address(_condOsp), address(legacyOSP), "Legacy osp not set"); + assertEq( + _condOsp.getStartMachineHash( + keccak256(abi.encodePacked("globalStateHash[0]")), legacyRoot + ), + legacySegment0, + "Unexpected start machine hash" + ); + assertEq( + _condOsp.getEndMachineHash( + MachineStatus.FINISHED, keccak256(abi.encodePacked("globalStateHashes[1]")) + ), + legacySegment1, + "Unexpected end machine hash" + ); + + /// check new osp + IOneStepProofEntry _newOsp = chalman.getOsp(randomRoot); + assertEq(address(_newOsp), address(_newOSP), "New osp not set"); + assertEq( + _newOsp.getStartMachineHash( + keccak256(abi.encodePacked("globalStateHash[0]")), randomRoot + ), + newSegment0, + "Unexpected start machine hash" + ); + assertEq( + _newOsp.getEndMachineHash( + MachineStatus.FINISHED, keccak256(abi.encodePacked("new_globalStateHashes[1]")) + ), + newSegment1, + "Unexpected end machine hash" + ); + } + function testPostUpgradeInit() public { ChallengeManager chalman = deploy(); @@ -44,10 +132,7 @@ contract ChallengeManagerTest is Test { TransparentUpgradeableProxy(payable(address(chalman))).upgradeToAndCall( address(chalmanImpl), abi.encodeWithSelector( - ChallengeManager.postUpgradeInit.selector, - newOsp, - randomRoot, - condOsp + ChallengeManager.postUpgradeInit.selector, newOsp, randomRoot, condOsp ) ); From 7867bae59ab522f7eedd83d020329184c3a818da Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 26 Apr 2024 10:13:28 +0200 Subject: [PATCH 115/126] Add check --- test/foundry/ChallengeManager.t.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/foundry/ChallengeManager.t.sol b/test/foundry/ChallengeManager.t.sol index c4f127f9..16e0916e 100644 --- a/test/foundry/ChallengeManager.t.sol +++ b/test/foundry/ChallengeManager.t.sol @@ -123,6 +123,10 @@ contract ChallengeManagerTest is Test { newSegment1, "Unexpected end machine hash" ); + + /// check hashes are different + assertNotEq(legacySegment0, newSegment0, "Start machine hash should be different"); + assertNotEq(legacySegment1, newSegment1, "End machine hash should be different"); } function testPostUpgradeInit() public { From 8b6aded4627715c9114a54e2c31f980693773996 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 29 Apr 2024 22:38:18 -0600 Subject: [PATCH 116/126] add wasm to module & new precompile methods --- src/precompiles/ArbOwner.sol | 7 ++++--- src/precompiles/ArbWasm.sol | 8 ++++++-- src/state/Deserialize.sol | 3 +++ src/state/Module.sol | 2 ++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 2999480d..69273fc6 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -105,9 +105,6 @@ interface ArbOwner { /// @notice Sets the base cost of each additional wasm page function setWasmPageGas(uint16 gas) external; - /// @notice Sets the ramp that drives exponential wasm memory costs - function setWasmPageRamp(uint64 ramp) external; - /// @notice Sets the maximum number of pages a wasm may allocate function setWasmPageLimit(uint16 limit) external; @@ -116,6 +113,10 @@ interface ArbOwner { /// @param cached amount of gas paid in increments of 64 when the program is cached function setWasmMinInitGas(uint8 gas, uint16 cached) external; + /// @notice Sets the linear adjustment made to program init costs. + /// @param percent the adjustment (100% = no adjustment). + function setWasmInitCostScalar(uint64 percent) external; + /// @notice Sets the number of days after which programs deactivate function setWasmExpiryDays(uint16 _days) external; diff --git a/src/precompiles/ArbWasm.sol b/src/precompiles/ArbWasm.sol index 7d43b847..b690ed24 100644 --- a/src/precompiles/ArbWasm.sol +++ b/src/precompiles/ArbWasm.sol @@ -39,13 +39,13 @@ interface ArbWasm { /// @return version the program version (reverts for EVM contracts) function programVersion(address program) external view returns (uint16 version); - /// @notice Gets the cost to invoke the program (not including minInitGas) + /// @notice Gets the cost to invoke the program /// @return gas the amount of gas /// @return gasWhenCached the amount of gas if the program was recently used function programInitGas(address program) external view - returns (uint16 gas, uint16 gasWhenCached); + returns (uint64 gas, uint64 gasWhenCached); /// @notice Gets the memory footprint of the program at the given address in pages /// @return footprint the memory footprint of program in pages (reverts for EVM contracts) @@ -84,6 +84,10 @@ interface ArbWasm { /// @return cached amount of gas in increments of 64 when cached function minInitGas() external view returns (uint8 gas, uint8 cached); + /// @notice Gets the linear adjustment made to program init costs. + /// @return percent the adjustment (100% = no adjustment). + function initCostScalar() external view returns (uint64 percent); + /// @notice Gets the number of days after which programs deactivate /// @return _days the number of days function expiryDays() external view returns (uint16 _days); diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 04596a4f..71c7e224 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -232,17 +232,20 @@ library Deserialize { ModuleMemory memory mem; bytes32 tablesMerkleRoot; bytes32 functionsMerkleRoot; + bytes32 wasmHash; uint32 internalsOffset; (globalsMerkleRoot, offset) = b32(proof, offset); (mem, offset) = moduleMemory(proof, offset); (tablesMerkleRoot, offset) = b32(proof, offset); (functionsMerkleRoot, offset) = b32(proof, offset); + (wasmHash, offset) = b32(proof, offset); (internalsOffset, offset) = u32(proof, offset); mod = Module({ globalsMerkleRoot: globalsMerkleRoot, moduleMemory: mem, tablesMerkleRoot: tablesMerkleRoot, functionsMerkleRoot: functionsMerkleRoot, + wasmHash: wasmHash, internalsOffset: internalsOffset }); } diff --git a/src/state/Module.sol b/src/state/Module.sol index dc4776c7..76b382db 100644 --- a/src/state/Module.sol +++ b/src/state/Module.sol @@ -11,6 +11,7 @@ struct Module { ModuleMemory moduleMemory; bytes32 tablesMerkleRoot; bytes32 functionsMerkleRoot; + bytes32 wasmHash; uint32 internalsOffset; } @@ -26,6 +27,7 @@ library ModuleLib { mod.moduleMemory.hash(), mod.tablesMerkleRoot, mod.functionsMerkleRoot, + mod.wasmHash, mod.internalsOffset ) ); From 707e3d4cf077337a65d54ab8c8fd6b4e4d4bbd27 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 29 Apr 2024 23:22:31 -0600 Subject: [PATCH 117/126] revert wasm hash --- src/state/Deserialize.sol | 3 --- src/state/Module.sol | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 71c7e224..04596a4f 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -232,20 +232,17 @@ library Deserialize { ModuleMemory memory mem; bytes32 tablesMerkleRoot; bytes32 functionsMerkleRoot; - bytes32 wasmHash; uint32 internalsOffset; (globalsMerkleRoot, offset) = b32(proof, offset); (mem, offset) = moduleMemory(proof, offset); (tablesMerkleRoot, offset) = b32(proof, offset); (functionsMerkleRoot, offset) = b32(proof, offset); - (wasmHash, offset) = b32(proof, offset); (internalsOffset, offset) = u32(proof, offset); mod = Module({ globalsMerkleRoot: globalsMerkleRoot, moduleMemory: mem, tablesMerkleRoot: tablesMerkleRoot, functionsMerkleRoot: functionsMerkleRoot, - wasmHash: wasmHash, internalsOffset: internalsOffset }); } diff --git a/src/state/Module.sol b/src/state/Module.sol index 76b382db..dc4776c7 100644 --- a/src/state/Module.sol +++ b/src/state/Module.sol @@ -11,7 +11,6 @@ struct Module { ModuleMemory moduleMemory; bytes32 tablesMerkleRoot; bytes32 functionsMerkleRoot; - bytes32 wasmHash; uint32 internalsOffset; } @@ -27,7 +26,6 @@ library ModuleLib { mod.moduleMemory.hash(), mod.tablesMerkleRoot, mod.functionsMerkleRoot, - mod.wasmHash, mod.internalsOffset ) ); From a51e769b59bec60556ce1bc317d665843621ef03 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 29 Apr 2024 23:57:47 -0600 Subject: [PATCH 118/126] add extra hash (more powerful than just wasm) --- src/state/Deserialize.sol | 3 +++ src/state/Module.sol | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/state/Deserialize.sol b/src/state/Deserialize.sol index 04596a4f..7ff2cfad 100644 --- a/src/state/Deserialize.sol +++ b/src/state/Deserialize.sol @@ -232,17 +232,20 @@ library Deserialize { ModuleMemory memory mem; bytes32 tablesMerkleRoot; bytes32 functionsMerkleRoot; + bytes32 extraHash; uint32 internalsOffset; (globalsMerkleRoot, offset) = b32(proof, offset); (mem, offset) = moduleMemory(proof, offset); (tablesMerkleRoot, offset) = b32(proof, offset); (functionsMerkleRoot, offset) = b32(proof, offset); + (extraHash, offset) = b32(proof, offset); (internalsOffset, offset) = u32(proof, offset); mod = Module({ globalsMerkleRoot: globalsMerkleRoot, moduleMemory: mem, tablesMerkleRoot: tablesMerkleRoot, functionsMerkleRoot: functionsMerkleRoot, + extraHash: extraHash, internalsOffset: internalsOffset }); } diff --git a/src/state/Module.sol b/src/state/Module.sol index dc4776c7..07630067 100644 --- a/src/state/Module.sol +++ b/src/state/Module.sol @@ -11,6 +11,7 @@ struct Module { ModuleMemory moduleMemory; bytes32 tablesMerkleRoot; bytes32 functionsMerkleRoot; + bytes32 extraHash; uint32 internalsOffset; } @@ -26,6 +27,7 @@ library ModuleLib { mod.moduleMemory.hash(), mod.tablesMerkleRoot, mod.functionsMerkleRoot, + mod.extraHash, mod.internalsOffset ) ); From 09ff1db5fee0023eef2935bd236518f338cdd09c Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 30 Apr 2024 17:32:46 +0200 Subject: [PATCH 119/126] Remove debug --- test/foundry/ChallengeManager.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/foundry/ChallengeManager.t.sol b/test/foundry/ChallengeManager.t.sol index 16e0916e..88557db9 100644 --- a/test/foundry/ChallengeManager.t.sol +++ b/test/foundry/ChallengeManager.t.sol @@ -5,7 +5,6 @@ import "forge-std/Test.sol"; import "./util/TestUtil.sol"; import "../../src/challenge/ChallengeManager.sol"; import "../../src/osp/OneStepProofEntry.sol"; -import "forge-std/console.sol"; contract ChallengeManagerTest is Test { IChallengeResultReceiver resultReceiver = IChallengeResultReceiver(address(137)); From 3fad9a2c499ea42c08bd1ebaff1d3dc18fa2c4bb Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 7 May 2024 00:04:20 +1000 Subject: [PATCH 120/126] ci: use known working version of foundry --- .github/workflows/contract-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/contract-tests.yml b/.github/workflows/contract-tests.yml index 5637607f..28111add 100644 --- a/.github/workflows/contract-tests.yml +++ b/.github/workflows/contract-tests.yml @@ -112,7 +112,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly + version: nightly-11e7dfdacf7292e135efb21a717f4c6ebbfe6fdb - uses: OffchainLabs/actions/run-nitro-test-node@test-node-args with: From 68e179a453465ceaa55811c3daf07d33abdb83ba Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 7 May 2024 00:10:08 +1000 Subject: [PATCH 121/126] ci: use known working version of foundry 2 --- .github/workflows/contract-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/contract-tests.yml b/.github/workflows/contract-tests.yml index 28111add..9fee42e2 100644 --- a/.github/workflows/contract-tests.yml +++ b/.github/workflows/contract-tests.yml @@ -21,7 +21,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly + version: nightly-11e7dfdacf7292e135efb21a717f4c6ebbfe6fdb - name: Setup node/yarn uses: actions/setup-node@v3 @@ -50,7 +50,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly + version: nightly-11e7dfdacf7292e135efb21a717f4c6ebbfe6fdb - name: Setup nodejs uses: actions/setup-node@v2 @@ -62,7 +62,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly + version: nightly-11e7dfdacf7292e135efb21a717f4c6ebbfe6fdb - name: Install dependencies run: yarn install From d6154241ed9061327bb5ff5bfcaf4337bcadfb3a Mon Sep 17 00:00:00 2001 From: Pepper Lebeck-Jobe Date: Mon, 6 May 2024 16:11:33 +0200 Subject: [PATCH 122/126] Fix the nitro CI builds. For some reason, the master branch of nitro won't build the contracts if there are any remappings specified for a yul build. This change removes the remappings for the yul build. --- foundry.toml | 4 +++- remappings.txt | 5 ----- 2 files changed, 3 insertions(+), 6 deletions(-) delete mode 100644 remappings.txt diff --git a/foundry.toml b/foundry.toml index 774b6a24..e45c847d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -14,8 +14,10 @@ src = 'yul' out = 'out/yul' libs = ['node_modules', 'lib'] cache_path = 'forge-cache/yul' +remappings = [] +auto_detect_remappings = false [fmt] number_underscore = 'thousands' line_length = 100 -# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file +# See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/remappings.txt b/remappings.txt deleted file mode 100644 index f02afee2..00000000 --- a/remappings.txt +++ /dev/null @@ -1,5 +0,0 @@ -ds-test/=lib/forge-std/lib/ds-test/src/ -forge-std/=lib/forge-std/src/ - -@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ -@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/ \ No newline at end of file From c36913f4f82e21a0ae1aedc9565c950bb3e1fa82 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 7 May 2024 00:21:57 +1000 Subject: [PATCH 123/126] chore: keep static remapping --- foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/foundry.toml b/foundry.toml index e45c847d..b8918c76 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,6 +8,7 @@ optimizer = true optimizer_runs = 20000 via_ir = false solc_version = '0.8.9' +remappings = ['ds-test/=lib/forge-std/lib/ds-test/src/','forge-std/=lib/forge-std/src/','@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/','@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/'] [profile.yul] src = 'yul' From b9e21f0da1d06446dd7fb58e742c2c1adb10b375 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 7 May 2024 00:27:46 +1000 Subject: [PATCH 124/126] format: remappings --- foundry.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/foundry.toml b/foundry.toml index 5182f5b5..1bb66e83 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,7 +8,10 @@ optimizer = true optimizer_runs = 100 via_ir = false solc_version = '0.8.9' -remappings = ['ds-test/=lib/forge-std/lib/ds-test/src/','forge-std/=lib/forge-std/src/','@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/','@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/'] +remappings = ['ds-test/=lib/forge-std/lib/ds-test/src/', + 'forge-std/=lib/forge-std/src/', + '@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/', + '@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/'] [profile.yul] src = 'yul' From 8f3d93f76a3611665b467dd126d2cfb817bea8db Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 7 May 2024 00:39:18 +1000 Subject: [PATCH 125/126] Revert "ci: use known working version of foundry 2" This reverts commit 68e179a453465ceaa55811c3daf07d33abdb83ba. --- .github/workflows/contract-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/contract-tests.yml b/.github/workflows/contract-tests.yml index 9fee42e2..28111add 100644 --- a/.github/workflows/contract-tests.yml +++ b/.github/workflows/contract-tests.yml @@ -21,7 +21,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly-11e7dfdacf7292e135efb21a717f4c6ebbfe6fdb + version: nightly - name: Setup node/yarn uses: actions/setup-node@v3 @@ -50,7 +50,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly-11e7dfdacf7292e135efb21a717f4c6ebbfe6fdb + version: nightly - name: Setup nodejs uses: actions/setup-node@v2 @@ -62,7 +62,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly-11e7dfdacf7292e135efb21a717f4c6ebbfe6fdb + version: nightly - name: Install dependencies run: yarn install From 16c4eb1aa9851f9ad8c9182b5780fdc9549fad1a Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 7 May 2024 00:39:25 +1000 Subject: [PATCH 126/126] Revert "ci: use known working version of foundry" This reverts commit 3fad9a2c499ea42c08bd1ebaff1d3dc18fa2c4bb. --- .github/workflows/contract-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/contract-tests.yml b/.github/workflows/contract-tests.yml index 28111add..5637607f 100644 --- a/.github/workflows/contract-tests.yml +++ b/.github/workflows/contract-tests.yml @@ -112,7 +112,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly-11e7dfdacf7292e135efb21a717f4c6ebbfe6fdb + version: nightly - uses: OffchainLabs/actions/run-nitro-test-node@test-node-args with: