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/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/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..68c67e21 100644 --- a/src/precompiles/ArbDebug.sol +++ b/src/precompiles/ArbDebug.sol @@ -1,4 +1,4 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 diff --git a/src/precompiles/ArbOwner.sol b/src/precompiles/ArbOwner.sol index 165039c9..63be3a56 100644 --- a/src/precompiles/ArbOwner.sol +++ b/src/precompiles/ArbOwner.sol @@ -1,89 +1,100 @@ -// 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.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; + /// @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 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; + } }