From dad7b2eb65ba67d3d9240d321bc3dfa55808f8f0 Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Wed, 2 Oct 2024 13:26:28 +0900 Subject: [PATCH 01/19] FPS op-contracts v1.6.0 --- .../src/Safe/DeputyGuardianModule.sol | 22 +- .../contracts-bedrock/src/cannon/MIPS.sol | 953 ++---------------- .../src/cannon/PreimageOracle.sol | 54 +- .../src/dispute/AnchorStateRegistry.sol | 52 +- .../src/dispute/FaultDisputeGame.sol | 74 +- .../src/dispute/weth/DelayedWETH.sol | 8 +- 6 files changed, 274 insertions(+), 889 deletions(-) diff --git a/packages/tokamak/contracts-bedrock/src/Safe/DeputyGuardianModule.sol b/packages/tokamak/contracts-bedrock/src/Safe/DeputyGuardianModule.sol index d88fe317f..367acc001 100644 --- a/packages/tokamak/contracts-bedrock/src/Safe/DeputyGuardianModule.sol +++ b/packages/tokamak/contracts-bedrock/src/Safe/DeputyGuardianModule.sol @@ -4,11 +4,13 @@ pragma solidity 0.8.15; import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; import { Enum } from "safe-contracts/common/Enum.sol"; +import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { Unauthorized } from "src/libraries/PortalErrors.sol"; +import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; import "src/dispute/lib/Types.sol"; @@ -43,8 +45,8 @@ contract DeputyGuardianModule is ISemver { address internal immutable DEPUTY_GUARDIAN; /// @notice Semantic version. - /// @custom:semver 1.1.0 - string public constant version = "1.1.0"; + /// @custom:semver 2.0.0 + string public constant version = "2.0.0"; // Constructor to initialize the Safe and baseModule instances constructor(Safe _safe, SuperchainConfig _superchainConfig, address _deputyGuardian) { @@ -108,6 +110,22 @@ contract DeputyGuardianModule is ISemver { emit Unpaused(); } + /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments + /// necessary to call `setAnchorState()` on the `AnchorStateRegistry` contract. + /// Only the deputy guardian can call this function. + /// @param _registry The `AnchorStateRegistry` contract instance. + /// @param _game The `IFaultDisputeGame` contract instance. + function setAnchorState(AnchorStateRegistry _registry, IFaultDisputeGame _game) external { + _onlyDeputyGuardian(); + + bytes memory data = abi.encodeCall(AnchorStateRegistry.setAnchorState, (_game)); + (bool success, bytes memory returnData) = + SAFE.execTransactionFromModuleReturnData(address(_registry), 0, data, Enum.Operation.Call); + if (!success) { + revert ExecutionFailed(string(returnData)); + } + } + /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments /// necessary to call `blacklistDisputeGame()` on the `OptimismPortal2` contract. /// Only the deputy guardian can call this function. diff --git a/packages/tokamak/contracts-bedrock/src/cannon/MIPS.sol b/packages/tokamak/contracts-bedrock/src/cannon/MIPS.sol index b47fb9313..f3d2d4975 100644 --- a/packages/tokamak/contracts-bedrock/src/cannon/MIPS.sol +++ b/packages/tokamak/contracts-bedrock/src/cannon/MIPS.sol @@ -4,6 +4,10 @@ pragma solidity 0.8.15; import { ISemver } from "src/universal/ISemver.sol"; import { IPreimageOracle } from "./interfaces/IPreimageOracle.sol"; import { PreimageKeyLib } from "./PreimageKeyLib.sol"; +import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol"; +import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol"; +import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; +import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; /// @title MIPS /// @notice The MIPS contract emulates a single MIPS instruction. @@ -39,27 +43,16 @@ contract MIPS is ISemver { uint32[32] registers; } - /// @notice Start of the data segment. - uint32 public constant BRK_START = 0x40000000; - /// @notice The semantic version of the MIPS contract. - /// @custom:semver 1.0.1 - string public constant version = "1.0.1"; - - uint32 internal constant FD_STDIN = 0; - uint32 internal constant FD_STDOUT = 1; - uint32 internal constant FD_STDERR = 2; - uint32 internal constant FD_HINT_READ = 3; - uint32 internal constant FD_HINT_WRITE = 4; - uint32 internal constant FD_PREIMAGE_READ = 5; - uint32 internal constant FD_PREIMAGE_WRITE = 6; - - uint32 internal constant EBADF = 0x9; - uint32 internal constant EINVAL = 0x16; + /// @custom:semver 1.1.0 + string public constant version = "1.1.0"; /// @notice The preimage oracle contract. IPreimageOracle internal immutable ORACLE; + // The offset of the start of proof calldata (_proof.offset) in the step() function + uint256 internal constant STEP_PROOF_OFFSET = 420; + /// @param _oracle The address of the preimage oracle contract. constructor(IPreimageOracle _oracle) { ORACLE = _oracle; @@ -71,16 +64,6 @@ contract MIPS is ISemver { oracle_ = ORACLE; } - /// @notice Extends the value leftwards with its most significant bit (sign extension). - function SE(uint32 _dat, uint32 _idx) internal pure returns (uint32 out_) { - unchecked { - bool isSigned = (_dat >> (_idx - 1)) != 0; - uint256 signed = ((1 << (32 - _idx)) - 1) << _idx; - uint256 mask = (1 << _idx) - 1; - return uint32(_dat & mask | (isSigned ? signed : 0)); - } - } - /// @notice Computes the hash of the MIPS state. /// @return out_ The hashed MIPS state. function outputState() internal returns (bytes32 out_) { @@ -115,6 +98,14 @@ contract MIPS is ISemver { from, to := copyMem(from, to, 8) // step from := add(from, 32) // offset to registers + // Verify that the value of exited is valid (0 or 1) + if gt(exited, 1) { + // revert InvalidExitedValue(); + let ptr := mload(0x40) + mstore(ptr, shl(224, 0x0136cc76)) + revert(ptr, 0x04) + } + // Copy registers for { let i := 0 } lt(i, 32) { i := add(i, 1) } { from, to := copyMem(from, to, 4) } @@ -156,481 +147,60 @@ contract MIPS is ISemver { state := 0x80 } - // Load the syscall number from the registers - uint32 syscall_no = state.registers[2]; + // Load the syscall numbers and args from the registers + (uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2,) = sys.getSyscallArgs(state.registers); + uint32 v0 = 0; uint32 v1 = 0; - // Load the syscall arguments from the registers - uint32 a0 = state.registers[4]; - uint32 a1 = state.registers[5]; - uint32 a2 = state.registers[6]; - - // mmap: Allocates a page from the heap. - if (syscall_no == 4090) { - uint32 sz = a1; - if (sz & 4095 != 0) { - // adjust size to align with page size - sz += 4096 - (sz & 4095); - } - if (a0 == 0) { - v0 = state.heap; - state.heap += sz; - } else { - v0 = a0; - } - } - // brk: Returns a fixed address for the program break at 0x40000000 - else if (syscall_no == 4045) { - v0 = BRK_START; - } - // clone (not supported) returns 1 - else if (syscall_no == 4120) { + if (syscall_no == sys.SYS_MMAP) { + (v0, v1, state.heap) = sys.handleSysMmap(a0, a1, state.heap); + } else if (syscall_no == sys.SYS_BRK) { + // brk: Returns a fixed address for the program break at 0x40000000 + v0 = sys.PROGRAM_BREAK; + } else if (syscall_no == sys.SYS_CLONE) { + // clone (not supported) returns 1 v0 = 1; - } - // exit group: Sets the Exited and ExitCode states to true and argument 0. - else if (syscall_no == 4246) { + } else if (syscall_no == sys.SYS_EXIT_GROUP) { + // exit group: Sets the Exited and ExitCode states to true and argument 0. state.exited = true; state.exitCode = uint8(a0); return outputState(); - } - // read: Like Linux read syscall. Splits unaligned reads into aligned reads. - else if (syscall_no == 4003) { - // args: a0 = fd, a1 = addr, a2 = count - // returns: v0 = read, v1 = err code - if (a0 == FD_STDIN) { - // Leave v0 and v1 zero: read nothing, no error - } - // pre-image oracle read - else if (a0 == FD_PREIMAGE_READ) { - // verify proof 1 is correct, and get the existing memory. - uint32 mem = readMem(a1 & 0xFFffFFfc, 1); // mask the addr to align it to 4 bytes - bytes32 preimageKey = state.preimageKey; - // If the preimage key is a local key, localize it in the context of the caller. - if (uint8(preimageKey[0]) == 1) { - preimageKey = PreimageKeyLib.localize(preimageKey, _localContext); - } - (bytes32 dat, uint256 datLen) = ORACLE.readPreimage(preimageKey, state.preimageOffset); - - // Transform data for writing to memory - // We use assembly for more precise ops, and no var count limit - assembly { - let alignment := and(a1, 3) // the read might not start at an aligned address - let space := sub(4, alignment) // remaining space in memory word - if lt(space, datLen) { datLen := space } // if less space than data, shorten data - if lt(a2, datLen) { datLen := a2 } // if requested to read less, read less - dat := shr(sub(256, mul(datLen, 8)), dat) // right-align data - dat := shl(mul(sub(sub(4, datLen), alignment), 8), dat) // position data to insert into memory - // word - let mask := sub(shl(mul(sub(4, alignment), 8), 1), 1) // mask all bytes after start - let suffixMask := sub(shl(mul(sub(sub(4, alignment), datLen), 8), 1), 1) // mask of all bytes - // starting from end, maybe none - mask := and(mask, not(suffixMask)) // reduce mask to just cover the data we insert - mem := or(and(mem, not(mask)), dat) // clear masked part of original memory, and insert data - } - - // Write memory back - writeMem(a1 & 0xFFffFFfc, 1, mem); - state.preimageOffset += uint32(datLen); - v0 = uint32(datLen); - } - // hint response - else if (a0 == FD_HINT_READ) { - // Don't read into memory, just say we read it all - // The result is ignored anyway - v0 = a2; - } else { - v0 = 0xFFffFFff; - v1 = EBADF; - } - } - // write: like Linux write syscall. Splits unaligned writes into aligned writes. - else if (syscall_no == 4004) { - // args: a0 = fd, a1 = addr, a2 = count - // returns: v0 = written, v1 = err code - if (a0 == FD_STDOUT || a0 == FD_STDERR || a0 == FD_HINT_WRITE) { - v0 = a2; // tell program we have written everything - } - // pre-image oracle - else if (a0 == FD_PREIMAGE_WRITE) { - uint32 mem = readMem(a1 & 0xFFffFFfc, 1); // mask the addr to align it to 4 bytes - bytes32 key = state.preimageKey; - - // Construct pre-image key from memory - // We use assembly for more precise ops, and no var count limit - assembly { - let alignment := and(a1, 3) // the read might not start at an aligned address - let space := sub(4, alignment) // remaining space in memory word - if lt(space, a2) { a2 := space } // if less space than data, shorten data - key := shl(mul(a2, 8), key) // shift key, make space for new info - let mask := sub(shl(mul(a2, 8), 1), 1) // mask for extracting value from memory - mem := and(shr(mul(sub(space, a2), 8), mem), mask) // align value to right, mask it - key := or(key, mem) // insert into key - } - - // Write pre-image key to oracle - state.preimageKey = key; - state.preimageOffset = 0; // reset offset, to read new pre-image data from the start - v0 = a2; - } else { - v0 = 0xFFffFFff; - v1 = EBADF; - } - } - // fcntl: Like linux fcntl syscall, but only supports minimal file-descriptor control commands, - // to retrieve the file-descriptor R/W flags. - else if (syscall_no == 4055) { - // fcntl - // args: a0 = fd, a1 = cmd - if (a1 == 3) { - // F_GETFL: get file descriptor flags - if (a0 == FD_STDIN || a0 == FD_PREIMAGE_READ || a0 == FD_HINT_READ) { - v0 = 0; // O_RDONLY - } else if (a0 == FD_STDOUT || a0 == FD_STDERR || a0 == FD_PREIMAGE_WRITE || a0 == FD_HINT_WRITE) { - v0 = 1; // O_WRONLY - } else { - v0 = 0xFFffFFff; - v1 = EBADF; - } - } else { - v0 = 0xFFffFFff; - v1 = EINVAL; // cmd not recognized by this kernel - } - } - - // Write the results back to the state registers - state.registers[2] = v0; - state.registers[7] = v1; - - // Update the PC and nextPC - state.pc = state.nextPC; - state.nextPC = state.nextPC + 4; - - out_ = outputState(); - } - } - - /// @notice Handles a branch instruction, updating the MIPS state PC where needed. - /// @param _opcode The opcode of the branch instruction. - /// @param _insn The instruction to be executed. - /// @param _rtReg The register to be used for the branch. - /// @param _rs The register to be compared with the branch register. - /// @return out_ The hashed MIPS state. - function handleBranch(uint32 _opcode, uint32 _insn, uint32 _rtReg, uint32 _rs) internal returns (bytes32 out_) { - unchecked { - // Load state from memory - State memory state; - assembly { - state := 0x80 - } - - bool shouldBranch = false; - - if (state.nextPC != state.pc + 4) { - revert("branch in delay slot"); - } - - // beq/bne: Branch on equal / not equal - if (_opcode == 4 || _opcode == 5) { - uint32 rt = state.registers[_rtReg]; - shouldBranch = (_rs == rt && _opcode == 4) || (_rs != rt && _opcode == 5); - } - // blez: Branches if instruction is less than or equal to zero - else if (_opcode == 6) { - shouldBranch = int32(_rs) <= 0; - } - // bgtz: Branches if instruction is greater than zero - else if (_opcode == 7) { - shouldBranch = int32(_rs) > 0; - } - // bltz/bgez: Branch on less than zero / greater than or equal to zero - else if (_opcode == 1) { - // regimm - uint32 rtv = ((_insn >> 16) & 0x1F); - if (rtv == 0) { - shouldBranch = int32(_rs) < 0; - } - if (rtv == 1) { - shouldBranch = int32(_rs) >= 0; - } - } - - // Update the state's previous PC - uint32 prevPC = state.pc; - - // Execute the delay slot first - state.pc = state.nextPC; - - // If we should branch, update the PC to the branch target - // Otherwise, proceed to the next instruction - if (shouldBranch) { - state.nextPC = prevPC + 4 + (SE(_insn & 0xFFFF, 16) << 2); - } else { - state.nextPC = state.nextPC + 4; - } - - // Return the hash of the resulting state - out_ = outputState(); - } - } - - /// @notice Handles HI and LO register instructions. - /// @param _func The function code of the instruction. - /// @param _rs The value of the RS register. - /// @param _rt The value of the RT register. - /// @param _storeReg The register to store the result in. - /// @return out_ The hashed MIPS state. - function handleHiLo(uint32 _func, uint32 _rs, uint32 _rt, uint32 _storeReg) internal returns (bytes32 out_) { - unchecked { - // Load state from memory - State memory state; - assembly { - state := 0x80 - } - - uint32 val; - - // mfhi: Move the contents of the HI register into the destination - if (_func == 0x10) { - val = state.hi; - } - // mthi: Move the contents of the source into the HI register - else if (_func == 0x11) { - state.hi = _rs; - } - // mflo: Move the contents of the LO register into the destination - else if (_func == 0x12) { - val = state.lo; - } - // mtlo: Move the contents of the source into the LO register - else if (_func == 0x13) { - state.lo = _rs; - } - // mult: Multiplies `rs` by `rt` and stores the result in HI and LO registers - else if (_func == 0x18) { - uint64 acc = uint64(int64(int32(_rs)) * int64(int32(_rt))); - state.hi = uint32(acc >> 32); - state.lo = uint32(acc); - } - // multu: Unsigned multiplies `rs` by `rt` and stores the result in HI and LO registers - else if (_func == 0x19) { - uint64 acc = uint64(uint64(_rs) * uint64(_rt)); - state.hi = uint32(acc >> 32); - state.lo = uint32(acc); - } - // div: Divides `rs` by `rt`. - // Stores the quotient in LO - // And the remainder in HI - else if (_func == 0x1a) { - if (int32(_rt) == 0) { - revert("MIPS: division by zero"); - } - state.hi = uint32(int32(_rs) % int32(_rt)); - state.lo = uint32(int32(_rs) / int32(_rt)); - } - // divu: Unsigned divides `rs` by `rt`. - // Stores the quotient in LO - // And the remainder in HI - else if (_func == 0x1b) { - if (_rt == 0) { - revert("MIPS: division by zero"); - } - state.hi = _rs % _rt; - state.lo = _rs / _rt; - } - - // Store the result in the destination register, if applicable - if (_storeReg != 0) { - state.registers[_storeReg] = val; - } - - // Update the PC - state.pc = state.nextPC; - state.nextPC = state.nextPC + 4; - - // Return the hash of the resulting state - out_ = outputState(); - } - } + } else if (syscall_no == sys.SYS_READ) { + sys.SysReadParams memory args = sys.SysReadParams({ + a0: a0, + a1: a1, + a2: a2, + preimageKey: state.preimageKey, + preimageOffset: state.preimageOffset, + localContext: _localContext, + oracle: ORACLE, + proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), + memRoot: state.memRoot + }); + (v0, v1, state.preimageOffset, state.memRoot) = sys.handleSysRead(args); + } else if (syscall_no == sys.SYS_WRITE) { + (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ + _a0: a0, + _a1: a1, + _a2: a2, + _preimageKey: state.preimageKey, + _preimageOffset: state.preimageOffset, + _proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), + _memRoot: state.memRoot + }); + } else if (syscall_no == sys.SYS_FCNTL) { + (v0, v1) = sys.handleSysFcntl(a0, a1); + } + + st.CpuScalars memory cpu = getCpuScalars(state); + sys.handleSyscallUpdates(cpu, state.registers, v0, v1); + setStateCpuScalars(state, cpu); - /// @notice Handles a jump instruction, updating the MIPS state PC where needed. - /// @param _linkReg The register to store the link to the instruction after the delay slot instruction. - /// @param _dest The destination to jump to. - /// @return out_ The hashed MIPS state. - function handleJump(uint32 _linkReg, uint32 _dest) internal returns (bytes32 out_) { - unchecked { - // Load state from memory. - State memory state; - assembly { - state := 0x80 - } - - if (state.nextPC != state.pc + 4) { - revert("jump in delay slot"); - } - - // Update the next PC to the jump destination. - uint32 prevPC = state.pc; - state.pc = state.nextPC; - state.nextPC = _dest; - - // Update the link-register to the instruction after the delay slot instruction. - if (_linkReg != 0) { - state.registers[_linkReg] = prevPC + 8; - } - - // Return the hash of the resulting state. - out_ = outputState(); - } - } - - /// @notice Handles a storing a value into a register. - /// @param _storeReg The register to store the value into. - /// @param _val The value to store. - /// @param _conditional Whether or not the store is conditional. - /// @return out_ The hashed MIPS state. - function handleRd(uint32 _storeReg, uint32 _val, bool _conditional) internal returns (bytes32 out_) { - unchecked { - // Load state from memory. - State memory state; - assembly { - state := 0x80 - } - - // The destination register must be valid. - require(_storeReg < 32, "valid register"); - - // Never write to reg 0, and it can be conditional (movz, movn). - if (_storeReg != 0 && _conditional) { - state.registers[_storeReg] = _val; - } - - // Update the PC. - state.pc = state.nextPC; - state.nextPC = state.nextPC + 4; - - // Return the hash of the resulting state. out_ = outputState(); } } - /// @notice Computes the offset of the proof in the calldata. - /// @param _proofIndex The index of the proof in the calldata. - /// @return offset_ The offset of the proof in the calldata. - function proofOffset(uint8 _proofIndex) internal pure returns (uint256 offset_) { - unchecked { - // A proof of 32 bit memory, with 32-byte leaf values, is (32-5)=27 bytes32 entries. - // And the leaf value itself needs to be encoded as well. And proof.offset == 420 - offset_ = 420 + (uint256(_proofIndex) * (28 * 32)); - uint256 s = 0; - assembly { - s := calldatasize() - } - require(s >= (offset_ + 28 * 32), "check that there is enough calldata"); - return offset_; - } - } - - /// @notice Reads a 32-bit value from memory. - /// @param _addr The address to read from. - /// @param _proofIndex The index of the proof in the calldata. - /// @return out_ The hashed MIPS state. - function readMem(uint32 _addr, uint8 _proofIndex) internal pure returns (uint32 out_) { - unchecked { - // Compute the offset of the proof in the calldata. - uint256 offset = proofOffset(_proofIndex); - - assembly { - // Validate the address alignement. - if and(_addr, 3) { revert(0, 0) } - - // Load the leaf value. - let leaf := calldataload(offset) - offset := add(offset, 32) - - // Convenience function to hash two nodes together in scratch space. - function hashPair(a, b) -> h { - mstore(0, a) - mstore(32, b) - h := keccak256(0, 64) - } - - // Start with the leaf node. - // Work back up by combining with siblings, to reconstruct the root. - let path := shr(5, _addr) - let node := leaf - for { let i := 0 } lt(i, 27) { i := add(i, 1) } { - let sibling := calldataload(offset) - offset := add(offset, 32) - switch and(shr(i, path), 1) - case 0 { node := hashPair(node, sibling) } - case 1 { node := hashPair(sibling, node) } - } - - // Load the memory root from the first field of state. - let memRoot := mload(0x80) - - // Verify the root matches. - if iszero(eq(node, memRoot)) { - mstore(0, 0x0badf00d) - revert(0, 32) - } - - // Bits to shift = (32 - 4 - (addr % 32)) * 8 - let shamt := shl(3, sub(sub(32, 4), and(_addr, 31))) - out_ := and(shr(shamt, leaf), 0xFFffFFff) - } - } - } - - /// @notice Writes a 32-bit value to memory. - /// This function first overwrites the part of the leaf. - /// Then it recomputes the memory merkle root. - /// @param _addr The address to write to. - /// @param _proofIndex The index of the proof in the calldata. - /// @param _val The value to write. - function writeMem(uint32 _addr, uint8 _proofIndex, uint32 _val) internal pure { - unchecked { - // Compute the offset of the proof in the calldata. - uint256 offset = proofOffset(_proofIndex); - - assembly { - // Validate the address alignement. - if and(_addr, 3) { revert(0, 0) } - - // Load the leaf value. - let leaf := calldataload(offset) - let shamt := shl(3, sub(sub(32, 4), and(_addr, 31))) - - // Mask out 4 bytes, and OR in the value - leaf := or(and(leaf, not(shl(shamt, 0xFFffFFff))), shl(shamt, _val)) - offset := add(offset, 32) - - // Convenience function to hash two nodes together in scratch space. - function hashPair(a, b) -> h { - mstore(0, a) - mstore(32, b) - h := keccak256(0, 64) - } - - // Start with the leaf node. - // Work back up by combining with siblings, to reconstruct the root. - let path := shr(5, _addr) - let node := leaf - for { let i := 0 } lt(i, 27) { i := add(i, 1) } { - let sibling := calldataload(offset) - offset := add(offset, 32) - switch and(shr(i, path), 1) - case 0 { node := hashPair(node, sibling) } - case 1 { node := hashPair(sibling, node) } - } - - // Store the new memory root in the first field of state. - mstore(0x80, node) - } - } - } - /// @notice Executes a single step of the vm. /// Will revert if any required input state is missing. /// @param _stateData The encoded state witness data. @@ -655,7 +225,7 @@ contract MIPS is ISemver { // 32*4+4=132 expected state data offset revert(0, 0) } - if iszero(eq(_proof.offset, 420)) { + if iszero(eq(_proof.offset, STEP_PROOF_OFFSET)) { // 132+32+256=420 expected proof offset revert(0, 0) } @@ -681,10 +251,24 @@ contract MIPS is ISemver { c, m := putField(c, m, 4) // heap c, m := putField(c, m, 1) // exitCode c, m := putField(c, m, 1) // exited + let exited := mload(sub(m, 32)) c, m := putField(c, m, 8) // step + // Verify that the value of exited is valid (0 or 1) + if gt(exited, 1) { + // revert InvalidExitedValue(); + let ptr := mload(0x40) + mstore(ptr, shl(224, 0x0136cc76)) + revert(ptr, 0x04) + } + + // Compiler should have done this already + if iszero(eq(mload(m), add(m, 32))) { + // expected registers offset check + revert(0, 0) + } + // Unpack register calldata into memory - mstore(m, add(m, 32)) // offset to registers m := add(m, 32) for { let i := 0 } lt(i, 32) { i := add(i, 1) } { c, m := putField(c, m, 4) } } @@ -697,364 +281,41 @@ contract MIPS is ISemver { state.step += 1; // instruction fetch - uint32 insn = readMem(state.pc, 0); - uint32 opcode = insn >> 26; // 6-bits - - // j-type j/jal - if (opcode == 2 || opcode == 3) { - // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset - uint32 target = (state.nextPC & 0xF0000000) | (insn & 0x03FFFFFF) << 2; - return handleJump(opcode == 2 ? 0 : 31, target); - } - - // register fetch - uint32 rs; // source register 1 value - uint32 rt; // source register 2 / temp value - uint32 rtReg = (insn >> 16) & 0x1F; - - // R-type or I-type (stores rt) - rs = state.registers[(insn >> 21) & 0x1F]; - uint32 rdReg = rtReg; - - if (opcode == 0 || opcode == 0x1c) { - // R-type (stores rd) - rt = state.registers[rtReg]; - rdReg = (insn >> 11) & 0x1F; - } else if (opcode < 0x20) { - // rt is SignExtImm - // don't sign extend for andi, ori, xori - if (opcode == 0xC || opcode == 0xD || opcode == 0xe) { - // ZeroExtImm - rt = insn & 0xFFFF; - } else { - // SignExtImm - rt = SE(insn & 0xFFFF, 16); - } - } else if (opcode >= 0x28 || opcode == 0x22 || opcode == 0x26) { - // store rt value with store - rt = state.registers[rtReg]; - - // store actual rt with lwl and lwr - rdReg = rtReg; - } - - if ((opcode >= 4 && opcode < 8) || opcode == 1) { - return handleBranch(opcode, insn, rtReg, rs); - } - - uint32 storeAddr = 0xFF_FF_FF_FF; - // memory fetch (all I-type) - // we do the load for stores also - uint32 mem; - if (opcode >= 0x20) { - // M[R[rs]+SignExtImm] - rs += SE(insn & 0xFFFF, 16); - uint32 addr = rs & 0xFFFFFFFC; - mem = readMem(addr, 1); - if (opcode >= 0x28 && opcode != 0x30) { - // store - storeAddr = addr; - // store opcodes don't write back to a register - rdReg = 0; - } - } - - // ALU - uint32 val = execute(insn, rs, rt, mem) & 0xffFFffFF; // swr outputs more than 4 bytes without the mask - - uint32 func = insn & 0x3f; // 6-bits - if (opcode == 0 && func >= 8 && func < 0x1c) { - if (func == 8 || func == 9) { - // jr/jalr - return handleJump(func == 8 ? 0 : rdReg, rs); - } - - if (func == 0xa) { - // movz - return handleRd(rdReg, rs, rt == 0); - } - if (func == 0xb) { - // movn - return handleRd(rdReg, rs, rt != 0); - } - - // syscall (can read and write) - if (func == 0xC) { - return handleSyscall(_localContext); - } - - // lo and hi registers - // can write back - if (func >= 0x10 && func < 0x1c) { - return handleHiLo(func, rs, rt, rdReg); - } - } - - // stupid sc, write a 1 to rt - if (opcode == 0x38 && rtReg != 0) { - state.registers[rtReg] = 1; - } - - // write memory - if (storeAddr != 0xFF_FF_FF_FF) { - writeMem(storeAddr, 1, val); - } - - // write back the value to destination register - return handleRd(rdReg, val, true); + uint256 insnProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 0); + (uint32 insn, uint32 opcode, uint32 fun) = + ins.getInstructionDetails(state.pc, state.memRoot, insnProofOffset); + + // Handle syscall separately + // syscall (can read and write) + if (opcode == 0 && fun == 0xC) { + return handleSyscall(_localContext); + } + + // Exec the rest of the step logic + st.CpuScalars memory cpu = getCpuScalars(state); + (state.memRoot) = ins.execMipsCoreStepLogic({ + _cpu: cpu, + _registers: state.registers, + _memRoot: state.memRoot, + _memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), + _insn: insn, + _opcode: opcode, + _fun: fun + }); + setStateCpuScalars(state, cpu); + + return outputState(); } } - /// @notice Execute an instruction. - function execute(uint32 insn, uint32 rs, uint32 rt, uint32 mem) internal pure returns (uint32 out) { - unchecked { - uint32 opcode = insn >> 26; // 6-bits - - if (opcode == 0 || (opcode >= 8 && opcode < 0xF)) { - uint32 func = insn & 0x3f; // 6-bits - assembly { - // transform ArithLogI to SPECIAL - switch opcode - // addi - case 0x8 { func := 0x20 } - // addiu - case 0x9 { func := 0x21 } - // stli - case 0xA { func := 0x2A } - // sltiu - case 0xB { func := 0x2B } - // andi - case 0xC { func := 0x24 } - // ori - case 0xD { func := 0x25 } - // xori - case 0xE { func := 0x26 } - } + function getCpuScalars(State memory _state) internal pure returns (st.CpuScalars memory) { + return st.CpuScalars({ pc: _state.pc, nextPC: _state.nextPC, lo: _state.lo, hi: _state.hi }); + } - // sll - if (func == 0x00) { - return rt << ((insn >> 6) & 0x1F); - } - // srl - else if (func == 0x02) { - return rt >> ((insn >> 6) & 0x1F); - } - // sra - else if (func == 0x03) { - uint32 shamt = (insn >> 6) & 0x1F; - return SE(rt >> shamt, 32 - shamt); - } - // sllv - else if (func == 0x04) { - return rt << (rs & 0x1F); - } - // srlv - else if (func == 0x6) { - return rt >> (rs & 0x1F); - } - // srav - else if (func == 0x07) { - return SE(rt >> rs, 32 - rs); - } - // functs in range [0x8, 0x1b] are handled specially by other functions - // Explicitly enumerate each funct in range to reduce code diff against Go Vm - // jr - else if (func == 0x08) { - return rs; - } - // jalr - else if (func == 0x09) { - return rs; - } - // movz - else if (func == 0x0a) { - return rs; - } - // movn - else if (func == 0x0b) { - return rs; - } - // syscall - else if (func == 0x0c) { - return rs; - } - // 0x0d - break not supported - // sync - else if (func == 0x0f) { - return rs; - } - // mfhi - else if (func == 0x10) { - return rs; - } - // mthi - else if (func == 0x11) { - return rs; - } - // mflo - else if (func == 0x12) { - return rs; - } - // mtlo - else if (func == 0x13) { - return rs; - } - // mult - else if (func == 0x18) { - return rs; - } - // multu - else if (func == 0x19) { - return rs; - } - // div - else if (func == 0x1a) { - return rs; - } - // divu - else if (func == 0x1b) { - return rs; - } - // The rest includes transformed R-type arith imm instructions - // add - else if (func == 0x20) { - return (rs + rt); - } - // addu - else if (func == 0x21) { - return (rs + rt); - } - // sub - else if (func == 0x22) { - return (rs - rt); - } - // subu - else if (func == 0x23) { - return (rs - rt); - } - // and - else if (func == 0x24) { - return (rs & rt); - } - // or - else if (func == 0x25) { - return (rs | rt); - } - // xor - else if (func == 0x26) { - return (rs ^ rt); - } - // nor - else if (func == 0x27) { - return ~(rs | rt); - } - // slti - else if (func == 0x2a) { - return int32(rs) < int32(rt) ? 1 : 0; - } - // sltiu - else if (func == 0x2b) { - return rs < rt ? 1 : 0; - } else { - revert("invalid instruction"); - } - } else { - // SPECIAL2 - if (opcode == 0x1C) { - uint32 func = insn & 0x3f; // 6-bits - // mul - if (func == 0x2) { - return uint32(int32(rs) * int32(rt)); - } - // clz, clo - else if (func == 0x20 || func == 0x21) { - if (func == 0x20) { - rs = ~rs; - } - uint32 i = 0; - while (rs & 0x80000000 != 0) { - i++; - rs <<= 1; - } - return i; - } - } - // lui - else if (opcode == 0x0F) { - return rt << 16; - } - // lb - else if (opcode == 0x20) { - return SE((mem >> (24 - (rs & 3) * 8)) & 0xFF, 8); - } - // lh - else if (opcode == 0x21) { - return SE((mem >> (16 - (rs & 2) * 8)) & 0xFFFF, 16); - } - // lwl - else if (opcode == 0x22) { - uint32 val = mem << ((rs & 3) * 8); - uint32 mask = uint32(0xFFFFFFFF) << ((rs & 3) * 8); - return (rt & ~mask) | val; - } - // lw - else if (opcode == 0x23) { - return mem; - } - // lbu - else if (opcode == 0x24) { - return (mem >> (24 - (rs & 3) * 8)) & 0xFF; - } - // lhu - else if (opcode == 0x25) { - return (mem >> (16 - (rs & 2) * 8)) & 0xFFFF; - } - // lwr - else if (opcode == 0x26) { - uint32 val = mem >> (24 - (rs & 3) * 8); - uint32 mask = uint32(0xFFFFFFFF) >> (24 - (rs & 3) * 8); - return (rt & ~mask) | val; - } - // sb - else if (opcode == 0x28) { - uint32 val = (rt & 0xFF) << (24 - (rs & 3) * 8); - uint32 mask = 0xFFFFFFFF ^ uint32(0xFF << (24 - (rs & 3) * 8)); - return (mem & mask) | val; - } - // sh - else if (opcode == 0x29) { - uint32 val = (rt & 0xFFFF) << (16 - (rs & 2) * 8); - uint32 mask = 0xFFFFFFFF ^ uint32(0xFFFF << (16 - (rs & 2) * 8)); - return (mem & mask) | val; - } - // swl - else if (opcode == 0x2a) { - uint32 val = rt >> ((rs & 3) * 8); - uint32 mask = uint32(0xFFFFFFFF) >> ((rs & 3) * 8); - return (mem & ~mask) | val; - } - // sw - else if (opcode == 0x2b) { - return rt; - } - // swr - else if (opcode == 0x2e) { - uint32 val = rt << (24 - (rs & 3) * 8); - uint32 mask = uint32(0xFFFFFFFF) << (24 - (rs & 3) * 8); - return (mem & ~mask) | val; - } - // ll - else if (opcode == 0x30) { - return mem; - } - // sc - else if (opcode == 0x38) { - return rt; - } else { - revert("invalid instruction"); - } - } - revert("invalid instruction"); - } + function setStateCpuScalars(State memory _state, st.CpuScalars memory _cpu) internal pure { + _state.pc = _cpu.pc; + _state.nextPC = _cpu.nextPC; + _state.lo = _cpu.lo; + _state.hi = _cpu.hi; } } diff --git a/packages/tokamak/contracts-bedrock/src/cannon/PreimageOracle.sol b/packages/tokamak/contracts-bedrock/src/cannon/PreimageOracle.sol index 77dcfc20c..08ad2985a 100644 --- a/packages/tokamak/contracts-bedrock/src/cannon/PreimageOracle.sol +++ b/packages/tokamak/contracts-bedrock/src/cannon/PreimageOracle.sol @@ -27,10 +27,12 @@ contract PreimageOracle is IPreimageOracle, ISemver { uint256 public constant KECCAK_TREE_DEPTH = 16; /// @notice The maximum number of keccak blocks that can fit into the merkle tree. uint256 public constant MAX_LEAF_COUNT = 2 ** KECCAK_TREE_DEPTH - 1; + /// @notice The reserved gas for precompile call setup. + uint256 public constant PRECOMPILE_CALL_RESERVED_GAS = 100_000; /// @notice The semantic version of the Preimage Oracle contract. - /// @custom:semver 1.0.0 - string public constant version = "1.0.0"; + /// @custom:semver 1.1.2 + string public constant version = "1.1.2"; //////////////////////////////////////////////////////////////// // Authorized Preimage Parts // @@ -90,6 +92,11 @@ contract PreimageOracle is IPreimageOracle, ISemver { MIN_LPP_SIZE_BYTES = _minProposalSize; CHALLENGE_PERIOD = _challengePeriod; + // Make sure challenge period fits within uint64 so that it can safely be used within the + // FaultDisputeGame contract to compute clock extensions. Adding this check is simpler than + // changing the existing contract ABI. + require(_challengePeriod <= type(uint64).max, "challenge period too large"); + // Compute hashes in empty sparse Merkle tree. The first hash is not set, and kept as zero as the identity. for (uint256 height = 0; height < KECCAK_TREE_DEPTH - 1; height++) { zeroHashes[height + 1] = keccak256(abi.encodePacked(zeroHashes[height], zeroHashes[height])); @@ -131,7 +138,7 @@ contract PreimageOracle is IPreimageOracle, ISemver { key_ = PreimageKeyLib.localizeIdent(_ident, _localContext); // Revert if the given part offset is not within bounds. - if (_partOffset > _size + 8 || _size > 32) { + if (_partOffset >= _size + 8 || _size > 32) { revert PartOffsetOOB(); } @@ -332,7 +339,14 @@ contract PreimageOracle is IPreimageOracle, ISemver { } /// @inheritdoc IPreimageOracle - function loadPrecompilePreimagePart(uint256 _partOffset, address _precompile, bytes calldata _input) external { + function loadPrecompilePreimagePart( + uint256 _partOffset, + address _precompile, + uint64 _requiredGas, + bytes calldata _input + ) + external + { bytes32 res; bytes32 key; bytes32 part; @@ -341,21 +355,32 @@ contract PreimageOracle is IPreimageOracle, ISemver { // we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory. let ptr := 0x80 - // copy precompile address and input into memory - // len(sig) + len(_partOffset) + address-offset-in-slot - calldatacopy(ptr, 48, 20) - calldatacopy(add(20, ptr), _input.offset, _input.length) + // copy precompile address, requiredGas, and input into memory to compute the key + mstore(ptr, shl(96, _precompile)) + mstore(add(ptr, 20), shl(192, _requiredGas)) + calldatacopy(add(28, ptr), _input.offset, _input.length) // compute the hash - let h := keccak256(ptr, add(20, _input.length)) + let h := keccak256(ptr, add(28, _input.length)) // mask out prefix byte, replace with type 6 byte key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x06)) + // Check if the precompile call has at least the required gas. + // This assumes there are no further memory expansion costs until after the staticall on the precompile + // Also assumes that the gas expended in setting up the staticcall is less than PRECOMPILE_CALL_RESERVED_GAS + // require(gas() >= (requiredGas * 64 / 63) + reservedGas) + if lt(mul(gas(), 63), add(mul(_requiredGas, 64), mul(PRECOMPILE_CALL_RESERVED_GAS, 63))) { + // Store "NotEnoughGas()" + mstore(0, 0xdd629f86) + revert(0x1c, 4) + } + // Call the precompile to get the result. + // SAFETY: Given the above gas check, the staticall cannot fail due to insufficient gas. res := staticcall( gas(), // forward all gas _precompile, - add(20, ptr), // input ptr + add(28, ptr), // input ptr _input.length, 0x0, // Unused as we don't copy anything 0x00 // don't copy anything @@ -429,6 +454,10 @@ contract PreimageOracle is IPreimageOracle, ISemver { // Initialize the proposal metadata. LPPMetaData metaData = proposalMetadata[msg.sender][_uuid]; + + // Revert if the proposal has already been initialized. 0-size preimages are *not* allowed. + if (metaData.claimedSize() != 0) revert AlreadyInitialized(); + proposalMetadata[msg.sender][_uuid] = metaData.setPartOffset(_partOffset).setClaimedSize(_claimedSize); proposals.push(LargePreimageProposalKeys(msg.sender, _uuid)); @@ -484,7 +513,7 @@ contract PreimageOracle is IPreimageOracle, ISemver { let inputPtr := add(input, 0x20) // The input length must be a multiple of 136 bytes - // The input lenth / 136 must be equal to the number of state commitments. + // The input length / 136 must be equal to the number of state commitments. if or(mod(inputLen, 136), iszero(eq(_stateCommitments.length, div(inputLen, 136)))) { // Store "InvalidInputSize()" error selector mstore(0x00, 0x7b1daf1) @@ -653,6 +682,9 @@ contract PreimageOracle is IPreimageOracle, ISemver { // Check if the proposal was countered. if (metaData.countered()) revert BadProposal(); + // Check if the proposal has been finalized at all. + if (metaData.timestamp() == 0) revert ActiveProposal(); + // Check if the challenge period has passed since the proposal was finalized. if (block.timestamp - metaData.timestamp() <= CHALLENGE_PERIOD) revert ActiveProposal(); diff --git a/packages/tokamak/contracts-bedrock/src/dispute/AnchorStateRegistry.sol b/packages/tokamak/contracts-bedrock/src/dispute/AnchorStateRegistry.sol index c1f95f3df..ef742bd9e 100644 --- a/packages/tokamak/contracts-bedrock/src/dispute/AnchorStateRegistry.sol +++ b/packages/tokamak/contracts-bedrock/src/dispute/AnchorStateRegistry.sol @@ -8,8 +8,11 @@ import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistr import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; import "src/dispute/lib/Types.sol"; +import { Unauthorized } from "src/libraries/errors/CommonErrors.sol"; +import { UnregisteredGame, InvalidGameStatus } from "src/dispute/lib/Errors.sol"; /// @title AnchorStateRegistry /// @notice The AnchorStateRegistry is a contract that stores the latest "anchor" state for each available @@ -24,8 +27,8 @@ contract AnchorStateRegistry is Initializable, IAnchorStateRegistry, ISemver { } /// @notice Semantic version. - /// @custom:semver 1.0.0 - string public constant version = "1.0.0"; + /// @custom:semver 2.0.0 + string public constant version = "2.0.0"; /// @notice DisputeGameFactory address. IDisputeGameFactory internal immutable DISPUTE_GAME_FACTORY; @@ -33,21 +36,30 @@ contract AnchorStateRegistry is Initializable, IAnchorStateRegistry, ISemver { /// @inheritdoc IAnchorStateRegistry mapping(GameType => OutputRoot) public anchors; + /// @notice Address of the SuperchainConfig contract. + SuperchainConfig public superchainConfig; + /// @param _disputeGameFactory DisputeGameFactory address. constructor(IDisputeGameFactory _disputeGameFactory) { DISPUTE_GAME_FACTORY = _disputeGameFactory; - - // Initialize the implementation with an empty array of starting anchor roots. - initialize(new StartingAnchorRoot[](0)); + _disableInitializers(); } /// @notice Initializes the contract. /// @param _startingAnchorRoots An array of starting anchor roots. - function initialize(StartingAnchorRoot[] memory _startingAnchorRoots) public initializer { + /// @param _superchainConfig The address of the SuperchainConfig contract. + function initialize( + StartingAnchorRoot[] memory _startingAnchorRoots, + SuperchainConfig _superchainConfig + ) + public + initializer + { for (uint256 i = 0; i < _startingAnchorRoots.length; i++) { StartingAnchorRoot memory startingAnchorRoot = _startingAnchorRoots[i]; anchors[startingAnchorRoot.gameType] = startingAnchorRoot.outputRoot; } + superchainConfig = _superchainConfig; } /// @inheritdoc IAnchorStateRegistry @@ -67,10 +79,7 @@ contract AnchorStateRegistry is Initializable, IAnchorStateRegistry, ISemver { DISPUTE_GAME_FACTORY.games({ _gameType: gameType, _rootClaim: rootClaim, _extraData: extraData }); // Must be a valid game. - require( - address(factoryRegisteredGame) == address(game), - "AnchorStateRegistry: fault dispute game not registered with factory" - ); + if (address(factoryRegisteredGame) != address(game)) revert UnregisteredGame(); // No need to update anything if the anchor state is already newer. if (game.l2BlockNumber() <= anchors[gameType].l2BlockNumber) { @@ -85,4 +94,27 @@ contract AnchorStateRegistry is Initializable, IAnchorStateRegistry, ISemver { // Actually update the anchor state. anchors[gameType] = OutputRoot({ l2BlockNumber: game.l2BlockNumber(), root: Hash.wrap(game.rootClaim().raw()) }); } + + /// @inheritdoc IAnchorStateRegistry + function setAnchorState(IFaultDisputeGame _game) external { + if (msg.sender != superchainConfig.guardian()) revert Unauthorized(); + + // Get the metadata of the game. + (GameType gameType, Claim rootClaim, bytes memory extraData) = _game.gameData(); + + // Grab the verified address of the game based on the game data. + // slither-disable-next-line unused-return + (IDisputeGame factoryRegisteredGame,) = + DISPUTE_GAME_FACTORY.games({ _gameType: gameType, _rootClaim: rootClaim, _extraData: extraData }); + + // Must be a valid game. + if (address(factoryRegisteredGame) != address(_game)) revert UnregisteredGame(); + + // The game must have resolved in favor of the root claim. + if (_game.status() != GameStatus.DEFENDER_WINS) revert InvalidGameStatus(); + + // Update the anchor. + anchors[gameType] = + OutputRoot({ l2BlockNumber: _game.l2BlockNumber(), root: Hash.wrap(_game.rootClaim().raw()) }); + } } diff --git a/packages/tokamak/contracts-bedrock/src/dispute/FaultDisputeGame.sol b/packages/tokamak/contracts-bedrock/src/dispute/FaultDisputeGame.sol index ab559147e..1b181e50f 100644 --- a/packages/tokamak/contracts-bedrock/src/dispute/FaultDisputeGame.sol +++ b/packages/tokamak/contracts-bedrock/src/dispute/FaultDisputeGame.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.15; import { FixedPointMathLib } from "@solady/utils/FixedPointMathLib.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; @@ -69,8 +70,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver { uint256 internal constant HEADER_BLOCK_NUMBER_INDEX = 8; /// @notice Semantic version. - /// @custom:semver 1.2.0 - string public constant version = "1.2.0"; + /// @custom:semver 1.3.0 + string public constant version = "1.3.0"; /// @notice The starting timestamp of the game Timestamp public createdAt; @@ -136,11 +137,35 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver { ) { // The max game depth may not be greater than `LibPosition.MAX_POSITION_BITLEN - 1`. if (_maxGameDepth > LibPosition.MAX_POSITION_BITLEN - 1) revert MaxDepthTooLarge(); - // The split depth cannot be greater than or equal to the max game depth. - if (_splitDepth >= _maxGameDepth) revert InvalidSplitDepth(); - // The clock extension may not be greater than the max clock duration. - if (_clockExtension.raw() > _maxClockDuration.raw()) revert InvalidClockExtension(); + // The split depth plus one cannot be greater than or equal to the max game depth. We add + // an additional depth to the split depth to avoid a bug in trace ancestor lookup. We know + // that the case where the split depth is the max value for uint256 is equivalent to the + // second check though we do need to check it explicitly to avoid an overflow. + if (_splitDepth == type(uint256).max || _splitDepth + 1 >= _maxGameDepth) revert InvalidSplitDepth(); + + // The split depth cannot be 0 or 1 to stay in bounds of clock extension arithmetic. + if (_splitDepth < 2) revert InvalidSplitDepth(); + + // The PreimageOracle challenge period must fit into uint64 so we can safely use it here. + // Runtime check was added instead of changing the ABI since the contract is already + // deployed in production. We perform the same check within the PreimageOracle for the + // benefit of developers but also perform this check here defensively. + if (_vm.oracle().challengePeriod() > type(uint64).max) revert InvalidChallengePeriod(); + + // Determine the maximum clock extension which is either the split depth extension or the + // maximum game depth extension depending on the configuration of these contracts. + uint256 splitDepthExtension = uint256(_clockExtension.raw()) * 2; + uint256 maxGameDepthExtension = uint256(_clockExtension.raw()) + uint256(_vm.oracle().challengePeriod()); + uint256 maxClockExtension = Math.max(splitDepthExtension, maxGameDepthExtension); + + // The maximum clock extension must fit into a uint64. + if (maxClockExtension > type(uint64).max) revert InvalidClockExtension(); + + // The maximum clock extension may not be greater than the maximum clock duration. + if (uint64(maxClockExtension) > _maxClockDuration.raw()) revert InvalidClockExtension(); + + // Set up initial game state. GAME_TYPE = _gameType; ABSOLUTE_PRESTATE = _absolutePrestate; MAX_GAME_DEPTH = _maxGameDepth; @@ -368,17 +393,29 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver { // seconds of time. if (nextDuration.raw() == MAX_CLOCK_DURATION.raw()) revert ClockTimeExceeded(); - // If the remaining clock time has less than `CLOCK_EXTENSION` seconds remaining, grant the potential - // grandchild's clock `CLOCK_EXTENSION` seconds. This is to ensure that, even if a player has to inherit another - // team's clock to counter a freeloader claim, they will always have enough time to to respond. This extension - // is bounded by the depth of the tree. If the potential grandchild is an execution trace bisection root, the - // clock extension is doubled. This is to allow for extra time for the off-chain challenge agent to generate - // the initial instruction trace on the native FPVM. - if (nextDuration.raw() > MAX_CLOCK_DURATION.raw() - CLOCK_EXTENSION.raw()) { - // If the potential grandchild is an execution trace bisection root, double the clock extension. - uint64 extensionPeriod = - nextPositionDepth == SPLIT_DEPTH - 1 ? CLOCK_EXTENSION.raw() * 2 : CLOCK_EXTENSION.raw(); - nextDuration = Duration.wrap(MAX_CLOCK_DURATION.raw() - extensionPeriod); + // Clock extension is a mechanism that automatically extends the clock for a potential + // grandchild claim when there would be less than the clock extension time left if a player + // is forced to inherit another team's clock when countering a freeloader claim. Exact + // amount of clock extension time depends exactly where we are within the game. + uint64 actualExtension; + if (nextPositionDepth == MAX_GAME_DEPTH - 1) { + // If the next position is `MAX_GAME_DEPTH - 1` then we're about to execute a step. Our + // clock extension must therefore account for the LPP challenge period in addition to + // the standard clock extension. + actualExtension = CLOCK_EXTENSION.raw() + uint64(VM.oracle().challengePeriod()); + } else if (nextPositionDepth == SPLIT_DEPTH - 1) { + // If the next position is `SPLIT_DEPTH - 1` then we're about to begin an execution + // trace bisection and we need to give extra time for the off-chain challenge agent to + // be able to generate the initial instruction trace on the native FPVM. + actualExtension = CLOCK_EXTENSION.raw() * 2; + } else { + // Otherwise, we just use the standard clock extension. + actualExtension = CLOCK_EXTENSION.raw(); + } + + // Check if we need to apply the clock extension. + if (nextDuration.raw() > MAX_CLOCK_DURATION.raw() - actualExtension) { + nextDuration = Duration.wrap(MAX_CLOCK_DURATION.raw() - actualExtension); } // Construct the next clock with the new duration and the current block timestamp. @@ -452,6 +489,9 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver { // block number. uint256 l2Number = startingOutputRoot.l2BlockNumber + disputedPos.traceIndex(SPLIT_DEPTH) + 1; + // Choose the minimum between the `l2BlockNumber` claim and the bisected-to L2 block number. + l2Number = l2Number < l2BlockNumber() ? l2Number : l2BlockNumber(); + oracle.loadLocalData(_ident, uuid.raw(), bytes32(l2Number << 0xC0), 8, _partOffset); } else if (_ident == LocalPreimageKey.CHAIN_ID) { // Load the chain ID as a big-endian uint64 in the high order 8 bytes of the word. diff --git a/packages/tokamak/contracts-bedrock/src/dispute/weth/DelayedWETH.sol b/packages/tokamak/contracts-bedrock/src/dispute/weth/DelayedWETH.sol index c8a3f8f03..a3e684be4 100644 --- a/packages/tokamak/contracts-bedrock/src/dispute/weth/DelayedWETH.sol +++ b/packages/tokamak/contracts-bedrock/src/dispute/weth/DelayedWETH.sol @@ -21,8 +21,8 @@ import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; /// Not the prettiest contract in the world, but it gets the job done. contract DelayedWETH is OwnableUpgradeable, WETH98, IDelayedWETH, ISemver { /// @notice Semantic version. - /// @custom:semver 1.0.0 - string public constant version = "1.0.0"; + /// @custom:semver 1.1.0 + string public constant version = "1.1.0"; /// @inheritdoc IDelayedWETH mapping(address => mapping(address => WithdrawalRequest)) public withdrawals; @@ -85,7 +85,8 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, IDelayedWETH, ISemver { function recover(uint256 _wad) external { require(msg.sender == owner(), "DelayedWETH: not owner"); uint256 amount = _wad < address(this).balance ? _wad : address(this).balance; - payable(msg.sender).transfer(amount); + (bool success,) = payable(msg.sender).call{ value: amount }(hex""); + require(success, "DelayedWETH: recover failed"); } /// @inheritdoc IDelayedWETH @@ -95,3 +96,4 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, IDelayedWETH, ISemver { emit Approval(_guy, msg.sender, _wad); } } +ã…Š From 8d5c6688d7ac417bba88bb4e2a36522265d4802f Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 09:39:08 +0900 Subject: [PATCH 02/19] Update MIPS.sol import library --- .../src/cannon/libraries/MIPSInstructions.sol | 654 ++++++++++++++++++ .../src/cannon/libraries/MIPSMemory.sol | 126 ++++ .../src/cannon/libraries/MIPSState.sol | 11 + .../src/cannon/libraries/MIPSSyscalls.sol | 381 ++++++++++ 4 files changed, 1172 insertions(+) create mode 100644 packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol create mode 100644 packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol create mode 100644 packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSState.sol create mode 100644 packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol diff --git a/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol b/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol new file mode 100644 index 000000000..0652bea0d --- /dev/null +++ b/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; +import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; + +library MIPSInstructions { + /// @param _pc The program counter. + /// @param _memRoot The current memory root. + /// @param _insnProofOffset The calldata offset of the memory proof for the current instruction. + /// @return insn_ The current 32-bit instruction at the pc. + /// @return opcode_ The opcode value parsed from insn_. + /// @return fun_ The function value parsed from insn_. + function getInstructionDetails( + uint32 _pc, + bytes32 _memRoot, + uint256 _insnProofOffset + ) + internal + pure + returns (uint32 insn_, uint32 opcode_, uint32 fun_) + { + unchecked { + insn_ = MIPSMemory.readMem(_memRoot, _pc, _insnProofOffset); + opcode_ = insn_ >> 26; // First 6-bits + fun_ = insn_ & 0x3f; // Last 6-bits + + return (insn_, opcode_, fun_); + } + } + + /// @notice Execute core MIPS step logic. + /// @notice _cpu The CPU scalar fields. + /// @notice _registers The CPU registers. + /// @notice _memRoot The current merkle root of the memory. + /// @notice _memProofOffset The offset in calldata specify where the memory merkle proof is located. + /// @param _insn The current 32-bit instruction at the pc. + /// @param _opcode The opcode value parsed from insn_. + /// @param _fun The function value parsed from insn_. + /// @return newMemRoot_ The updated merkle root of memory after any modifications, may be unchanged. + function execMipsCoreStepLogic( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + bytes32 _memRoot, + uint256 _memProofOffset, + uint32 _insn, + uint32 _opcode, + uint32 _fun + ) + internal + pure + returns (bytes32 newMemRoot_) + { + unchecked { + newMemRoot_ = _memRoot; + + // j-type j/jal + if (_opcode == 2 || _opcode == 3) { + // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset + uint32 target = (_cpu.nextPC & 0xF0000000) | (_insn & 0x03FFFFFF) << 2; + handleJump(_cpu, _registers, _opcode == 2 ? 0 : 31, target); + return newMemRoot_; + } + + // register fetch + uint32 rs = 0; // source register 1 value + uint32 rt = 0; // source register 2 / temp value + uint32 rtReg = (_insn >> 16) & 0x1F; + + // R-type or I-type (stores rt) + rs = _registers[(_insn >> 21) & 0x1F]; + uint32 rdReg = rtReg; + + if (_opcode == 0 || _opcode == 0x1c) { + // R-type (stores rd) + rt = _registers[rtReg]; + rdReg = (_insn >> 11) & 0x1F; + } else if (_opcode < 0x20) { + // rt is SignExtImm + // don't sign extend for andi, ori, xori + if (_opcode == 0xC || _opcode == 0xD || _opcode == 0xe) { + // ZeroExtImm + rt = _insn & 0xFFFF; + } else { + // SignExtImm + rt = signExtend(_insn & 0xFFFF, 16); + } + } else if (_opcode >= 0x28 || _opcode == 0x22 || _opcode == 0x26) { + // store rt value with store + rt = _registers[rtReg]; + + // store actual rt with lwl and lwr + rdReg = rtReg; + } + + if ((_opcode >= 4 && _opcode < 8) || _opcode == 1) { + handleBranch({ + _cpu: _cpu, + _registers: _registers, + _opcode: _opcode, + _insn: _insn, + _rtReg: rtReg, + _rs: rs + }); + return newMemRoot_; + } + + uint32 storeAddr = 0xFF_FF_FF_FF; + // memory fetch (all I-type) + // we do the load for stores also + uint32 mem = 0; + if (_opcode >= 0x20) { + // M[R[rs]+SignExtImm] + rs += signExtend(_insn & 0xFFFF, 16); + uint32 addr = rs & 0xFFFFFFFC; + mem = MIPSMemory.readMem(_memRoot, addr, _memProofOffset); + if (_opcode >= 0x28 && _opcode != 0x30) { + // store + storeAddr = addr; + // store opcodes don't write back to a register + rdReg = 0; + } + } + + // ALU + // Note: swr outputs more than 4 bytes without the mask 0xffFFffFF + uint32 val = executeMipsInstruction(_insn, _opcode, _fun, rs, rt, mem) & 0xffFFffFF; + + if (_opcode == 0 && _fun >= 8 && _fun < 0x1c) { + if (_fun == 8 || _fun == 9) { + // jr/jalr + handleJump(_cpu, _registers, _fun == 8 ? 0 : rdReg, rs); + return newMemRoot_; + } + + if (_fun == 0xa) { + // movz + handleRd(_cpu, _registers, rdReg, rs, rt == 0); + return newMemRoot_; + } + if (_fun == 0xb) { + // movn + handleRd(_cpu, _registers, rdReg, rs, rt != 0); + return newMemRoot_; + } + + // lo and hi registers + // can write back + if (_fun >= 0x10 && _fun < 0x1c) { + handleHiLo({ _cpu: _cpu, _registers: _registers, _fun: _fun, _rs: rs, _rt: rt, _storeReg: rdReg }); + + return newMemRoot_; + } + } + + // stupid sc, write a 1 to rt + if (_opcode == 0x38 && rtReg != 0) { + _registers[rtReg] = 1; + } + + // write memory + if (storeAddr != 0xFF_FF_FF_FF) { + newMemRoot_ = MIPSMemory.writeMem(storeAddr, _memProofOffset, val); + } + + // write back the value to destination register + handleRd(_cpu, _registers, rdReg, val, true); + + return newMemRoot_; + } + } + + /// @notice Execute an instruction. + function executeMipsInstruction( + uint32 _insn, + uint32 _opcode, + uint32 _fun, + uint32 _rs, + uint32 _rt, + uint32 _mem + ) + internal + pure + returns (uint32 out_) + { + unchecked { + if (_opcode == 0 || (_opcode >= 8 && _opcode < 0xF)) { + assembly { + // transform ArithLogI to SPECIAL + switch _opcode + // addi + case 0x8 { _fun := 0x20 } + // addiu + case 0x9 { _fun := 0x21 } + // stli + case 0xA { _fun := 0x2A } + // sltiu + case 0xB { _fun := 0x2B } + // andi + case 0xC { _fun := 0x24 } + // ori + case 0xD { _fun := 0x25 } + // xori + case 0xE { _fun := 0x26 } + } + + // sll + if (_fun == 0x00) { + return _rt << ((_insn >> 6) & 0x1F); + } + // srl + else if (_fun == 0x02) { + return _rt >> ((_insn >> 6) & 0x1F); + } + // sra + else if (_fun == 0x03) { + uint32 shamt = (_insn >> 6) & 0x1F; + return signExtend(_rt >> shamt, 32 - shamt); + } + // sllv + else if (_fun == 0x04) { + return _rt << (_rs & 0x1F); + } + // srlv + else if (_fun == 0x6) { + return _rt >> (_rs & 0x1F); + } + // srav + else if (_fun == 0x07) { + // shamt here is different than the typical shamt which comes from the + // instruction itself, here it comes from the rs register + uint32 shamt = _rs & 0x1F; + return signExtend(_rt >> shamt, 32 - shamt); + } + // functs in range [0x8, 0x1b] are handled specially by other functions + // Explicitly enumerate each funct in range to reduce code diff against Go Vm + // jr + else if (_fun == 0x08) { + return _rs; + } + // jalr + else if (_fun == 0x09) { + return _rs; + } + // movz + else if (_fun == 0x0a) { + return _rs; + } + // movn + else if (_fun == 0x0b) { + return _rs; + } + // syscall + else if (_fun == 0x0c) { + return _rs; + } + // 0x0d - break not supported + // sync + else if (_fun == 0x0f) { + return _rs; + } + // mfhi + else if (_fun == 0x10) { + return _rs; + } + // mthi + else if (_fun == 0x11) { + return _rs; + } + // mflo + else if (_fun == 0x12) { + return _rs; + } + // mtlo + else if (_fun == 0x13) { + return _rs; + } + // mult + else if (_fun == 0x18) { + return _rs; + } + // multu + else if (_fun == 0x19) { + return _rs; + } + // div + else if (_fun == 0x1a) { + return _rs; + } + // divu + else if (_fun == 0x1b) { + return _rs; + } + // The rest includes transformed R-type arith imm instructions + // add + else if (_fun == 0x20) { + return (_rs + _rt); + } + // addu + else if (_fun == 0x21) { + return (_rs + _rt); + } + // sub + else if (_fun == 0x22) { + return (_rs - _rt); + } + // subu + else if (_fun == 0x23) { + return (_rs - _rt); + } + // and + else if (_fun == 0x24) { + return (_rs & _rt); + } + // or + else if (_fun == 0x25) { + return (_rs | _rt); + } + // xor + else if (_fun == 0x26) { + return (_rs ^ _rt); + } + // nor + else if (_fun == 0x27) { + return ~(_rs | _rt); + } + // slti + else if (_fun == 0x2a) { + return int32(_rs) < int32(_rt) ? 1 : 0; + } + // sltiu + else if (_fun == 0x2b) { + return _rs < _rt ? 1 : 0; + } else { + revert("invalid instruction"); + } + } else { + // SPECIAL2 + if (_opcode == 0x1C) { + // mul + if (_fun == 0x2) { + return uint32(int32(_rs) * int32(_rt)); + } + // clz, clo + else if (_fun == 0x20 || _fun == 0x21) { + if (_fun == 0x20) { + _rs = ~_rs; + } + uint32 i = 0; + while (_rs & 0x80000000 != 0) { + i++; + _rs <<= 1; + } + return i; + } + } + // lui + else if (_opcode == 0x0F) { + return _rt << 16; + } + // lb + else if (_opcode == 0x20) { + return signExtend((_mem >> (24 - (_rs & 3) * 8)) & 0xFF, 8); + } + // lh + else if (_opcode == 0x21) { + return signExtend((_mem >> (16 - (_rs & 2) * 8)) & 0xFFFF, 16); + } + // lwl + else if (_opcode == 0x22) { + uint32 val = _mem << ((_rs & 3) * 8); + uint32 mask = uint32(0xFFFFFFFF) << ((_rs & 3) * 8); + return (_rt & ~mask) | val; + } + // lw + else if (_opcode == 0x23) { + return _mem; + } + // lbu + else if (_opcode == 0x24) { + return (_mem >> (24 - (_rs & 3) * 8)) & 0xFF; + } + // lhu + else if (_opcode == 0x25) { + return (_mem >> (16 - (_rs & 2) * 8)) & 0xFFFF; + } + // lwr + else if (_opcode == 0x26) { + uint32 val = _mem >> (24 - (_rs & 3) * 8); + uint32 mask = uint32(0xFFFFFFFF) >> (24 - (_rs & 3) * 8); + return (_rt & ~mask) | val; + } + // sb + else if (_opcode == 0x28) { + uint32 val = (_rt & 0xFF) << (24 - (_rs & 3) * 8); + uint32 mask = 0xFFFFFFFF ^ uint32(0xFF << (24 - (_rs & 3) * 8)); + return (_mem & mask) | val; + } + // sh + else if (_opcode == 0x29) { + uint32 val = (_rt & 0xFFFF) << (16 - (_rs & 2) * 8); + uint32 mask = 0xFFFFFFFF ^ uint32(0xFFFF << (16 - (_rs & 2) * 8)); + return (_mem & mask) | val; + } + // swl + else if (_opcode == 0x2a) { + uint32 val = _rt >> ((_rs & 3) * 8); + uint32 mask = uint32(0xFFFFFFFF) >> ((_rs & 3) * 8); + return (_mem & ~mask) | val; + } + // sw + else if (_opcode == 0x2b) { + return _rt; + } + // swr + else if (_opcode == 0x2e) { + uint32 val = _rt << (24 - (_rs & 3) * 8); + uint32 mask = uint32(0xFFFFFFFF) << (24 - (_rs & 3) * 8); + return (_mem & ~mask) | val; + } + // ll + else if (_opcode == 0x30) { + return _mem; + } + // sc + else if (_opcode == 0x38) { + return _rt; + } else { + revert("invalid instruction"); + } + } + revert("invalid instruction"); + } + } + + /// @notice Extends the value leftwards with its most significant bit (sign extension). + function signExtend(uint32 _dat, uint32 _idx) internal pure returns (uint32 out_) { + unchecked { + bool isSigned = (_dat >> (_idx - 1)) != 0; + uint256 signed = ((1 << (32 - _idx)) - 1) << _idx; + uint256 mask = (1 << _idx) - 1; + return uint32(_dat & mask | (isSigned ? signed : 0)); + } + } + + /// @notice Handles a branch instruction, updating the MIPS state PC where needed. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _opcode The opcode of the branch instruction. + /// @param _insn The instruction to be executed. + /// @param _rtReg The register to be used for the branch. + /// @param _rs The register to be compared with the branch register. + function handleBranch( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _opcode, + uint32 _insn, + uint32 _rtReg, + uint32 _rs + ) + internal + pure + { + unchecked { + bool shouldBranch = false; + + if (_cpu.nextPC != _cpu.pc + 4) { + revert("branch in delay slot"); + } + + // beq/bne: Branch on equal / not equal + if (_opcode == 4 || _opcode == 5) { + uint32 rt = _registers[_rtReg]; + shouldBranch = (_rs == rt && _opcode == 4) || (_rs != rt && _opcode == 5); + } + // blez: Branches if instruction is less than or equal to zero + else if (_opcode == 6) { + shouldBranch = int32(_rs) <= 0; + } + // bgtz: Branches if instruction is greater than zero + else if (_opcode == 7) { + shouldBranch = int32(_rs) > 0; + } + // bltz/bgez: Branch on less than zero / greater than or equal to zero + else if (_opcode == 1) { + // regimm + uint32 rtv = ((_insn >> 16) & 0x1F); + if (rtv == 0) { + shouldBranch = int32(_rs) < 0; + } + if (rtv == 1) { + shouldBranch = int32(_rs) >= 0; + } + } + + // Update the state's previous PC + uint32 prevPC = _cpu.pc; + + // Execute the delay slot first + _cpu.pc = _cpu.nextPC; + + // If we should branch, update the PC to the branch target + // Otherwise, proceed to the next instruction + if (shouldBranch) { + _cpu.nextPC = prevPC + 4 + (signExtend(_insn & 0xFFFF, 16) << 2); + } else { + _cpu.nextPC = _cpu.nextPC + 4; + } + } + } + + /// @notice Handles HI and LO register instructions. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _fun The function code of the instruction. + /// @param _rs The value of the RS register. + /// @param _rt The value of the RT register. + /// @param _storeReg The register to store the result in. + function handleHiLo( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _fun, + uint32 _rs, + uint32 _rt, + uint32 _storeReg + ) + internal + pure + { + unchecked { + uint32 val = 0; + + // mfhi: Move the contents of the HI register into the destination + if (_fun == 0x10) { + val = _cpu.hi; + } + // mthi: Move the contents of the source into the HI register + else if (_fun == 0x11) { + _cpu.hi = _rs; + } + // mflo: Move the contents of the LO register into the destination + else if (_fun == 0x12) { + val = _cpu.lo; + } + // mtlo: Move the contents of the source into the LO register + else if (_fun == 0x13) { + _cpu.lo = _rs; + } + // mult: Multiplies `rs` by `rt` and stores the result in HI and LO registers + else if (_fun == 0x18) { + uint64 acc = uint64(int64(int32(_rs)) * int64(int32(_rt))); + _cpu.hi = uint32(acc >> 32); + _cpu.lo = uint32(acc); + } + // multu: Unsigned multiplies `rs` by `rt` and stores the result in HI and LO registers + else if (_fun == 0x19) { + uint64 acc = uint64(uint64(_rs) * uint64(_rt)); + _cpu.hi = uint32(acc >> 32); + _cpu.lo = uint32(acc); + } + // div: Divides `rs` by `rt`. + // Stores the quotient in LO + // And the remainder in HI + else if (_fun == 0x1a) { + if (int32(_rt) == 0) { + revert("MIPS: division by zero"); + } + _cpu.hi = uint32(int32(_rs) % int32(_rt)); + _cpu.lo = uint32(int32(_rs) / int32(_rt)); + } + // divu: Unsigned divides `rs` by `rt`. + // Stores the quotient in LO + // And the remainder in HI + else if (_fun == 0x1b) { + if (_rt == 0) { + revert("MIPS: division by zero"); + } + _cpu.hi = _rs % _rt; + _cpu.lo = _rs / _rt; + } + + // Store the result in the destination register, if applicable + if (_storeReg != 0) { + _registers[_storeReg] = val; + } + + // Update the PC + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _cpu.nextPC + 4; + } + } + + /// @notice Handles a jump instruction, updating the MIPS state PC where needed. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _linkReg The register to store the link to the instruction after the delay slot instruction. + /// @param _dest The destination to jump to. + function handleJump( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _linkReg, + uint32 _dest + ) + internal + pure + { + unchecked { + if (_cpu.nextPC != _cpu.pc + 4) { + revert("jump in delay slot"); + } + + // Update the next PC to the jump destination. + uint32 prevPC = _cpu.pc; + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _dest; + + // Update the link-register to the instruction after the delay slot instruction. + if (_linkReg != 0) { + _registers[_linkReg] = prevPC + 8; + } + } + } + + /// @notice Handles a storing a value into a register. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _storeReg The register to store the value into. + /// @param _val The value to store. + /// @param _conditional Whether or not the store is conditional. + function handleRd( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _storeReg, + uint32 _val, + bool _conditional + ) + internal + pure + { + unchecked { + // The destination register must be valid. + require(_storeReg < 32, "valid register"); + + // Never write to reg 0, and it can be conditional (movz, movn). + if (_storeReg != 0 && _conditional) { + _registers[_storeReg] = _val; + } + + // Update the PC. + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _cpu.nextPC + 4; + } + } +} diff --git a/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol b/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol new file mode 100644 index 000000000..12106ed23 --- /dev/null +++ b/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +library MIPSMemory { + /// @notice Reads a 32-bit value from memory. + /// @param _memRoot The current memory root + /// @param _addr The address to read from. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @return out_ The hashed MIPS state. + function readMem(bytes32 _memRoot, uint32 _addr, uint256 _proofOffset) internal pure returns (uint32 out_) { + unchecked { + validateMemoryProofAvailability(_proofOffset); + assembly { + // Validate the address alignement. + if and(_addr, 3) { revert(0, 0) } + + // Load the leaf value. + let leaf := calldataload(_proofOffset) + _proofOffset := add(_proofOffset, 32) + + // Convenience function to hash two nodes together in scratch space. + function hashPair(a, b) -> h { + mstore(0, a) + mstore(32, b) + h := keccak256(0, 64) + } + + // Start with the leaf node. + // Work back up by combining with siblings, to reconstruct the root. + let path := shr(5, _addr) + let node := leaf + for { let i := 0 } lt(i, 27) { i := add(i, 1) } { + let sibling := calldataload(_proofOffset) + _proofOffset := add(_proofOffset, 32) + switch and(shr(i, path), 1) + case 0 { node := hashPair(node, sibling) } + case 1 { node := hashPair(sibling, node) } + } + + // Verify the root matches. + if iszero(eq(node, _memRoot)) { + mstore(0, 0x0badf00d) + revert(0, 32) + } + + // Bits to shift = (32 - 4 - (addr % 32)) * 8 + let shamt := shl(3, sub(sub(32, 4), and(_addr, 31))) + out_ := and(shr(shamt, leaf), 0xFFffFFff) + } + } + } + + /// @notice Writes a 32-bit value to memory. + /// This function first overwrites the part of the leaf. + /// Then it recomputes the memory merkle root. + /// @param _addr The address to write to. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @param _val The value to write. + /// @return newMemRoot_ The new memory root after modification + function writeMem(uint32 _addr, uint256 _proofOffset, uint32 _val) internal pure returns (bytes32 newMemRoot_) { + unchecked { + validateMemoryProofAvailability(_proofOffset); + assembly { + // Validate the address alignement. + if and(_addr, 3) { revert(0, 0) } + + // Load the leaf value. + let leaf := calldataload(_proofOffset) + let shamt := shl(3, sub(sub(32, 4), and(_addr, 31))) + + // Mask out 4 bytes, and OR in the value + leaf := or(and(leaf, not(shl(shamt, 0xFFffFFff))), shl(shamt, _val)) + _proofOffset := add(_proofOffset, 32) + + // Convenience function to hash two nodes together in scratch space. + function hashPair(a, b) -> h { + mstore(0, a) + mstore(32, b) + h := keccak256(0, 64) + } + + // Start with the leaf node. + // Work back up by combining with siblings, to reconstruct the root. + let path := shr(5, _addr) + let node := leaf + for { let i := 0 } lt(i, 27) { i := add(i, 1) } { + let sibling := calldataload(_proofOffset) + _proofOffset := add(_proofOffset, 32) + switch and(shr(i, path), 1) + case 0 { node := hashPair(node, sibling) } + case 1 { node := hashPair(sibling, node) } + } + + newMemRoot_ := node + } + return newMemRoot_; + } + } + + /// @notice Computes the offset of a memory proof in the calldata. + /// @param _proofDataOffset The offset of the set of all memory proof data within calldata (proof.offset) + /// Equal to the offset of the first memory proof (at _proofIndex 0). + /// @param _proofIndex The index of the proof in the calldata. + /// @return offset_ The offset of the memory proof at the given _proofIndex in the calldata. + function memoryProofOffset(uint256 _proofDataOffset, uint8 _proofIndex) internal pure returns (uint256 offset_) { + unchecked { + // A proof of 32 bit memory, with 32-byte leaf values, is (32-5)=27 bytes32 entries. + // And the leaf value itself needs to be encoded as well: (27 + 1) = 28 bytes32 entries. + offset_ = _proofDataOffset + (uint256(_proofIndex) * (28 * 32)); + return offset_; + } + } + + /// @notice Validates that enough calldata is available to hold a full memory proof at the given offset + /// @param _proofStartOffset The index of the first byte of the target memory proof in calldata + function validateMemoryProofAvailability(uint256 _proofStartOffset) internal pure { + unchecked { + uint256 s = 0; + assembly { + s := calldatasize() + } + // A memory proof consists of 28 bytes32 values - verify we have enough calldata + require(s >= (_proofStartOffset + 28 * 32), "check that there is enough calldata"); + } + } +} diff --git a/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSState.sol b/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSState.sol new file mode 100644 index 000000000..d4431765c --- /dev/null +++ b/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSState.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +library MIPSState { + struct CpuScalars { + uint32 pc; + uint32 nextPC; + uint32 lo; + uint32 hi; + } +} diff --git a/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol b/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol new file mode 100644 index 000000000..f12f9b789 --- /dev/null +++ b/packages/tokamak/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; +import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; +import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; + +library MIPSSyscalls { + struct SysReadParams { + /// @param _a0 The file descriptor. + uint32 a0; + /// @param _a1 The memory location where data should be read to. + uint32 a1; + /// @param _a2 The number of bytes to read from the file + uint32 a2; + /// @param _preimageKey The key of the preimage to read. + bytes32 preimageKey; + /// @param _preimageOffset The offset of the preimage to read. + uint32 preimageOffset; + /// @param _localContext The local context for the preimage key. + bytes32 localContext; + /// @param _oracle The address of the preimage oracle. + IPreimageOracle oracle; + /// @param _proofOffset The offset of the memory proof in calldata. + uint256 proofOffset; + /// @param _memRoot The current memory root. + bytes32 memRoot; + } + + uint32 internal constant SYS_MMAP = 4090; + uint32 internal constant SYS_BRK = 4045; + uint32 internal constant SYS_CLONE = 4120; + uint32 internal constant SYS_EXIT_GROUP = 4246; + uint32 internal constant SYS_READ = 4003; + uint32 internal constant SYS_WRITE = 4004; + uint32 internal constant SYS_FCNTL = 4055; + uint32 internal constant SYS_EXIT = 4001; + uint32 internal constant SYS_SCHED_YIELD = 4162; + uint32 internal constant SYS_GETTID = 4222; + uint32 internal constant SYS_FUTEX = 4238; + uint32 internal constant SYS_OPEN = 4005; + uint32 internal constant SYS_NANOSLEEP = 4166; + // unused syscalls + uint32 internal constant SYS_CLOCK_GETTIME = 4263; + uint32 internal constant SYS_GET_AFFINITY = 4240; + uint32 internal constant SYS_GETAFFINITY = 4240; + uint32 internal constant SYS_MADVISE = 4218; + uint32 internal constant SYS_RTSIGPROCMASK = 4195; + uint32 internal constant SYS_SIGALTSTACK = 4206; + uint32 internal constant SYS_RTSIGACTION = 4194; + uint32 internal constant SYS_PRLIMIT64 = 4338; + uint32 internal constant SYS_CLOSE = 4006; + uint32 internal constant SYS_PREAD64 = 4200; + uint32 internal constant SYS_FSTAT64 = 4215; + uint32 internal constant SYS_OPENAT = 4288; + uint32 internal constant SYS_READLINK = 4085; + uint32 internal constant SYS_READLINKAT = 4298; + uint32 internal constant SYS_IOCTL = 4054; + uint32 internal constant SYS_EPOLLCREATE1 = 4326; + uint32 internal constant SYS_PIPE2 = 4328; + uint32 internal constant SYS_EPOLLCTL = 4249; + uint32 internal constant SYS_EPOLLPWAIT = 4313; + uint32 internal constant SYS_GETRANDOM = 4353; + uint32 internal constant SYS_UNAME = 4122; + uint32 internal constant SYS_STAT64 = 4213; + uint32 internal constant SYS_GETUID = 4024; + uint32 internal constant SYS_GETGID = 4047; + uint32 internal constant SYS_LLSEEK = 4140; + uint32 internal constant SYS_MINCORE = 4217; + uint32 internal constant SYS_TGKILL = 4266; + // profiling-related syscalls - ignored + uint32 internal constant SYS_SETITIMER = 4104; + uint32 internal constant SYS_TIMERCREATE = 4257; + uint32 internal constant SYS_TIMERSETTIME = 4258; + uint32 internal constant SYS_TIMERDELETE = 4261; + uint32 internal constant SYS_CLOCKGETTIME = 4263; + uint32 internal constant SYS_MUNMAP = 4091; + + uint32 internal constant FD_STDIN = 0; + uint32 internal constant FD_STDOUT = 1; + uint32 internal constant FD_STDERR = 2; + uint32 internal constant FD_HINT_READ = 3; + uint32 internal constant FD_HINT_WRITE = 4; + uint32 internal constant FD_PREIMAGE_READ = 5; + uint32 internal constant FD_PREIMAGE_WRITE = 6; + + uint32 internal constant SYS_ERROR_SIGNAL = 0xFF_FF_FF_FF; + uint32 internal constant EBADF = 0x9; + uint32 internal constant EINVAL = 0x16; + uint32 internal constant EAGAIN = 0xb; + uint32 internal constant ETIMEDOUT = 0x91; + + uint32 internal constant FUTEX_WAIT_PRIVATE = 128; + uint32 internal constant FUTEX_WAKE_PRIVATE = 129; + uint32 internal constant FUTEX_TIMEOUT_STEPS = 10000; + uint64 internal constant FUTEX_NO_TIMEOUT = type(uint64).max; + uint32 internal constant FUTEX_EMPTY_ADDR = 0xFF_FF_FF_FF; + + uint32 internal constant SCHED_QUANTUM = 100_000; + /// @notice Start of the data segment. + uint32 internal constant PROGRAM_BREAK = 0x40000000; + uint32 internal constant HEAP_END = 0x60000000; + + // SYS_CLONE flags + uint32 internal constant CLONE_VM = 0x100; + uint32 internal constant CLONE_FS = 0x200; + uint32 internal constant CLONE_FILES = 0x400; + uint32 internal constant CLONE_SIGHAND = 0x800; + uint32 internal constant CLONE_PTRACE = 0x2000; + uint32 internal constant CLONE_VFORK = 0x4000; + uint32 internal constant CLONE_PARENT = 0x8000; + uint32 internal constant CLONE_THREAD = 0x10000; + uint32 internal constant CLONE_NEWNS = 0x20000; + uint32 internal constant CLONE_SYSVSEM = 0x40000; + uint32 internal constant CLONE_SETTLS = 0x80000; + uint32 internal constant CLONE_PARENTSETTID = 0x100000; + uint32 internal constant CLONE_CHILDCLEARTID = 0x200000; + uint32 internal constant CLONE_UNTRACED = 0x800000; + uint32 internal constant CLONE_CHILDSETTID = 0x1000000; + uint32 internal constant CLONE_STOPPED = 0x2000000; + uint32 internal constant CLONE_NEWUTS = 0x4000000; + uint32 internal constant CLONE_NEWIPC = 0x8000000; + uint32 internal constant VALID_SYS_CLONE_FLAGS = + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD; + + /// @notice Extract syscall num and arguments from registers. + /// @param _registers The cpu registers. + /// @return sysCallNum_ The syscall number. + /// @return a0_ The first argument available to the syscall operation. + /// @return a1_ The second argument available to the syscall operation. + /// @return a2_ The third argument available to the syscall operation. + /// @return a3_ The fourth argument available to the syscall operation. + function getSyscallArgs(uint32[32] memory _registers) + internal + pure + returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_, uint32 a3_) + { + unchecked { + sysCallNum_ = _registers[2]; + + a0_ = _registers[4]; + a1_ = _registers[5]; + a2_ = _registers[6]; + a3_ = _registers[7]; + + return (sysCallNum_, a0_, a1_, a2_, a3_); + } + } + + /// @notice Like a Linux mmap syscall. Allocates a page from the heap. + /// @param _a0 The address for the new mapping + /// @param _a1 The size of the new mapping + /// @param _heap The current value of the heap pointer + /// @return v0_ The address of the new mapping + /// @return v1_ Unused error code (0) + /// @return newHeap_ The new value for the heap, may be unchanged + function handleSysMmap( + uint32 _a0, + uint32 _a1, + uint32 _heap + ) + internal + pure + returns (uint32 v0_, uint32 v1_, uint32 newHeap_) + { + unchecked { + v1_ = uint32(0); + newHeap_ = _heap; + + uint32 sz = _a1; + if (sz & 4095 != 0) { + // adjust size to align with page size + sz += 4096 - (sz & 4095); + } + if (_a0 == 0) { + v0_ = _heap; + newHeap_ += sz; + // Fail if new heap exceeds memory limit, newHeap overflows to low memory, or sz overflows + if (newHeap_ > HEAP_END || newHeap_ < _heap || sz < _a1) { + v0_ = SYS_ERROR_SIGNAL; + v1_ = EINVAL; + return (v0_, v1_, _heap); + } + } else { + v0_ = _a0; + } + + return (v0_, v1_, newHeap_); + } + } + + /// @notice Like a Linux read syscall. Splits unaligned reads into aligned reads. + /// Args are provided as a struct to reduce stack pressure. + /// @return v0_ The number of bytes read, -1 on error. + /// @return v1_ The error code, 0 if there is no error. + /// @return newPreimageOffset_ The new value for the preimage offset. + /// @return newMemRoot_ The new memory root. + function handleSysRead(SysReadParams memory _args) + internal + view + returns (uint32 v0_, uint32 v1_, uint32 newPreimageOffset_, bytes32 newMemRoot_) + { + unchecked { + v0_ = uint32(0); + v1_ = uint32(0); + newMemRoot_ = _args.memRoot; + newPreimageOffset_ = _args.preimageOffset; + + // args: _a0 = fd, _a1 = addr, _a2 = count + // returns: v0_ = read, v1_ = err code + if (_args.a0 == FD_STDIN) { + // Leave v0_ and v1_ zero: read nothing, no error + } + // pre-image oracle read + else if (_args.a0 == FD_PREIMAGE_READ) { + // verify proof is correct, and get the existing memory. + // mask the addr to align it to 4 bytes + uint32 mem = MIPSMemory.readMem(_args.memRoot, _args.a1 & 0xFFffFFfc, _args.proofOffset); + // If the preimage key is a local key, localize it in the context of the caller. + if (uint8(_args.preimageKey[0]) == 1) { + _args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext); + } + (bytes32 dat, uint256 datLen) = _args.oracle.readPreimage(_args.preimageKey, _args.preimageOffset); + + // Transform data for writing to memory + // We use assembly for more precise ops, and no var count limit + uint32 a1 = _args.a1; + uint32 a2 = _args.a2; + assembly { + let alignment := and(a1, 3) // the read might not start at an aligned address + let space := sub(4, alignment) // remaining space in memory word + if lt(space, datLen) { datLen := space } // if less space than data, shorten data + if lt(a2, datLen) { datLen := a2 } // if requested to read less, read less + dat := shr(sub(256, mul(datLen, 8)), dat) // right-align data + dat := shl(mul(sub(sub(4, datLen), alignment), 8), dat) // position data to insert into memory + // word + let mask := sub(shl(mul(sub(4, alignment), 8), 1), 1) // mask all bytes after start + let suffixMask := sub(shl(mul(sub(sub(4, alignment), datLen), 8), 1), 1) // mask of all bytes + // starting from end, maybe none + mask := and(mask, not(suffixMask)) // reduce mask to just cover the data we insert + mem := or(and(mem, not(mask)), dat) // clear masked part of original memory, and insert data + } + + // Write memory back + newMemRoot_ = MIPSMemory.writeMem(_args.a1 & 0xFFffFFfc, _args.proofOffset, mem); + newPreimageOffset_ += uint32(datLen); + v0_ = uint32(datLen); + } + // hint response + else if (_args.a0 == FD_HINT_READ) { + // Don't read into memory, just say we read it all + // The result is ignored anyway + v0_ = _args.a2; + } else { + v0_ = 0xFFffFFff; + v1_ = EBADF; + } + + return (v0_, v1_, newPreimageOffset_, newMemRoot_); + } + } + + /// @notice Like a Linux write syscall. Splits unaligned writes into aligned writes. + /// @param _a0 The file descriptor. + /// @param _a1 The memory address to read from. + /// @param _a2 The number of bytes to read. + /// @param _preimageKey The current preimaageKey. + /// @param _preimageOffset The current preimageOffset. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @param _memRoot The current memory root. + /// @return v0_ The number of bytes written, or -1 on error. + /// @return v1_ The error code, or 0 if empty. + /// @return newPreimageKey_ The new preimageKey. + /// @return newPreimageOffset_ The new preimageOffset. + function handleSysWrite( + uint32 _a0, + uint32 _a1, + uint32 _a2, + bytes32 _preimageKey, + uint32 _preimageOffset, + uint256 _proofOffset, + bytes32 _memRoot + ) + internal + pure + returns (uint32 v0_, uint32 v1_, bytes32 newPreimageKey_, uint32 newPreimageOffset_) + { + unchecked { + // args: _a0 = fd, _a1 = addr, _a2 = count + // returns: v0_ = written, v1_ = err code + v0_ = uint32(0); + v1_ = uint32(0); + newPreimageKey_ = _preimageKey; + newPreimageOffset_ = _preimageOffset; + + if (_a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_HINT_WRITE) { + v0_ = _a2; // tell program we have written everything + } + // pre-image oracle + else if (_a0 == FD_PREIMAGE_WRITE) { + // mask the addr to align it to 4 bytes + uint32 mem = MIPSMemory.readMem(_memRoot, _a1 & 0xFFffFFfc, _proofOffset); + bytes32 key = _preimageKey; + + // Construct pre-image key from memory + // We use assembly for more precise ops, and no var count limit + assembly { + let alignment := and(_a1, 3) // the read might not start at an aligned address + let space := sub(4, alignment) // remaining space in memory word + if lt(space, _a2) { _a2 := space } // if less space than data, shorten data + key := shl(mul(_a2, 8), key) // shift key, make space for new info + let mask := sub(shl(mul(_a2, 8), 1), 1) // mask for extracting value from memory + mem := and(shr(mul(sub(space, _a2), 8), mem), mask) // align value to right, mask it + key := or(key, mem) // insert into key + } + + // Write pre-image key to oracle + newPreimageKey_ = key; + newPreimageOffset_ = 0; // reset offset, to read new pre-image data from the start + v0_ = _a2; + } else { + v0_ = 0xFFffFFff; + v1_ = EBADF; + } + + return (v0_, v1_, newPreimageKey_, newPreimageOffset_); + } + } + + /// @notice Like Linux fcntl (file control) syscall, but only supports minimal file-descriptor control commands, to + /// retrieve the file-descriptor R/W flags. + /// @param _a0 The file descriptor. + /// @param _a1 The control command. + /// @param v0_ The file status flag (only supported command is F_GETFL), or -1 on error. + /// @param v1_ An error number, or 0 if there is no error. + function handleSysFcntl(uint32 _a0, uint32 _a1) internal pure returns (uint32 v0_, uint32 v1_) { + unchecked { + v0_ = uint32(0); + v1_ = uint32(0); + + // args: _a0 = fd, _a1 = cmd + if (_a1 == 3) { + // F_GETFL: get file descriptor flags + if (_a0 == FD_STDIN || _a0 == FD_PREIMAGE_READ || _a0 == FD_HINT_READ) { + v0_ = 0; // O_RDONLY + } else if (_a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_PREIMAGE_WRITE || _a0 == FD_HINT_WRITE) { + v0_ = 1; // O_WRONLY + } else { + v0_ = 0xFFffFFff; + v1_ = EBADF; + } + } else { + v0_ = 0xFFffFFff; + v1_ = EINVAL; // cmd not recognized by this kernel + } + + return (v0_, v1_); + } + } + + function handleSyscallUpdates( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _v0, + uint32 _v1 + ) + internal + pure + { + unchecked { + // Write the results back to the state registers + _registers[2] = _v0; + _registers[7] = _v1; + + // Update the PC and nextPC + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _cpu.nextPC + 4; + } + } +} From a0f46533b377a8f35f8760889b098648cf6c07ff Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 09:59:14 +0900 Subject: [PATCH 03/19] Update CommonError.sol & Error.sol library --- .../contracts-bedrock/src/dispute/lib/Errors.sol | 13 +++++++++++++ .../src/libraries/errors/CommonErrors.sol | 14 ++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 packages/tokamak/contracts-bedrock/src/libraries/errors/CommonErrors.sol diff --git a/packages/tokamak/contracts-bedrock/src/dispute/lib/Errors.sol b/packages/tokamak/contracts-bedrock/src/dispute/lib/Errors.sol index 9a3237b32..6cd1d1d07 100644 --- a/packages/tokamak/contracts-bedrock/src/dispute/lib/Errors.sol +++ b/packages/tokamak/contracts-bedrock/src/dispute/lib/Errors.sol @@ -94,6 +94,9 @@ error InvalidSplitDepth(); /// @notice Thrown on deployment if the max clock duration is less than or equal to the clock extension. error InvalidClockExtension(); +/// @notice Thrown on deployment if the PreimageOracle challenge period is too high. +error InvalidChallengePeriod(); + /// @notice Thrown on deployment if the max depth is greater than `LibPosition.` error MaxDepthTooLarge(); @@ -123,3 +126,13 @@ error L2BlockNumberChallenged(); /// @notice Thrown when an unauthorized address attempts to interact with the game. error BadAuth(); + +//////////////////////////////////////////////////////////////// +// `AnchorStateRegistry` Errors // +//////////////////////////////////////////////////////////////// + +/// @notice Thrown when attempting to set an anchor state using an unregistered game. +error UnregisteredGame(); + +/// @notice Thrown when attempting to set an anchor state using an invalid game result. +error InvalidGameStatus(); diff --git a/packages/tokamak/contracts-bedrock/src/libraries/errors/CommonErrors.sol b/packages/tokamak/contracts-bedrock/src/libraries/errors/CommonErrors.sol new file mode 100644 index 000000000..eee6cc699 --- /dev/null +++ b/packages/tokamak/contracts-bedrock/src/libraries/errors/CommonErrors.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice Error for an unauthorized CALLER. +error Unauthorized(); + +/// @notice Error for when a method is called that only works when using a custom gas token. +error OnlyCustomGasToken(); + +/// @notice Error for when a method is called that only works when NOT using a custom gas token. +error NotCustomGasToken(); + +/// @notice Error for when a transfer via call fails. +error TransferFailed(); From d3b07eb2ceff69cca981931c0f89fa5f03db49b7 Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 10:11:54 +0900 Subject: [PATCH 04/19] Fix unexpected character error --- .../tokamak/contracts-bedrock/src/dispute/weth/DelayedWETH.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tokamak/contracts-bedrock/src/dispute/weth/DelayedWETH.sol b/packages/tokamak/contracts-bedrock/src/dispute/weth/DelayedWETH.sol index a3e684be4..8d8abdd45 100644 --- a/packages/tokamak/contracts-bedrock/src/dispute/weth/DelayedWETH.sol +++ b/packages/tokamak/contracts-bedrock/src/dispute/weth/DelayedWETH.sol @@ -96,4 +96,3 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, IDelayedWETH, ISemver { emit Approval(_guy, msg.sender, _wad); } } -ã…Š From f11f188c1a877be0eeed68a68822d5365233e605 Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 10:37:37 +0900 Subject: [PATCH 05/19] Update AlreadyInitialized on CannonErrors.sol --- .../src/cannon/interfaces/IPreimageOracle.sol | 13 ++++++++++++- .../src/cannon/libraries/CannonErrors.sol | 9 +++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/tokamak/contracts-bedrock/src/cannon/interfaces/IPreimageOracle.sol b/packages/tokamak/contracts-bedrock/src/cannon/interfaces/IPreimageOracle.sol index 7c4a57b25..9f923fcfe 100644 --- a/packages/tokamak/contracts-bedrock/src/cannon/interfaces/IPreimageOracle.sol +++ b/packages/tokamak/contracts-bedrock/src/cannon/interfaces/IPreimageOracle.sol @@ -4,6 +4,10 @@ pragma solidity 0.8.15; /// @title IPreimageOracle /// @notice Interface for a preimage oracle. interface IPreimageOracle { + /// @notice Returns the length of the large preimage proposal challenge period. + /// @return challengePeriod_ The length of the challenge period in seconds. + function challengePeriod() external view returns (uint256 challengePeriod_); + /// @notice Reads a preimage from the oracle. /// @param _key The key of the preimage to read. /// @param _offset The offset of the preimage to read. @@ -75,6 +79,13 @@ interface IPreimageOracle { /// The preimage key is `6 ++ keccak256(precompile ++ input)[1:]`. /// @param _partOffset The offset of the precompile result being loaded. /// @param _precompile The precompile address + /// @param _requiredGas The gas required to fully execute an L1 precompile. /// @param _input The input to the precompile call. - function loadPrecompilePreimagePart(uint256 _partOffset, address _precompile, bytes calldata _input) external; + function loadPrecompilePreimagePart( + uint256 _partOffset, + address _precompile, + uint64 _requiredGas, + bytes calldata _input + ) + external; } diff --git a/packages/tokamak/contracts-bedrock/src/cannon/libraries/CannonErrors.sol b/packages/tokamak/contracts-bedrock/src/cannon/libraries/CannonErrors.sol index 621c0609a..e45cbaf9d 100644 --- a/packages/tokamak/contracts-bedrock/src/cannon/libraries/CannonErrors.sol +++ b/packages/tokamak/contracts-bedrock/src/cannon/libraries/CannonErrors.sol @@ -4,6 +4,9 @@ pragma solidity 0.8.15; /// @notice Thrown when a passed part offset is out of bounds. error PartOffsetOOB(); +/// @notice Thrown when insufficient gas is provided when loading precompile preimages. +error NotEnoughGas(); + /// @notice Thrown when a merkle proof fails to verify. error InvalidProof(); @@ -37,6 +40,9 @@ error BadProposal(); /// @notice Thrown when attempting to add leaves to a preimage proposal that has not been initialized. error NotInitialized(); +/// @notice Thrown when attempting to re-initialize an existing large preimage proposal. +error AlreadyInitialized(); + /// @notice Thrown when the caller of a function is not an EOA. error NotEOA(); @@ -45,3 +51,6 @@ error InsufficientBond(); /// @notice Thrown when a bond transfer fails. error BondTransferFailed(); + +/// @notice Thrown when the value of the exited boolean is not 0 or 1. +error InvalidExitedValue(); From 6baeb7db7ba6a6bd9eaceac0d634a711dc9c10cf Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 13:53:49 +0900 Subject: [PATCH 06/19] Update imports contracts on op-contracts_v1.6.0 --- .../scripts/libraries/Process.sol | 25 ++++++++++++++----- .../interfaces/IAnchorStateRegistry.sol | 5 ++++ .../src/dispute/weth/WETH98.sol | 2 +- .../src/libraries/Constants.sol | 4 --- .../src/libraries/rlp/RLPErrors.sol | 2 +- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/tokamak/contracts-bedrock/scripts/libraries/Process.sol b/packages/tokamak/contracts-bedrock/scripts/libraries/Process.sol index c95a95d76..d2cf5c3af 100644 --- a/packages/tokamak/contracts-bedrock/scripts/libraries/Process.sol +++ b/packages/tokamak/contracts-bedrock/scripts/libraries/Process.sol @@ -10,15 +10,28 @@ library Process { /// @notice Foundry cheatcode VM. Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - function run(string[] memory cmd) internal returns (bytes memory stdout_) { - Vm.FfiResult memory result = vm.tryFfi(cmd); + /// @notice Run a command in a subprocess. Fails if no output is returned. + /// @param _command Command to run. + function run(string[] memory _command) internal returns (bytes memory stdout_) { + stdout_ = run({ _command: _command, _allowEmpty: false }); + } + + /// @notice Run a command in a subprocess. + /// @param _command Command to run. + /// @param _allowEmpty Allow empty output. + function run(string[] memory _command, bool _allowEmpty) internal returns (bytes memory stdout_) { + Vm.FfiResult memory result = vm.tryFfi(_command); + string memory command; + for (uint256 i = 0; i < _command.length; i++) { + command = string.concat(command, _command[i], " "); + } if (result.exitCode != 0) { - string memory command; - for (uint256 i = 0; i < cmd.length; i++) { - command = string.concat(command, cmd[i], " "); - } revert FfiFailed(string.concat("Command: ", command, "\nError: ", string(result.stderr))); } + // If the output is empty, result.stdout is "[]". + if (!_allowEmpty && keccak256(result.stdout) == keccak256(bytes("[]"))) { + revert FfiFailed(string.concat("No output from Command: ", command)); + } stdout_ = result.stdout; } } diff --git a/packages/tokamak/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol b/packages/tokamak/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol index 2294a76ef..dd7724538 100644 --- a/packages/tokamak/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol +++ b/packages/tokamak/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; import "src/dispute/lib/Types.sol"; @@ -21,4 +22,8 @@ interface IAnchorStateRegistry { /// the FaultDisputeGame contract and stores it in the registry if the new anchor state is valid and the /// state is newer than the current anchor state. function tryUpdateAnchorState() external; + + /// @notice Sets the anchor state given the game. + /// @param _game The game to set the anchor state for. + function setAnchorState(IFaultDisputeGame _game) external; } diff --git a/packages/tokamak/contracts-bedrock/src/dispute/weth/WETH98.sol b/packages/tokamak/contracts-bedrock/src/dispute/weth/WETH98.sol index f95699a3b..2b054c704 100644 --- a/packages/tokamak/contracts-bedrock/src/dispute/weth/WETH98.sol +++ b/packages/tokamak/contracts-bedrock/src/dispute/weth/WETH98.sol @@ -50,7 +50,7 @@ contract WETH98 is IWETH { } /// @inheritdoc IWETH - function deposit() public payable { + function deposit() public payable virtual { balanceOf[msg.sender] += msg.value; emit Deposit(msg.sender, msg.value); } diff --git a/packages/tokamak/contracts-bedrock/src/libraries/Constants.sol b/packages/tokamak/contracts-bedrock/src/libraries/Constants.sol index 31d01e966..e86010421 100644 --- a/packages/tokamak/contracts-bedrock/src/libraries/Constants.sol +++ b/packages/tokamak/contracts-bedrock/src/libraries/Constants.sol @@ -50,8 +50,4 @@ library Constants { }); return config; } - - /// @notice The `reinitailizer` input for upgradable contracts. This value must be updated - /// each time that the contracts are deployed. - uint8 internal constant INITIALIZER = 3; } diff --git a/packages/tokamak/contracts-bedrock/src/libraries/rlp/RLPErrors.sol b/packages/tokamak/contracts-bedrock/src/libraries/rlp/RLPErrors.sol index f7a9cdb09..c2ce80a6c 100644 --- a/packages/tokamak/contracts-bedrock/src/libraries/rlp/RLPErrors.sol +++ b/packages/tokamak/contracts-bedrock/src/libraries/rlp/RLPErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0; /// @notice The length of an RLP item must be greater than zero to be decodable error EmptyItem(); From 30f88729d1b459b0ce58038ea90785f80d684220 Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 15:16:20 +0900 Subject: [PATCH 07/19] Fix script error on Deploy.s.sol FPACOPS.s.sol PreimageOracle.t.sol --- .../contracts-bedrock/scripts/Deploy.s.sol | 101 ++++++++++--- .../scripts/fpac/FPACOPS.s.sol | 5 +- .../test/cannon/PreimageOracle.t.sol | 134 ++++++++++++++++-- 3 files changed, 202 insertions(+), 38 deletions(-) diff --git a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol index 827f3ee21..65b48bb0a 100644 --- a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol @@ -155,6 +155,7 @@ contract Deploy is Deployer { L2OutputOracle: mustGetAddress("L2OutputOracleProxy"), DisputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"), DelayedWETH: mustGetAddress("DelayedWETHProxy"), + PermissionedDelayedWETH: mustGetAddress("PermissionedDelayedWETHProxy"), AnchorStateRegistry: mustGetAddress("AnchorStateRegistryProxy"), OptimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"), OptimismPortal: mustGetAddress("OptimismPortalProxy"), @@ -174,6 +175,7 @@ contract Deploy is Deployer { L2OutputOracle: getAddress("L2OutputOracleProxy"), DisputeGameFactory: getAddress("DisputeGameFactoryProxy"), DelayedWETH: getAddress("DelayedWETHProxy"), + PermissionedDelayedWETH: getAddress("PermissionedDelayedWETHProxy"), AnchorStateRegistry: getAddress("AnchorStateRegistryProxy"), OptimismMintableERC20Factory: getAddress("OptimismMintableERC20FactoryProxy"), OptimismPortal: getAddress("OptimismPortalProxy"), @@ -215,7 +217,7 @@ contract Deploy is Deployer { /// @notice Make a call from the Safe contract to an arbitrary address with arbitrary data function _callViaSafe(Safe _safe, address _target, bytes memory _data) internal { - // This is the signature format used the caller is also the signer. + // This is the signature format used when the caller is also the signer. bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1)); _safe.execTransaction({ @@ -293,11 +295,11 @@ contract Deploy is Deployer { console.log("deployed Safe!"); setupSuperchain(); console.log("set up superchain!"); - if (cfg.usePlasma()) { + if (cfg.useAltDA()) { bytes32 typeHash = keccak256(bytes(cfg.daCommitmentType())); bytes32 keccakHash = keccak256(bytes("KeccakCommitment")); if (typeHash == keccakHash) { - setupOpPlasma(); + setupOpAltDA(); } } setupOpChain(); @@ -376,6 +378,7 @@ contract Deploy is Deployer { deployERC1967Proxy("DisputeGameFactoryProxy"); deployERC1967Proxy("L2OutputOracleProxy"); deployERC1967Proxy("DelayedWETHProxy"); + deployERC1967Proxy("PermissionedDelayedWETHProxy"); deployERC1967Proxy("AnchorStateRegistryProxy"); transferAddressManagerOwnership(); // to the ProxyAdmin @@ -391,7 +394,6 @@ contract Deploy is Deployer { deployL1ERC721Bridge(); deployOptimismPortal(); deployL2OutputOracle(); - // Fault proofs deployOptimismPortal2(); deployDisputeGameFactory(); @@ -423,12 +425,13 @@ contract Deploy is Deployer { initializeL2OutputOracle(); initializeDisputeGameFactory(); initializeDelayedWETH(); + initializePermissionedDelayedWETH(); initializeAnchorStateRegistry(); } - /// @notice Add Plasma setup to the OP chain - function setupOpPlasma() public { - console.log("Deploying OP Plasma"); + /// @notice Add AltDA setup to the OP chain + function setupOpAltDA() public { + console.log("Deploying OP AltDA"); deployDataAvailabilityChallengeProxy(); deployDataAvailabilityChallenge(); initializeDataAvailabilityChallenge(); @@ -464,8 +467,8 @@ contract Deploy is Deployer { console.log("Deploying safe: %s with salt %s", _name, vm.toString(salt)); (SafeProxyFactory safeProxyFactory, Safe safeSingleton) = _getSafeFactory(); - address[] memory expandedOwners = new address[](_owners.length + 1); if (_keepDeployer) { + address[] memory expandedOwners = new address[](_owners.length + 1); // By always adding msg.sender first we know that the previousOwner will be SENTINEL_OWNERS, which makes it // easier to call removeOwner later. expandedOwners[0] = msg.sender; @@ -651,10 +654,9 @@ contract Deploy is Deployer { function deployOptimismPortal() public broadcast returns (address addr_) { console.log("Deploying OptimismPortal implementation"); if (cfg.useInterop()) { - addr_ = address(new OptimismPortalInterop{ salt: _implSalt() }()); - } else { - addr_ = address(new OptimismPortal{ salt: _implSalt() }()); + console.log("Attempting to deploy OptimismPortal with interop, this config is a noop"); } + addr_ = address(new OptimismPortal{ salt: _implSalt() }()); save("OptimismPortal", addr_); console.log("OptimismPortal deployed at %s", addr_); @@ -675,22 +677,31 @@ contract Deploy is Deployer { uint32(cfg.respectedGameType()) == cfg.respectedGameType(), "Deploy: respectedGameType must fit into uint32" ); - OptimismPortal2 portal = new OptimismPortal2{ salt: _implSalt() }({ - _proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(), - _disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds() - }); + if (cfg.useInterop()) { + addr_ = address( + new OptimismPortalInterop{ salt: _implSalt() }({ + _proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(), + _disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds() + }) + ); + } else { + addr_ = address( + new OptimismPortal2{ salt: _implSalt() }({ + _proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(), + _disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds() + }) + ); + } - save("OptimismPortal2", address(portal)); - console.log("OptimismPortal2 deployed at %s", address(portal)); + save("OptimismPortal2", addr_); + console.log("OptimismPortal2 deployed at %s", addr_); // Override the `OptimismPortal2` contract to the deployed implementation. This is necessary // to check the `OptimismPortal2` implementation alongside dependent contracts, which // are always proxies. Types.ContractSet memory contracts = _proxiesUnstrict(); - contracts.OptimismPortal2 = address(portal); + contracts.OptimismPortal2 = addr_; ChainAssertions.checkOptimismPortal2({ _contracts: contracts, _cfg: cfg, _isProxy: false }); - - addr_ = address(portal); } /// @notice Deploy the L2OutputOracle @@ -1035,10 +1046,34 @@ contract Deploy is Deployer { }); } + function initializePermissionedDelayedWETH() public broadcast { + console.log("Upgrading and initializing permissioned DelayedWETH proxy"); + address delayedWETHProxy = mustGetAddress("PermissionedDelayedWETHProxy"); + address delayedWETH = mustGetAddress("DelayedWETH"); + address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); + + _upgradeAndCallViaSafe({ + _proxy: payable(delayedWETHProxy), + _implementation: delayedWETH, + _innerCallData: abi.encodeCall(DelayedWETH.initialize, (msg.sender, SuperchainConfig(superchainConfigProxy))) + }); + + string memory version = DelayedWETH(payable(delayedWETHProxy)).version(); + console.log("DelayedWETH version: %s", version); + + ChainAssertions.checkPermissionedDelayedWETH({ + _contracts: _proxiesUnstrict(), + _cfg: cfg, + _isProxy: true, + _expectedOwner: msg.sender + }); + } + function initializeAnchorStateRegistry() public broadcast { console.log("Upgrading and initializing AnchorStateRegistry proxy"); address anchorStateRegistryProxy = mustGetAddress("AnchorStateRegistryProxy"); address anchorStateRegistry = mustGetAddress("AnchorStateRegistry"); + SuperchainConfig superchainConfig = SuperchainConfig(mustGetAddress("SuperchainConfigProxy")); AnchorStateRegistry.StartingAnchorRoot[] memory roots = new AnchorStateRegistry.StartingAnchorRoot[](5); roots[0] = AnchorStateRegistry.StartingAnchorRoot({ @@ -1080,7 +1115,7 @@ contract Deploy is Deployer { _upgradeAndCallViaSafe({ _proxy: payable(anchorStateRegistryProxy), _implementation: anchorStateRegistry, - _innerCallData: abi.encodeCall(AnchorStateRegistry.initialize, (roots)) + _innerCallData: abi.encodeCall(AnchorStateRegistry.initialize, (roots, superchainConfig)) }); string memory version = AnchorStateRegistry(payable(anchorStateRegistryProxy)).version(); @@ -1433,6 +1468,25 @@ contract Deploy is Deployer { ChainAssertions.checkDelayedWETH({ _contracts: _proxies(), _cfg: cfg, _isProxy: true, _expectedOwner: safe }); } + /// @notice Transfer ownership of the permissioned DelayedWETH contract to the final system owner + function transferPermissionedDelayedWETHOwnership() public broadcast { + console.log("Transferring permissioned DelayedWETH ownership to Safe"); + DelayedWETH weth = DelayedWETH(mustGetAddress("PermissionedDelayedWETHProxy")); + address owner = weth.owner(); + + address safe = mustGetAddress("SystemOwnerSafe"); + if (owner != safe) { + weth.transferOwnership(safe); + console.log("DelayedWETH ownership transferred to Safe at: %s", safe); + } + ChainAssertions.checkPermissionedDelayedWETH({ + _contracts: _proxies(), + _cfg: cfg, + _isProxy: true, + _expectedOwner: safe + }); + } + /// @notice Loads the mips absolute prestate from the prestate-proof for devnets otherwise /// from the config. function loadMipsAbsolutePrestate() internal returns (Claim mipsAbsolutePrestate_) { @@ -1486,7 +1540,7 @@ contract Deploy is Deployer { function setPermissionedCannonFaultGameImplementation(bool _allowUpgrade) public broadcast { console.log("Setting Cannon PermissionedDisputeGame implementation"); DisputeGameFactory factory = DisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); - DelayedWETH weth = DelayedWETH(mustGetAddress("DelayedWETHProxy")); + DelayedWETH weth = DelayedWETH(mustGetAddress("PermissionedDelayedWETHProxy")); // Set the Cannon FaultDisputeGame implementation in the factory. _setFaultGameImplementation({ @@ -1534,6 +1588,7 @@ contract Deploy is Deployer { DelayedWETH weth = DelayedWETH(mustGetAddress("DelayedWETHProxy")); Claim outputAbsolutePrestate = Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())); + PreimageOracle fastOracle = new PreimageOracle(cfg.preimageOracleMinProposalSize(), 0); _setFaultGameImplementation({ _factory: factory, _allowUpgrade: _allowUpgrade, @@ -1542,7 +1597,7 @@ contract Deploy is Deployer { weth: weth, gameType: GameTypes.FAST, absolutePrestate: outputAbsolutePrestate, - faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, PreimageOracle(mustGetAddress("PreimageOracle")))), + faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, fastOracle)), // The max depth for the alphabet trace is always 3. Add 1 because split depth is fully inclusive. maxGameDepth: cfg.faultGameSplitDepth() + 3 + 1, maxClockDuration: Duration.wrap(0) // Resolvable immediately diff --git a/packages/tokamak/contracts-bedrock/scripts/fpac/FPACOPS.s.sol b/packages/tokamak/contracts-bedrock/scripts/fpac/FPACOPS.s.sol index 434b5274d..ff10709f3 100644 --- a/packages/tokamak/contracts-bedrock/scripts/fpac/FPACOPS.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/fpac/FPACOPS.s.sol @@ -92,6 +92,8 @@ contract FPACOPS is Deploy, StdAssertions { function initializeAnchorStateRegistryProxy() internal broadcast { console.log("Initializing AnchorStateRegistryProxy with AnchorStateRegistry."); + address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); + SuperchainConfig superchainConfig = SuperchainConfig(superchainConfigProxy); AnchorStateRegistry.StartingAnchorRoot[] memory roots = new AnchorStateRegistry.StartingAnchorRoot[](2); roots[0] = AnchorStateRegistry.StartingAnchorRoot({ @@ -111,7 +113,8 @@ contract FPACOPS is Deploy, StdAssertions { address asrProxy = mustGetAddress("AnchorStateRegistryProxy"); Proxy(payable(asrProxy)).upgradeToAndCall( - mustGetAddress("AnchorStateRegistry"), abi.encodeCall(AnchorStateRegistry.initialize, (roots)) + mustGetAddress("AnchorStateRegistry"), + abi.encodeCall(AnchorStateRegistry.initialize, (roots, superchainConfig)) ); } diff --git a/packages/tokamak/contracts-bedrock/test/cannon/PreimageOracle.t.sol b/packages/tokamak/contracts-bedrock/test/cannon/PreimageOracle.t.sol index ab312cca5..902f22b41 100644 --- a/packages/tokamak/contracts-bedrock/test/cannon/PreimageOracle.t.sol +++ b/packages/tokamak/contracts-bedrock/test/cannon/PreimageOracle.t.sol @@ -20,6 +20,14 @@ contract PreimageOracle_Test is Test { vm.label(address(oracle), "PreimageOracle"); } + /// @notice Tests that the challenge period cannot be made too large. + /// @param _challengePeriod The challenge period to test. + function testFuzz_constructor_challengePeriodTooLarge_reverts(uint256 _challengePeriod) public { + _challengePeriod = bound(_challengePeriod, uint256(type(uint64).max) + 1, type(uint256).max); + vm.expectRevert("challenge period too large"); + new PreimageOracle(0, _challengePeriod); + } + /// @notice Test the pre-image key computation with a known pre-image. function test_keccak256PreimageKey_succeeds() public pure { bytes memory preimage = hex"deadbeef"; @@ -92,8 +100,8 @@ contract PreimageOracle_Test is Test { { // Bound the size to [0, 32] size = bound(size, 0, 32); - // Bound the part offset to [0, size + 8] - partOffset = bound(partOffset, 0, size + 8); + // Bound the part offset to [0, size + 8) + partOffset = bound(partOffset, 0, size + 7); // Load the local data into the preimage oracle under the test contract's context. bytes32 contextKey = oracle.loadLocalData(ident, localContext, word, uint8(size), uint8(partOffset)); @@ -178,8 +186,9 @@ contract PreimageOracle_Test is Test { bytes memory input = hex"deadbeef"; uint256 offset = 0; address precompile = address(bytes20(uint160(0x02))); // sha256 - bytes32 key = precompilePreimageKey(precompile, input); - oracle.loadPrecompilePreimagePart(offset, precompile, input); + uint64 gas = 72; + bytes32 key = precompilePreimageKey(precompile, gas, input); + oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); bytes32 part = oracle.preimageParts(key, offset); // size prefix - 1-byte result + 32-byte sha return data @@ -203,8 +212,9 @@ contract PreimageOracle_Test is Test { bytes memory input = hex"deadbeef"; uint256 offset = 9; address precompile = address(bytes20(uint160(0x02))); // sha256 - bytes32 key = precompilePreimageKey(precompile, input); - oracle.loadPrecompilePreimagePart(offset, precompile, input); + uint64 gas = 72; + bytes32 key = precompilePreimageKey(precompile, gas, input); + oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); bytes32 part = oracle.preimageParts(key, offset); // 32-byte sha return data @@ -224,8 +234,9 @@ contract PreimageOracle_Test is Test { bytes memory input = new bytes(193); // invalid input to induce a failed precompile call uint256 offset = 0; address precompile = address(bytes20(uint160(0x08))); // bn256Pairing - bytes32 key = precompilePreimageKey(precompile, input); - oracle.loadPrecompilePreimagePart(offset, precompile, input); + uint64 gas = 72; + bytes32 key = precompilePreimageKey(precompile, gas, input); + oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); bytes32 part = oracle.preimageParts(key, offset); // size prefix - 1-byte result + 0-byte sha return data @@ -249,8 +260,9 @@ contract PreimageOracle_Test is Test { bytes memory input = hex"deadbeef"; uint256 offset = 41; // 8-byte prefix + 1-byte result + 32-byte sha return data address precompile = address(bytes20(uint160(0x02))); // sha256 + uint64 gas = 72; vm.expectRevert(PartOffsetOOB.selector); - oracle.loadPrecompilePreimagePart(offset, precompile, input); + oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); } /// @notice Tests that a global precompile result cannot be set with an out-of-bounds offset. @@ -258,8 +270,34 @@ contract PreimageOracle_Test is Test { bytes memory input = hex"deadbeef"; uint256 offset = 42; address precompile = address(bytes20(uint160(0x02))); // sha256 + uint64 gas = 72; vm.expectRevert(PartOffsetOOB.selector); - oracle.loadPrecompilePreimagePart(offset, precompile, input); + oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); + } + + /// @notice Tests that a global precompile load succeeds on a variety of gas inputs. + function testFuzz_loadPrecompilePreimagePart_withVaryingGas_succeeds(uint64 _gas) public { + uint64 requiredGas = 100_000; + bytes memory input = hex"deadbeef"; + address precompile = address(uint160(0xdeadbeef)); + vm.mockCall(precompile, input, hex"abba"); + uint256 offset = 0; + uint64 minGas = uint64(bound(_gas, requiredGas * 3, 20_000_000)); + vm.expectCallMinGas(precompile, 0, requiredGas, input); + oracle.loadPrecompilePreimagePart{ gas: minGas }(offset, precompile, requiredGas, input); + } + + /// @notice Tests that a global precompile load succeeds on insufficient gas. + function test_loadPrecompilePreimagePart_withInsufficientGas_reverts() public { + uint64 requiredGas = 1_000_000; + bytes memory input = hex"deadbeef"; + uint256 offset = 0; + address precompile = address(uint160(0xdeadbeef)); + // This gas is sufficient to reach the gas checks in `loadPrecompilePreimagePart` but not enough to pass those + // checks + uint64 insufficientGas = requiredGas * 63 / 64; + vm.expectRevert(NotEnoughGas.selector); + oracle.loadPrecompilePreimagePart{ gas: insufficientGas }(offset, precompile, requiredGas, input); } } @@ -312,6 +350,17 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { oracle.initLPP{ value: bondSize }(TEST_UUID, 0, uint32(data.length)); } + /// @notice Tests that the `initLPP` function reverts if the proposal has already been initialized. + function test_initLPP_alreadyInitialized_reverts() public { + // Initialize the proposal. + uint256 bondSize = oracle.MIN_BOND_SIZE(); + oracle.initLPP{ value: bondSize }(TEST_UUID, 0, uint32(500)); + + // Re-initialize the proposal. + vm.expectRevert(AlreadyInitialized.selector); + oracle.initLPP{ value: bondSize }(TEST_UUID, 0, uint32(500)); + } + /// @notice Gas snapshot for `addLeaves` function test_addLeaves_gasSnapshot() public { // Allocate the preimage data. @@ -599,6 +648,63 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { }); } + /// @notice Tests that a proposal cannot be squeezed if the proposal has not been finalized. + function test_squeeze_notFinalized_reverts() public { + // Allocate the preimage data. + bytes memory data = new bytes(136); + for (uint256 i; i < data.length; i++) { + data[i] = 0xFF; + } + + // Initialize the proposal. + oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); + + // Generate the padded input data. + // Since the data is 136 bytes, which is exactly one keccak block, we will add one extra + // keccak block of empty padding to the input data. We need to do this here because the + // addLeavesLPP function will normally perform this padding internally when _finalize is + // set to true but we're explicitly testing the case where _finalize is not true. + bytes memory paddedData = new bytes(136 * 2); + for (uint256 i; i < data.length; i++) { + paddedData[i] = data[i]; + } + + // Add the leaves to the tree (2 keccak blocks.) + LibKeccak.StateMatrix memory stateMatrix; + bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); + oracle.addLeavesLPP(TEST_UUID, 0, paddedData, stateCommitments, false); + + // Construct the leaf preimage data for the blocks added. + LibKeccak.StateMatrix memory matrix; + PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); + + // Create a proof array with 16 elements. + bytes32[] memory preProof = new bytes32[](16); + preProof[0] = _hashLeaf(leaves[1]); + bytes32[] memory postProof = new bytes32[](16); + postProof[0] = _hashLeaf(leaves[0]); + for (uint256 i = 1; i < preProof.length; i++) { + bytes32 zeroHash = oracle.zeroHashes(i); + preProof[i] = zeroHash; + postProof[i] = zeroHash; + } + + // Warp past the challenge period. + vm.warp(block.timestamp + oracle.challengePeriod() + 1 seconds); + + // Finalize the proposal. + vm.expectRevert(ActiveProposal.selector); + oracle.squeezeLPP({ + _claimant: address(this), + _uuid: TEST_UUID, + _stateMatrix: _stateMatrixAtBlockIndex(data, 1), + _preState: leaves[0], + _preStateProof: preProof, + _postState: leaves[1], + _postStateProof: postProof + }); + } + /// @notice Tests that a proposal cannot be finalized until it has passed the challenge period. function test_squeeze_challengePeriodActive_reverts() public { // Allocate the preimage data. @@ -754,7 +860,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { /// @notice Tests that squeezing a large preimage proposal after the challenge period has passed always succeeds and /// persists the correct data. - function testFuzz_squeeze_succeeds(uint256 _numBlocks, uint32 _partOffset) public { + function testFuzz_squeezeLPP_succeeds(uint256 _numBlocks, uint32 _partOffset) public { _numBlocks = bound(_numBlocks, 1, 2 ** 8); _partOffset = uint32(bound(_partOffset, 0, _numBlocks * LibKeccak.BLOCK_SIZE_BYTES + 8 - 1)); @@ -1363,9 +1469,9 @@ function _setStatusByte(bytes32 _hash, uint8 _status) pure returns (bytes32 out_ } /// @notice Computes a precompile key for a given precompile address and input. -function precompilePreimageKey(address _precompile, bytes memory _input) pure returns (bytes32 key_) { - bytes memory p = abi.encodePacked(_precompile, _input); - uint256 sz = 20 + _input.length; +function precompilePreimageKey(address _precompile, uint64 _gas, bytes memory _input) pure returns (bytes32 key_) { + bytes memory p = abi.encodePacked(_precompile, _gas, _input); + uint256 sz = 20 + 8 + _input.length; assembly { let h := keccak256(add(0x20, p), sz) // Mask out prefix byte, replace with type 6 byte From 7a105fa3bb70eb9ccf37d01470a920c11e664da8 Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 15:53:15 +0900 Subject: [PATCH 08/19] Fix script error on Deploy.s.sol Config.sol Deployconfig.s.sol Types.sol --- .../contracts-bedrock/scripts/Config.sol | 95 +++++++++++++++ .../contracts-bedrock/scripts/Deploy.s.sol | 108 ++---------------- .../scripts/DeployConfig.s.sol | 75 +++++++++--- .../contracts-bedrock/scripts/Types.sol | 1 + 4 files changed, 161 insertions(+), 118 deletions(-) diff --git a/packages/tokamak/contracts-bedrock/scripts/Config.sol b/packages/tokamak/contracts-bedrock/scripts/Config.sol index 1a7692d49..d4253113c 100644 --- a/packages/tokamak/contracts-bedrock/scripts/Config.sol +++ b/packages/tokamak/contracts-bedrock/scripts/Config.sol @@ -3,6 +3,59 @@ pragma solidity ^0.8.0; import { Vm, VmSafe } from "forge-std/Vm.sol"; +/// @notice Enum representing different ways of outputting genesis allocs. +/// @custom:value NONE No output, used in internal tests. +/// @custom:value LATEST Output allocs only for latest fork. +/// @custom:value ALL Output allocs for all intermediary forks. +enum OutputMode { + NONE, + LATEST, + ALL +} + +library OutputModeUtils { + function toString(OutputMode _mode) internal pure returns (string memory) { + if (_mode == OutputMode.NONE) { + return "none"; + } else if (_mode == OutputMode.LATEST) { + return "latest"; + } else if (_mode == OutputMode.ALL) { + return "all"; + } else { + return "unknown"; + } + } +} + +/// @notice Enum of forks available for selection when generating genesis allocs. +enum Fork { + NONE, + DELTA, + ECOTONE, + FJORD, + GRANITE +} + +Fork constant LATEST_FORK = Fork.GRANITE; + +library ForkUtils { + function toString(Fork _fork) internal pure returns (string memory) { + if (_fork == Fork.NONE) { + return "none"; + } else if (_fork == Fork.DELTA) { + return "delta"; + } else if (_fork == Fork.ECOTONE) { + return "ecotone"; + } else if (_fork == Fork.FJORD) { + return "fjord"; + } else if (_fork == Fork.GRANITE) { + return "granite"; + } else { + return "unknown"; + } + } +} + /// @title Config /// @notice Contains all env var based config. Add any new env var parsing to this file /// to ensure that all config is in a single place. @@ -67,4 +120,46 @@ library Config { function drippieOwnerPrivateKey() internal view returns (uint256 _env) { _env = vm.envUint("DRIPPIE_OWNER_PRIVATE_KEY"); } + + /// @notice Returns the OutputMode for genesis allocs generation. + /// It reads the mode from the environment variable OUTPUT_MODE. + /// If it is unset, OutputMode.ALL is returned. + function outputMode() internal view returns (OutputMode) { + string memory modeStr = vm.envOr("OUTPUT_MODE", string("latest")); + bytes32 modeHash = keccak256(bytes(modeStr)); + if (modeHash == keccak256(bytes("none"))) { + return OutputMode.NONE; + } else if (modeHash == keccak256(bytes("latest"))) { + return OutputMode.LATEST; + } else if (modeHash == keccak256(bytes("all"))) { + return OutputMode.ALL; + } else { + revert(string.concat("Config: unknown output mode: ", modeStr)); + } + } + + /// @notice Returns the latest fork to use for genesis allocs generation. + /// It reads the fork from the environment variable FORK. If it is + /// unset, NONE is returned. + /// If set to the special value "latest", the latest fork is returned. + function fork() internal view returns (Fork) { + string memory forkStr = vm.envOr("FORK", string("")); + if (bytes(forkStr).length == 0) { + return Fork.NONE; + } + bytes32 forkHash = keccak256(bytes(forkStr)); + if (forkHash == keccak256(bytes("latest"))) { + return LATEST_FORK; + } else if (forkHash == keccak256(bytes("delta"))) { + return Fork.DELTA; + } else if (forkHash == keccak256(bytes("ecotone"))) { + return Fork.ECOTONE; + } else if (forkHash == keccak256(bytes("fjord"))) { + return Fork.FJORD; + } else if (forkHash == keccak256(bytes("granite"))) { + return Fork.GRANITE; + } else { + revert(string.concat("Config: unknown fork: ", forkStr)); + } + } } diff --git a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol index 65b48bb0a..6c70faab1 100644 --- a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol @@ -12,7 +12,7 @@ import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; import { GnosisSafeProxyFactory as SafeProxyFactory } from "safe-contracts/proxies/GnosisSafeProxyFactory.sol"; import { Enum as SafeOps } from "safe-contracts/common/Enum.sol"; -import { Deployer } from "scripts/Deployer.sol"; +import { Deployer } from "scripts/deploy/Deployer.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { AddressManager } from "src/legacy/AddressManager.sol"; @@ -44,24 +44,20 @@ import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol"; import { StorageSetter } from "src/universal/StorageSetter.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { Chains } from "scripts/Chains.sol"; -import { Config } from "scripts/Config.sol"; +import { Chains } from "scripts/libraries/Chains.sol"; +import { Config } from "scripts/libraries/Config.sol"; import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; import "src/dispute/lib/Types.sol"; -import { ChainAssertions } from "scripts/ChainAssertions.sol"; -import { Types } from "scripts/Types.sol"; +import { ChainAssertions } from "scripts/deploy/ChainAssertions.sol"; +import { Types } from "scripts/libraries/Types.sol"; import { LibStateDiff } from "scripts/libraries/LibStateDiff.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; -import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol"; +import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; import { Process } from "scripts/libraries/Process.sol"; -import { L2NativeToken } from "src/L1/L2NativeToken.sol"; -import { L1UsdcBridge } from "src/tokamak-contracts/USDC/L1//tokamak-UsdcBridge/L1UsdcBridge.sol"; -import { L1UsdcBridgeProxy } from "src/tokamak-contracts/USDC/L1/tokamak-UsdcBridge/L1UsdcBridgeProxy.sol"; - /// @title Deploy /// @notice Script used to deploy a bedrock system. The entire system is deployed within the `run` function. /// To add a new contract to the system, add a public function that deploys that individual contract. @@ -345,11 +341,8 @@ contract Deploy is Deployer { mustGetAddress("AddressManager"); mustGetAddress("ProxyAdmin"); - deployL2NativeToken(); deployProxies(); deployImplementations(); - deployL1UsdcBridgeProxy(); - setL1UsdcBridge(); initializeImplementations(); setAlphabetFaultGameImplementation({ _allowUpgrade: false }); @@ -401,8 +394,6 @@ contract Deploy is Deployer { deployPreimageOracle(); deployMips(); deployAnchorStateRegistry(); - - deployL1UsdcBridge(); } /// @notice Initialize all of the implementations @@ -799,80 +790,6 @@ contract Deploy is Deployer { addr_ = address(versions); } - function getL2NativeToken() public returns (address) { - bool isForkPublicNetwork = vm.envOr("FORK_PUBLIC_NETWORK", false); - if (isForkPublicNetwork) { - address addr_ = vm.envAddress("L2_NATIVE_TOKEN"); - return addr_; - } else { - L2NativeToken token = new L2NativeToken{ salt: _implSalt() }(); - address addr_ = address(token); - return addr_; - } - } - - /// @notice Deploy the Safe - function deployL2NativeToken() public broadcast { - string memory path = Config.deployConfigPath(); - address setupAddr_ = vm.envOr("L2_NATIVE_TOKEN", address(0)); - // If L2NativeToken is already existing on the network, we don't deploy the new contract. - if (setupAddr_ != address(0)) { - cfg.setNativeTokenAddress(setupAddr_, path); - console.log("Native token deployed at", setupAddr_); - return; - } - address addr_ = getL2NativeToken(); - cfg.setNativeTokenAddress(addr_, path); - console.log("Native token deployed at", addr_); - save("L2NativeToken", addr_); - } - - /// @notice Deploy the L1UsdcBridge - function deployL1UsdcBridge() public broadcast returns (address addr_) { - L1UsdcBridge bridge = new L1UsdcBridge{ salt: _implSalt() }(); - - require(address(bridge.messenger()) == address(0)); - require(address(bridge.otherBridge()) == address(0)); - require(address(bridge.l1Usdc()) == address(0)); - require(address(bridge.l2Usdc()) == address(0)); - - save("L1UsdcBridge", address(bridge)); - console.log("L1UsdcBridge deployed at %s", address(bridge)); - - addr_ = address(bridge); - } - - /// @notice Deploy the L1UsdcBridgeProxy - function deployL1UsdcBridgeProxy() public broadcast returns (address addr_) { - address l1UsdcBridge = mustGetAddress("L1UsdcBridge"); - address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); - L1UsdcBridgeProxy proxy = - new L1UsdcBridgeProxy({ _logic: l1UsdcBridge, initialOwner: msg.sender, _data: abi.encode() }); - - require(EIP1967Helper.getAdmin(address(proxy)) == address(msg.sender)); - - proxy.setAddress( - l1CrossDomainMessengerProxy, Predeploys.L2_USDC_BRIDGE, cfg.l1UsdcAddr(), Predeploys.FIATTOKENV2_2 - ); - proxy.upgradeTo(l1UsdcBridge); - - save("L1UsdcBridgeProxy", address(proxy)); - console.log("L1UsdcBridgeProxy deployed at %s", address(proxy)); - addr_ = address(proxy); - } - - function setL1UsdcBridge() public broadcast { - address l1UsdcBridgeProxy = mustGetAddress("L1UsdcBridgeProxy"); - address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); - - L1UsdcBridge bridge = L1UsdcBridge(l1UsdcBridgeProxy); - - require(address(bridge.messenger()) == l1CrossDomainMessengerProxy); - require(address(bridge.otherBridge()) == Predeploys.L2_USDC_BRIDGE); - require(address(bridge.l1Usdc()) == cfg.l1UsdcAddr()); - require(address(bridge.l2Usdc()) == Predeploys.FIATTOKENV2_2); - } - /// @notice Deploy the PreimageOracle function deployPreimageOracle() public broadcast returns (address addr_) { console.log("Deploying PreimageOracle implementation"); @@ -1135,14 +1052,6 @@ contract Deploy is Deployer { customGasTokenAddress = cfg.customGasTokenAddress(); } - // deployL2NativeToken(); - address l2NativeTokenAddress = cfg.nativeTokenAddress(); - if (l2NativeTokenAddress == address(0)) { - L2NativeToken token = new L2NativeToken{ salt: _implSalt() }(); - l2NativeTokenAddress = address(token); - } - console.log(" [Check ]l2NativeTokenAddress", l2NativeTokenAddress); - _upgradeAndCallViaSafe({ _proxy: payable(systemConfigProxy), _implementation: systemConfig, @@ -1164,8 +1073,7 @@ contract Deploy is Deployer { disputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"), optimismPortal: mustGetAddress("OptimismPortalProxy"), optimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"), - gasPayingToken: customGasTokenAddress, - nativeTokenAddress: l2NativeTokenAddress + gasPayingToken: customGasTokenAddress }) ) ) @@ -1492,7 +1400,7 @@ contract Deploy is Deployer { function loadMipsAbsolutePrestate() internal returns (Claim mipsAbsolutePrestate_) { if (block.chainid == Chains.LocalDevnet || block.chainid == Chains.GethDevnet) { // Fetch the absolute prestate dump - string memory filePath = string.concat(vm.projectRoot(), "/../../../op-program/bin/prestate-proof.json"); + string memory filePath = string.concat(vm.projectRoot(), "/../../op-program/bin/prestate-proof.json"); string[] memory commands = new string[](3); commands[0] = "bash"; commands[1] = "-c"; diff --git a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol index 6ad9637c5..6433509f6 100644 --- a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol @@ -4,23 +4,33 @@ pragma solidity 0.8.15; import { Script } from "forge-std/Script.sol"; import { console2 as console } from "forge-std/console2.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { Executables } from "scripts/Executables.sol"; +import { Executables } from "scripts/libraries/Executables.sol"; import { Process } from "scripts/libraries/Process.sol"; -import { Chains } from "scripts/Chains.sol"; +import { Chains } from "scripts/libraries/Chains.sol"; +import { Config, Fork, ForkUtils } from "scripts/libraries/Config.sol"; /// @title DeployConfig /// @notice Represents the configuration required to deploy the system. It is expected /// to read the file from JSON. A future improvement would be to have fallback /// values if they are not defined in the JSON themselves. contract DeployConfig is Script { + using stdJson for string; + using ForkUtils for Fork; + + /// @notice Represents an unset offset value, as opposed to 0, which denotes no-offset. + uint256 constant NULL_OFFSET = type(uint256).max; + string internal _json; address public finalSystemOwner; address public superchainConfigGuardian; - address public nativeTokenAddress; uint256 public l1ChainID; uint256 public l2ChainID; uint256 public l2BlockTime; + uint256 public l2GenesisDeltaTimeOffset; + uint256 public l2GenesisEcotoneTimeOffset; + uint256 public l2GenesisFjordTimeOffset; + uint256 public l2GenesisGraniteTimeOffset; uint256 public maxSequencerDrift; uint256 public sequencerWindowSize; uint256 public channelTimeout; @@ -66,12 +76,11 @@ contract DeployConfig is Script { uint256 public systemConfigStartBlock; uint256 public requiredProtocolVersion; uint256 public recommendedProtocolVersion; - address public l1UsdcAddr; uint256 public proofMaturityDelaySeconds; uint256 public disputeGameFinalityDelaySeconds; uint256 public respectedGameType; bool public useFaultProofs; - bool public usePlasma; + bool public useAltDA; string public daCommitmentType; uint256 public daChallengeWindow; uint256 public daResolveWindow; @@ -93,10 +102,15 @@ contract DeployConfig is Script { finalSystemOwner = stdJson.readAddress(_json, "$.finalSystemOwner"); superchainConfigGuardian = stdJson.readAddress(_json, "$.superchainConfigGuardian"); - nativeTokenAddress = stdJson.readAddress(_json, "$.nativeTokenAddress"); l1ChainID = stdJson.readUint(_json, "$.l1ChainID"); l2ChainID = stdJson.readUint(_json, "$.l2ChainID"); l2BlockTime = stdJson.readUint(_json, "$.l2BlockTime"); + + l2GenesisDeltaTimeOffset = _readOr(_json, "$.l2GenesisDeltaTimeOffset", NULL_OFFSET); + l2GenesisEcotoneTimeOffset = _readOr(_json, "$.l2GenesisEcotoneTimeOffset", NULL_OFFSET); + l2GenesisFjordTimeOffset = _readOr(_json, "$.l2GenesisFjordTimeOffset", NULL_OFFSET); + l2GenesisGraniteTimeOffset = _readOr(_json, "$.l2GenesisGraniteTimeOffset", NULL_OFFSET); + maxSequencerDrift = stdJson.readUint(_json, "$.maxSequencerDrift"); sequencerWindowSize = stdJson.readUint(_json, "$.sequencerWindowSize"); channelTimeout = stdJson.readUint(_json, "$.channelTimeout"); @@ -133,7 +147,7 @@ contract DeployConfig is Script { systemConfigStartBlock = stdJson.readUint(_json, "$.systemConfigStartBlock"); requiredProtocolVersion = stdJson.readUint(_json, "$.requiredProtocolVersion"); recommendedProtocolVersion = stdJson.readUint(_json, "$.recommendedProtocolVersion"); - l1UsdcAddr = stdJson.readAddress(_json, "$.l1UsdcAddr"); + useFaultProofs = _readOr(_json, "$.useFaultProofs", false); proofMaturityDelaySeconds = _readOr(_json, "$.proofMaturityDelaySeconds", 0); disputeGameFinalityDelaySeconds = _readOr(_json, "$.disputeGameFinalityDelaySeconds", 0); @@ -151,7 +165,7 @@ contract DeployConfig is Script { preimageOracleMinProposalSize = stdJson.readUint(_json, "$.preimageOracleMinProposalSize"); preimageOracleChallengePeriod = stdJson.readUint(_json, "$.preimageOracleChallengePeriod"); - usePlasma = _readOr(_json, "$.usePlasma", false); + useAltDA = _readOr(_json, "$.useAltDA", false); daCommitmentType = _readOr(_json, "$.daCommitmentType", "KeccakCommitment"); daChallengeWindow = _readOr(_json, "$.daChallengeWindow", 1000); daResolveWindow = _readOr(_json, "$.daResolveWindow", 1000); @@ -164,9 +178,16 @@ contract DeployConfig is Script { useInterop = _readOr(_json, "$.useInterop", false); } - function setNativeTokenAddress(address _nativeTokenAddress, string memory _path) public { - nativeTokenAddress = _nativeTokenAddress; - stdJson.write(vm.toString(_nativeTokenAddress), _path, "$.nativeTokenAddress"); + function fork() public view returns (Fork fork_) { + // let env var take precedence + fork_ = Config.fork(); + if (fork_ == Fork.NONE) { + // Will revert if no deploy config can be found either. + fork_ = latestGenesisFork(); + console.log("DeployConfig: using deploy config fork: %s", fork_.toString()); + } else { + console.log("DeployConfig: using env var fork: %s", fork_.toString()); + } } function l1StartingBlockTag() public returns (bytes32) { @@ -197,9 +218,9 @@ contract DeployConfig is Script { return uint256(_l2OutputOracleStartingTimestamp); } - /// @notice Allow the `usePlasma` config to be overridden in testing environments - function setUsePlasma(bool _usePlasma) public { - usePlasma = _usePlasma; + /// @notice Allow the `useAltDA` config to be overridden in testing environments + function setUseAltDA(bool _useAltDA) public { + useAltDA = _useAltDA; } /// @notice Allow the `useFaultProofs` config to be overridden in testing environments @@ -223,6 +244,19 @@ contract DeployConfig is Script { customGasTokenAddress = _token; } + function latestGenesisFork() internal view returns (Fork) { + if (l2GenesisGraniteTimeOffset == 0) { + return Fork.GRANITE; + } else if (l2GenesisFjordTimeOffset == 0) { + return Fork.FJORD; + } else if (l2GenesisEcotoneTimeOffset == 0) { + return Fork.ECOTONE; + } else if (l2GenesisDeltaTimeOffset == 0) { + return Fork.DELTA; + } + revert("DeployConfig: no supported fork active at genesis"); + } + function _getBlockByTag(string memory _tag) internal returns (bytes32) { string[] memory cmd = new string[](3); cmd[0] = Executables.bash; @@ -233,15 +267,20 @@ contract DeployConfig is Script { } function _readOr(string memory json, string memory key, bool defaultValue) internal view returns (bool) { - return vm.keyExists(json, key) ? stdJson.readBool(json, key) : defaultValue; + return vm.keyExistsJson(json, key) ? json.readBool(key) : defaultValue; } function _readOr(string memory json, string memory key, uint256 defaultValue) internal view returns (uint256) { - return vm.keyExists(json, key) ? stdJson.readUint(json, key) : defaultValue; + return (vm.keyExistsJson(json, key) && !_isNull(json, key)) ? json.readUint(key) : defaultValue; } function _readOr(string memory json, string memory key, address defaultValue) internal view returns (address) { - return vm.keyExists(json, key) ? stdJson.readAddress(json, key) : defaultValue; + return vm.keyExistsJson(json, key) ? json.readAddress(key) : defaultValue; + } + + function _isNull(string memory json, string memory key) internal pure returns (bool) { + string memory value = json.readString(key); + return (keccak256(bytes(value)) == keccak256(bytes("null"))); } function _readOr( @@ -253,6 +292,6 @@ contract DeployConfig is Script { view returns (string memory) { - return vm.keyExists(json, key) ? stdJson.readString(json, key) : defaultValue; + return vm.keyExists(json, key) ? json.readString(key) : defaultValue; } } diff --git a/packages/tokamak/contracts-bedrock/scripts/Types.sol b/packages/tokamak/contracts-bedrock/scripts/Types.sol index c8b48dc1e..7733be34f 100644 --- a/packages/tokamak/contracts-bedrock/scripts/Types.sol +++ b/packages/tokamak/contracts-bedrock/scripts/Types.sol @@ -9,6 +9,7 @@ library Types { address L2OutputOracle; address DisputeGameFactory; address DelayedWETH; + address PermissionedDelayedWETH; address AnchorStateRegistry; address OptimismMintableERC20Factory; address OptimismPortal; From 367a561ba693a7e497ff6b4d728f752c2dad734c Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 15:55:45 +0900 Subject: [PATCH 09/19] Fix script error on Deploy.s.sol Config.sol Deployconfig.s.sol Types.sol --- .../contracts-bedrock/scripts/Deploy.s.sol | 110 ++++++++++++++++-- .../scripts/DeployConfig.s.sol | 11 +- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol index 6c70faab1..d970f3b0f 100644 --- a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol @@ -12,7 +12,7 @@ import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; import { GnosisSafeProxyFactory as SafeProxyFactory } from "safe-contracts/proxies/GnosisSafeProxyFactory.sol"; import { Enum as SafeOps } from "safe-contracts/common/Enum.sol"; -import { Deployer } from "scripts/deploy/Deployer.sol"; +import { Deployer } from "scripts/Deployer.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { AddressManager } from "src/legacy/AddressManager.sol"; @@ -44,20 +44,24 @@ import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol"; import { StorageSetter } from "src/universal/StorageSetter.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { Chains } from "scripts/libraries/Chains.sol"; -import { Config } from "scripts/libraries/Config.sol"; +import { Chains } from "scripts/Chains.sol"; +import { Config } from "scripts/Config.sol"; import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; import "src/dispute/lib/Types.sol"; -import { ChainAssertions } from "scripts/deploy/ChainAssertions.sol"; -import { Types } from "scripts/libraries/Types.sol"; +import { ChainAssertions } from "scripts/ChainAssertions.sol"; +import { Types } from "scripts/Types.sol"; import { LibStateDiff } from "scripts/libraries/LibStateDiff.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; -import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; +import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol"; import { Process } from "scripts/libraries/Process.sol"; +import { L2NativeToken } from "src/L1/L2NativeToken.sol"; +import { L1UsdcBridge } from "src/tokamak-contracts/USDC/L1//tokamak-UsdcBridge/L1UsdcBridge.sol"; +import { L1UsdcBridgeProxy } from "src/tokamak-contracts/USDC/L1/tokamak-UsdcBridge/L1UsdcBridgeProxy.sol"; + /// @title Deploy /// @notice Script used to deploy a bedrock system. The entire system is deployed within the `run` function. /// To add a new contract to the system, add a public function that deploys that individual contract. @@ -279,7 +283,7 @@ contract Deploy is Deployer { vm.dumpState(Config.stateDumpPath("")); } - /// @notice Deploy all L1 contracts and write the state diff to a file. + /// @notice Deploy all L1 contracts and write the state diff to a file.DeployConfig function runWithStateDiff() public stateDiff { _run(); } @@ -341,8 +345,11 @@ contract Deploy is Deployer { mustGetAddress("AddressManager"); mustGetAddress("ProxyAdmin"); + deployL2NativeToken(); deployProxies(); deployImplementations(); + deployL1UsdcBridgeProxy(); + setL1UsdcBridge(); initializeImplementations(); setAlphabetFaultGameImplementation({ _allowUpgrade: false }); @@ -394,6 +401,8 @@ contract Deploy is Deployer { deployPreimageOracle(); deployMips(); deployAnchorStateRegistry(); + + deployL1UsdcBridge(); } /// @notice Initialize all of the implementations @@ -790,6 +799,80 @@ contract Deploy is Deployer { addr_ = address(versions); } + function getL2NativeToken() public returns (address) { + bool isForkPublicNetwork = vm.envOr("FORK_PUBLIC_NETWORK", false); + if (isForkPublicNetwork) { + address addr_ = vm.envAddress("L2_NATIVE_TOKEN"); + return addr_; + } else { + L2NativeToken token = new L2NativeToken{ salt: _implSalt() }(); + address addr_ = address(token); + return addr_; + } + } + + /// @notice Deploy the Safe + function deployL2NativeToken() public broadcast { + string memory path = Config.deployConfigPath(); + address setupAddr_ = vm.envOr("L2_NATIVE_TOKEN", address(0)); + // If L2NativeToken is already existing on the network, we don't deploy the new contract. + if (setupAddr_ != address(0)) { + cfg.setNativeTokenAddress(setupAddr_, path); + console.log("Native token deployed at", setupAddr_); + return; + } + address addr_ = getL2NativeToken(); + cfg.setNativeTokenAddress(addr_, path); + console.log("Native token deployed at", addr_); + save("L2NativeToken", addr_); + } + + /// @notice Deploy the L1UsdcBridge + function deployL1UsdcBridge() public broadcast returns (address addr_) { + L1UsdcBridge bridge = new L1UsdcBridge{ salt: _implSalt() }(); + + require(address(bridge.messenger()) == address(0)); + require(address(bridge.otherBridge()) == address(0)); + require(address(bridge.l1Usdc()) == address(0)); + require(address(bridge.l2Usdc()) == address(0)); + + save("L1UsdcBridge", address(bridge)); + console.log("L1UsdcBridge deployed at %s", address(bridge)); + + addr_ = address(bridge); + } + + /// @notice Deploy the L1UsdcBridgeProxy + function deployL1UsdcBridgeProxy() public broadcast returns (address addr_) { + address l1UsdcBridge = mustGetAddress("L1UsdcBridge"); + address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); + L1UsdcBridgeProxy proxy = + new L1UsdcBridgeProxy({ _logic: l1UsdcBridge, initialOwner: msg.sender, _data: abi.encode() }); + + require(EIP1967Helper.getAdmin(address(proxy)) == address(msg.sender)); + + proxy.setAddress( + l1CrossDomainMessengerProxy, Predeploys.L2_USDC_BRIDGE, cfg.l1UsdcAddr(), Predeploys.FIATTOKENV2_2 + ); + proxy.upgradeTo(l1UsdcBridge); + + save("L1UsdcBridgeProxy", address(proxy)); + console.log("L1UsdcBridgeProxy deployed at %s", address(proxy)); + addr_ = address(proxy); + } + + function setL1UsdcBridge() public broadcast { + address l1UsdcBridgeProxy = mustGetAddress("L1UsdcBridgeProxy"); + address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); + + L1UsdcBridge bridge = L1UsdcBridge(l1UsdcBridgeProxy); + + require(address(bridge.messenger()) == l1CrossDomainMessengerProxy); + require(address(bridge.otherBridge()) == Predeploys.L2_USDC_BRIDGE); + require(address(bridge.l1Usdc()) == cfg.l1UsdcAddr()); + require(address(bridge.l2Usdc()) == Predeploys.FIATTOKENV2_2); + } + /// @notice Deploy the PreimageOracle function deployPreimageOracle() public broadcast returns (address addr_) { console.log("Deploying PreimageOracle implementation"); @@ -1052,6 +1135,14 @@ contract Deploy is Deployer { customGasTokenAddress = cfg.customGasTokenAddress(); } + // deployL2NativeToken(); + address l2NativeTokenAddress = cfg.nativeTokenAddress(); + if (l2NativeTokenAddress == address(0)) { + L2NativeToken token = new L2NativeToken{ salt: _implSalt() }(); + l2NativeTokenAddress = address(token); + } + console.log(" [Check ]l2NativeTokenAddress", l2NativeTokenAddress); + _upgradeAndCallViaSafe({ _proxy: payable(systemConfigProxy), _implementation: systemConfig, @@ -1073,7 +1164,8 @@ contract Deploy is Deployer { disputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"), optimismPortal: mustGetAddress("OptimismPortalProxy"), optimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"), - gasPayingToken: customGasTokenAddress + gasPayingToken: customGasTokenAddress, + nativeTokenAddress: l2NativeTokenAddress }) ) ) @@ -1400,7 +1492,7 @@ contract Deploy is Deployer { function loadMipsAbsolutePrestate() internal returns (Claim mipsAbsolutePrestate_) { if (block.chainid == Chains.LocalDevnet || block.chainid == Chains.GethDevnet) { // Fetch the absolute prestate dump - string memory filePath = string.concat(vm.projectRoot(), "/../../op-program/bin/prestate-proof.json"); + string memory filePath = string.concat(vm.projectRoot(), "/../../../op-program/bin/prestate-proof.json"); string[] memory commands = new string[](3); commands[0] = "bash"; commands[1] = "-c"; diff --git a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol index 6433509f6..3a4c54b0b 100644 --- a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol @@ -4,9 +4,9 @@ pragma solidity 0.8.15; import { Script } from "forge-std/Script.sol"; import { console2 as console } from "forge-std/console2.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { Executables } from "scripts/libraries/Executables.sol"; +import { Executables } from "scripts/Executables.sol"; import { Process } from "scripts/libraries/Process.sol"; -import { Chains } from "scripts/libraries/Chains.sol"; +import { Chains } from "scripts/Chains.sol"; import { Config, Fork, ForkUtils } from "scripts/libraries/Config.sol"; /// @title DeployConfig @@ -24,6 +24,7 @@ contract DeployConfig is Script { address public finalSystemOwner; address public superchainConfigGuardian; + address public nativeTokenAddress; uint256 public l1ChainID; uint256 public l2ChainID; uint256 public l2BlockTime; @@ -76,6 +77,7 @@ contract DeployConfig is Script { uint256 public systemConfigStartBlock; uint256 public requiredProtocolVersion; uint256 public recommendedProtocolVersion; + address public l1UsdcAddr; uint256 public proofMaturityDelaySeconds; uint256 public disputeGameFinalityDelaySeconds; uint256 public respectedGameType; @@ -102,6 +104,7 @@ contract DeployConfig is Script { finalSystemOwner = stdJson.readAddress(_json, "$.finalSystemOwner"); superchainConfigGuardian = stdJson.readAddress(_json, "$.superchainConfigGuardian"); + nativeTokenAddress = stdJson.readAddress(_json, "$.nativeTokenAddress"); l1ChainID = stdJson.readUint(_json, "$.l1ChainID"); l2ChainID = stdJson.readUint(_json, "$.l2ChainID"); l2BlockTime = stdJson.readUint(_json, "$.l2BlockTime"); @@ -178,6 +181,10 @@ contract DeployConfig is Script { useInterop = _readOr(_json, "$.useInterop", false); } + function setNativeTokenAddress(address _nativeTokenAddress, string memory _path) public { + nativeTokenAddress = _nativeTokenAddress; + stdJson.write(vm.toString(_nativeTokenAddress), _path, "$.nativeTokenAddress"); + function fork() public view returns (Fork fork_) { // let env var take precedence fork_ = Config.fork(); From 077894ad2d56a88a3d6cdf8f6e3e3da89fa4da5f Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 16:16:52 +0900 Subject: [PATCH 10/19] Fix minor error on Deployconfig.s.sol --- packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol index 3a4c54b0b..d0df4cc7f 100644 --- a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol @@ -150,7 +150,7 @@ contract DeployConfig is Script { systemConfigStartBlock = stdJson.readUint(_json, "$.systemConfigStartBlock"); requiredProtocolVersion = stdJson.readUint(_json, "$.requiredProtocolVersion"); recommendedProtocolVersion = stdJson.readUint(_json, "$.recommendedProtocolVersion"); - + l1UsdcAddr = stdJson.readAddress(_json, "$.l1UsdcAddr"); useFaultProofs = _readOr(_json, "$.useFaultProofs", false); proofMaturityDelaySeconds = _readOr(_json, "$.proofMaturityDelaySeconds", 0); disputeGameFinalityDelaySeconds = _readOr(_json, "$.disputeGameFinalityDelaySeconds", 0); @@ -181,7 +181,7 @@ contract DeployConfig is Script { useInterop = _readOr(_json, "$.useInterop", false); } - function setNativeTokenAddress(address _nativeTokenAddress, string memory _path) public { + function setNativeTokenAddress(address _nativeTokenAddress, string memory _path) public { nativeTokenAddress = _nativeTokenAddress; stdJson.write(vm.toString(_nativeTokenAddress), _path, "$.nativeTokenAddress"); From 2489ce0dbac2f49bd7e4442ef33dab7efa629d31 Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 16:26:44 +0900 Subject: [PATCH 11/19] Fix minor error on Deployconfig.s.sol --- packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol index d0df4cc7f..56cfc2b46 100644 --- a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol @@ -184,6 +184,7 @@ contract DeployConfig is Script { function setNativeTokenAddress(address _nativeTokenAddress, string memory _path) public { nativeTokenAddress = _nativeTokenAddress; stdJson.write(vm.toString(_nativeTokenAddress), _path, "$.nativeTokenAddress"); + } function fork() public view returns (Fork fork_) { // let env var take precedence From f2193ec6e943ed6ed4df65772a04a747b9126d89 Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 16:34:54 +0900 Subject: [PATCH 12/19] Fix import error on DeployConfig.s.sol --- packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol index 56cfc2b46..8b9e0509a 100644 --- a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol @@ -7,7 +7,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { Executables } from "scripts/Executables.sol"; import { Process } from "scripts/libraries/Process.sol"; import { Chains } from "scripts/Chains.sol"; -import { Config, Fork, ForkUtils } from "scripts/libraries/Config.sol"; +import { Config, Fork, ForkUtils } from "scripts/Config.sol"; /// @title DeployConfig /// @notice Represents the configuration required to deploy the system. It is expected From 35fa13edea4a0d406c3992d66b2343ab5aeb884f Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 16:54:27 +0900 Subject: [PATCH 13/19] Update constuctor setting for OptimismPortalInterop.sol --- .../src/L1/OptimismPortalInterop.sol | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/tokamak/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/tokamak/contracts-bedrock/src/L1/OptimismPortalInterop.sol index 630ae5637..9fad94a09 100644 --- a/packages/tokamak/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/tokamak/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { OptimismPortal } from "src/L1/OptimismPortal.sol"; +import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; import { L1BlockInterop, ConfigType } from "src/L2/L1BlockInterop.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Constants } from "src/libraries/Constants.sol"; @@ -11,10 +11,17 @@ import { Constants } from "src/libraries/Constants.sol"; /// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1 /// and L2. Messages sent directly to the OptimismPortal have no form of replayability. /// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface. -contract OptimismPortalInterop is OptimismPortal { +contract OptimismPortalInterop is OptimismPortal2 { /// @notice Thrown when a non-depositor account attempts update static configuration. error Unauthorized(); + constructor( + uint256 _proofMaturityDelaySeconds, + uint256 _disputeGameFinalityDelaySeconds + ) + OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) + { } + /// @custom:semver +interop function version() public pure override returns (string memory) { return string.concat(super.version(), "+interop"); From 6bec1f3659cb967806117c527c7f79e1fa91eabd Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Tue, 8 Oct 2024 17:53:25 +0900 Subject: [PATCH 14/19] Update to original OptimismPortalInterop.sol --- packages/tokamak/contracts-bedrock/src/L2/L1BlockInterop.sol | 1 + .../tokamak/contracts-bedrock/src/libraries/Constants.sol | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/tokamak/contracts-bedrock/src/L2/L1BlockInterop.sol b/packages/tokamak/contracts-bedrock/src/L2/L1BlockInterop.sol index 8dbf986fb..fcaeb4234 100644 --- a/packages/tokamak/contracts-bedrock/src/L2/L1BlockInterop.sol +++ b/packages/tokamak/contracts-bedrock/src/L2/L1BlockInterop.sol @@ -1,3 +1,4 @@ + // SPDX-License-Identifier: MIT pragma solidity 0.8.15; diff --git a/packages/tokamak/contracts-bedrock/src/libraries/Constants.sol b/packages/tokamak/contracts-bedrock/src/libraries/Constants.sol index e86010421..31d01e966 100644 --- a/packages/tokamak/contracts-bedrock/src/libraries/Constants.sol +++ b/packages/tokamak/contracts-bedrock/src/libraries/Constants.sol @@ -50,4 +50,8 @@ library Constants { }); return config; } + + /// @notice The `reinitailizer` input for upgradable contracts. This value must be updated + /// each time that the contracts are deployed. + uint8 internal constant INITIALIZER = 3; } From 4a60fcf65a9337be4e2587efb56e2a630ab6c93b Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Wed, 9 Oct 2024 20:46:10 +0900 Subject: [PATCH 15/19] Update contracts for op-contracts_v1.6.0 upgrade --- .../scripts/ChainAssertions.sol | 26 +++++++++++++++++++ .../src/L1/OptimismPortal2.sol | 7 ++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/tokamak/contracts-bedrock/scripts/ChainAssertions.sol b/packages/tokamak/contracts-bedrock/scripts/ChainAssertions.sol index a99c14e95..5d23381b0 100644 --- a/packages/tokamak/contracts-bedrock/scripts/ChainAssertions.sol +++ b/packages/tokamak/contracts-bedrock/scripts/ChainAssertions.sol @@ -208,6 +208,32 @@ library ChainAssertions { } } + /// @notice Asserts that the permissioned DelayedWETH is setup correctly + function checkPermissionedDelayedWETH( + Types.ContractSet memory _contracts, + DeployConfig _cfg, + bool _isProxy, + address _expectedOwner + ) + internal + view + { + console.log("Running chain assertions on the permissioned DelayedWETH"); + DelayedWETH weth = DelayedWETH(payable(_contracts.PermissionedDelayedWETH)); + + // Check that the contract is initialized + assertSlotValueIsOne({ _contractAddress: address(weth), _slot: 0, _offset: 0 }); + + if (_isProxy) { + require(weth.owner() == _expectedOwner); + require(weth.delay() == _cfg.faultGameWithdrawalDelay()); + require(weth.config() == SuperchainConfig(_contracts.SuperchainConfig)); + } else { + require(weth.owner() == _expectedOwner); + require(weth.delay() == _cfg.faultGameWithdrawalDelay()); + } + } + /// @notice Asserts that the L2OutputOracle is setup correctly function checkL2OutputOracle( Types.ContractSet memory _contracts, diff --git a/packages/tokamak/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/tokamak/contracts-bedrock/src/L1/OptimismPortal2.sol index 8c5af46f6..26191df52 100644 --- a/packages/tokamak/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/tokamak/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -51,6 +51,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, OnApprove, ISemver /// @notice The L2 gas limit set when eth is deposited using the receive() function. uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; + /// @notice The L2 gas limit for system deposit transactions that are initiated from L1. + uint32 internal constant SYSTEM_DEPOSIT_GAS_LIMIT = 200_000; + /// @notice Address of the L2 account which initiated a withdrawal in this transaction. /// If the of this variable is the default L2 sender address, then we are NOT inside of /// a call to finalizeWithdrawalTransaction. @@ -147,7 +150,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, OnApprove, ISemver /// @notice Semantic version. /// @custom:semver 3.10.0 - string public constant version = "3.10.0"; + function version() public pure virtual returns (string memory) { + return "3.10.0"; + } /// @notice Constructs the OptimismPortal contract. constructor(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) { From fb838b006c90b963c26efa4ad60b09c61d7be62a Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Thu, 10 Oct 2024 15:07:03 +0900 Subject: [PATCH 16/19] Modified to ensure no changes to optimismportal2 --- .../contracts-bedrock/scripts/Deploy.s.sol | 32 +++++++------------ .../src/L1/OptimismPortal2.sol | 7 +--- .../src/L1/OptimismPortalInterop.sol | 11 ++----- 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol index d970f3b0f..4d7e2ea65 100644 --- a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol @@ -654,9 +654,10 @@ contract Deploy is Deployer { function deployOptimismPortal() public broadcast returns (address addr_) { console.log("Deploying OptimismPortal implementation"); if (cfg.useInterop()) { - console.log("Attempting to deploy OptimismPortal with interop, this config is a noop"); + addr_ = address(new OptimismPortalInterop{ salt: _implSalt() }()); + } else { + addr_ = address(new OptimismPortal{ salt: _implSalt() }()); } - addr_ = address(new OptimismPortal{ salt: _implSalt() }()); save("OptimismPortal", addr_); console.log("OptimismPortal deployed at %s", addr_); @@ -677,31 +678,22 @@ contract Deploy is Deployer { uint32(cfg.respectedGameType()) == cfg.respectedGameType(), "Deploy: respectedGameType must fit into uint32" ); - if (cfg.useInterop()) { - addr_ = address( - new OptimismPortalInterop{ salt: _implSalt() }({ - _proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(), - _disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds() - }) - ); - } else { - addr_ = address( - new OptimismPortal2{ salt: _implSalt() }({ - _proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(), - _disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds() - }) - ); - } + OptimismPortal2 portal = new OptimismPortal2{ salt: _implSalt() }({ + _proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(), + _disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds() + }); - save("OptimismPortal2", addr_); - console.log("OptimismPortal2 deployed at %s", addr_); + save("OptimismPortal2", address(portal)); + console.log("OptimismPortal2 deployed at %s", address(portal)); // Override the `OptimismPortal2` contract to the deployed implementation. This is necessary // to check the `OptimismPortal2` implementation alongside dependent contracts, which // are always proxies. Types.ContractSet memory contracts = _proxiesUnstrict(); - contracts.OptimismPortal2 = addr_; + contracts.OptimismPortal2 = address(portal); ChainAssertions.checkOptimismPortal2({ _contracts: contracts, _cfg: cfg, _isProxy: false }); + + addr_ = address(portal); } /// @notice Deploy the L2OutputOracle diff --git a/packages/tokamak/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/tokamak/contracts-bedrock/src/L1/OptimismPortal2.sol index 26191df52..8c5af46f6 100644 --- a/packages/tokamak/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/tokamak/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -51,9 +51,6 @@ contract OptimismPortal2 is Initializable, ResourceMetering, OnApprove, ISemver /// @notice The L2 gas limit set when eth is deposited using the receive() function. uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; - /// @notice The L2 gas limit for system deposit transactions that are initiated from L1. - uint32 internal constant SYSTEM_DEPOSIT_GAS_LIMIT = 200_000; - /// @notice Address of the L2 account which initiated a withdrawal in this transaction. /// If the of this variable is the default L2 sender address, then we are NOT inside of /// a call to finalizeWithdrawalTransaction. @@ -150,9 +147,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, OnApprove, ISemver /// @notice Semantic version. /// @custom:semver 3.10.0 - function version() public pure virtual returns (string memory) { - return "3.10.0"; - } + string public constant version = "3.10.0"; /// @notice Constructs the OptimismPortal contract. constructor(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) { diff --git a/packages/tokamak/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/tokamak/contracts-bedrock/src/L1/OptimismPortalInterop.sol index 9fad94a09..630ae5637 100644 --- a/packages/tokamak/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/tokamak/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; +import { OptimismPortal } from "src/L1/OptimismPortal.sol"; import { L1BlockInterop, ConfigType } from "src/L2/L1BlockInterop.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Constants } from "src/libraries/Constants.sol"; @@ -11,17 +11,10 @@ import { Constants } from "src/libraries/Constants.sol"; /// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1 /// and L2. Messages sent directly to the OptimismPortal have no form of replayability. /// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface. -contract OptimismPortalInterop is OptimismPortal2 { +contract OptimismPortalInterop is OptimismPortal { /// @notice Thrown when a non-depositor account attempts update static configuration. error Unauthorized(); - constructor( - uint256 _proofMaturityDelaySeconds, - uint256 _disputeGameFinalityDelaySeconds - ) - OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) - { } - /// @custom:semver +interop function version() public pure override returns (string memory) { return string.concat(super.version(), "+interop"); From 69c6b217b23f9bec64bd845fb027540551e46879 Mon Sep 17 00:00:00 2001 From: AaronLee22 Date: Thu, 10 Oct 2024 16:27:20 +0900 Subject: [PATCH 17/19] Modified to use usePlasma --- .../tokamak/contracts-bedrock/scripts/Deploy.s.sol | 13 +++++++------ .../contracts-bedrock/scripts/DeployConfig.s.sol | 10 +++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol index 4d7e2ea65..39fce4991 100644 --- a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol @@ -295,11 +295,11 @@ contract Deploy is Deployer { console.log("deployed Safe!"); setupSuperchain(); console.log("set up superchain!"); - if (cfg.useAltDA()) { + if (cfg.usePlasma()) { bytes32 typeHash = keccak256(bytes(cfg.daCommitmentType())); bytes32 keccakHash = keccak256(bytes("KeccakCommitment")); if (typeHash == keccakHash) { - setupOpAltDA(); + setupOpPlasma(); } } setupOpChain(); @@ -394,6 +394,7 @@ contract Deploy is Deployer { deployL1ERC721Bridge(); deployOptimismPortal(); deployL2OutputOracle(); + // Fault proofs deployOptimismPortal2(); deployDisputeGameFactory(); @@ -429,9 +430,9 @@ contract Deploy is Deployer { initializeAnchorStateRegistry(); } - /// @notice Add AltDA setup to the OP chain - function setupOpAltDA() public { - console.log("Deploying OP AltDA"); + /// @notice Add Plasma setup to the OP chain + function setupOpPlasma() public { + console.log("Deploying OP Plasma"); deployDataAvailabilityChallengeProxy(); deployDataAvailabilityChallenge(); initializeDataAvailabilityChallenge(); @@ -467,8 +468,8 @@ contract Deploy is Deployer { console.log("Deploying safe: %s with salt %s", _name, vm.toString(salt)); (SafeProxyFactory safeProxyFactory, Safe safeSingleton) = _getSafeFactory(); + address[] memory expandedOwners = new address[](_owners.length + 1); if (_keepDeployer) { - address[] memory expandedOwners = new address[](_owners.length + 1); // By always adding msg.sender first we know that the previousOwner will be SENTINEL_OWNERS, which makes it // easier to call removeOwner later. expandedOwners[0] = msg.sender; diff --git a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol index 8b9e0509a..9fb56ceee 100644 --- a/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/DeployConfig.s.sol @@ -82,7 +82,7 @@ contract DeployConfig is Script { uint256 public disputeGameFinalityDelaySeconds; uint256 public respectedGameType; bool public useFaultProofs; - bool public useAltDA; + bool public usePlasma; string public daCommitmentType; uint256 public daChallengeWindow; uint256 public daResolveWindow; @@ -168,7 +168,7 @@ contract DeployConfig is Script { preimageOracleMinProposalSize = stdJson.readUint(_json, "$.preimageOracleMinProposalSize"); preimageOracleChallengePeriod = stdJson.readUint(_json, "$.preimageOracleChallengePeriod"); - useAltDA = _readOr(_json, "$.useAltDA", false); + usePlasma = _readOr(_json, "$.usePlasma", false); daCommitmentType = _readOr(_json, "$.daCommitmentType", "KeccakCommitment"); daChallengeWindow = _readOr(_json, "$.daChallengeWindow", 1000); daResolveWindow = _readOr(_json, "$.daResolveWindow", 1000); @@ -226,9 +226,9 @@ contract DeployConfig is Script { return uint256(_l2OutputOracleStartingTimestamp); } - /// @notice Allow the `useAltDA` config to be overridden in testing environments - function setUseAltDA(bool _useAltDA) public { - useAltDA = _useAltDA; + /// @notice Allow the `usePlasma` config to be overridden in testing environments + function setUsePlasma(bool _usePlasma) public { + usePlasma = _usePlasma; } /// @notice Allow the `useFaultProofs` config to be overridden in testing environments From 7cc8473c17c9b2997b0936b41537eede0d163d81 Mon Sep 17 00:00:00 2001 From: ohbyeongmin Date: Wed, 16 Oct 2024 13:30:36 +0900 Subject: [PATCH 18/19] [OR-1875] fix failed test and add setAnchor tests --- .../test/dispute/AnchorStateRegistry.t.sol | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/packages/tokamak/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol b/packages/tokamak/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol index 4f9cb3c81..b5d1d7c55 100644 --- a/packages/tokamak/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol +++ b/packages/tokamak/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol @@ -124,7 +124,7 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In // Try to update the anchor state. vm.prank(address(gameProxy)); - vm.expectRevert("AnchorStateRegistry: fault dispute game not registered with factory"); + vm.expectRevert(UnregisteredGame.selector); anchorStateRegistry.tryUpdateAnchorState(); // Confirm that the anchor state has not updated. @@ -132,4 +132,88 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In assertEq(updatedL2BlockNumber, l2BlockNumber); assertEq(updatedRoot.raw(), root.raw()); } + + function test_setAnchorState_invalidGame_fails() public { + // Confirm that the anchor state is older than the game state. + (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); + require(l2BlockNumber < gameProxy.l2BlockNumber(), "l2BlockNumber < gameProxy.l2BlockNumber()"); + + // Mock the state that we want. + vm.mockCall( + address(disputeGameFactory), + abi.encodeWithSelector( + disputeGameFactory.games.selector, gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData() + ), + abi.encode(address(0), 0) + ); + + // Try to update the anchor state. + vm.prank(superchainConfig.guardian()); + vm.expectRevert(UnregisteredGame.selector); + anchorStateRegistry.setAnchorState(gameProxy); + + // Confirm that the anchor state has not updated. + (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); + assertEq(updatedL2BlockNumber, l2BlockNumber); + assertEq(updatedRoot.raw(), root.raw()); + } + + /// @dev Tests that setting the anchor state fails if the challenger wins. + function test_setAnchorState_challengerWins_fails() public { + (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); + + // Mock the state that we want. + vm.mockCall( + address(gameProxy), + abi.encodeWithSelector(gameProxy.status.selector), + abi.encode(GameStatus.CHALLENGER_WINS) + ); + + // Set the anchor state. + vm.prank(superchainConfig.guardian()); + vm.expectRevert(InvalidGameStatus.selector); + anchorStateRegistry.setAnchorState(gameProxy); + + // Confirm that the anchor state has not updated. + (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); + assertEq(updatedL2BlockNumber, l2BlockNumber); + assertEq(updatedRoot.raw(), root.raw()); + } + + /// @dev Tests that setting the anchor state fails if the game is in progress. + function test_setAnchorState_gameInProgress_fails() public { + (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); + + // Mock the state that we want. + vm.mockCall( + address(gameProxy), abi.encodeWithSelector(gameProxy.status.selector), abi.encode(GameStatus.IN_PROGRESS) + ); + + // Set the anchor state. + vm.prank(superchainConfig.guardian()); + vm.expectRevert(InvalidGameStatus.selector); + anchorStateRegistry.setAnchorState(gameProxy); + + // Confirm that the anchor state has not updated. + (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); + assertEq(updatedL2BlockNumber, l2BlockNumber); + assertEq(updatedRoot.raw(), root.raw()); + } + + /// @dev Tests that setting the anchor state succeeds. + function test_setAnchorState_succeeds() public { + // Mock the state that we want. + vm.mockCall( + address(gameProxy), abi.encodeWithSelector(gameProxy.status.selector), abi.encode(GameStatus.DEFENDER_WINS) + ); + + // Set the anchor state. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.setAnchorState(gameProxy); + + // Confirm that the anchor state has updated. + (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); + assertEq(updatedL2BlockNumber, gameProxy.l2BlockNumber()); + assertEq(updatedRoot.raw(), gameProxy.rootClaim().raw()); + } } From 740a5a5437f73fb7126bdd8fc11afbedd1acd746 Mon Sep 17 00:00:00 2001 From: ohbyeongmin Date: Thu, 17 Oct 2024 11:30:29 +0900 Subject: [PATCH 19/19] [OR-1875] fix oracle_kzg test --- cannon/mipsevm/evm_test.go | 6 ++- .../open_mips_tests/test/bin/oracle_kzg.bin | Bin 296 -> 296 bytes .../open_mips_tests/test/oracle_kzg.asm | 40 +++++++++--------- cannon/mipsevm/state_test.go | 2 +- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/cannon/mipsevm/evm_test.go b/cannon/mipsevm/evm_test.go index 138df8f24..59cec5d47 100644 --- a/cannon/mipsevm/evm_test.go +++ b/cannon/mipsevm/evm_test.go @@ -3,6 +3,7 @@ package mipsevm import ( "bytes" "debug/elf" + "encoding/binary" "errors" "fmt" "io" @@ -137,11 +138,14 @@ func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext Loca } preimage := localOracle.GetPreimage(preimage.Keccak256Key(wit.PreimageKey).PreimageKey()) precompile := common.BytesToAddress(preimage[:20]) - callInput := preimage[20:] + requiredGas := binary.BigEndian.Uint64(preimage[20:28]) + callInput := preimage[28:] + input, err := oracle.ABI.Pack( "loadPrecompilePreimagePart", new(big.Int).SetUint64(uint64(wit.PreimageOffset)), precompile, + requiredGas, callInput, ) require.NoError(t, err) diff --git a/cannon/mipsevm/open_mips_tests/test/bin/oracle_kzg.bin b/cannon/mipsevm/open_mips_tests/test/bin/oracle_kzg.bin index 20a642fa430cff0e2670d40d4e178694b419af50..ba8f0a95fb85523324d6dd3eb22b954f113a3ba3 100644 GIT binary patch delta 135 zcmZ3%w1TPLM!|DMyUWIt~UPTPD<$BWMPY&0@pBlWfYd?kSMXVZ-5C zV9If*49MoO;W+)@l%v!L$QH2SsLVFy@GS+hMQk_>jZ8VNN&(ptHXPY+O*uXs1F~gQ dSQyxBSPd9VmI^Sa0L@ZiV_;EX;-6T*5&*cuAOZjY delta 135 zcmZ3%w1TPLM!rwG}n|PeHM@{VZ-62Y05E49>|ta dVPRmiVKrbdSt`Jw0yIm7je$jliGO1KN&xYMALsx8 diff --git a/cannon/mipsevm/open_mips_tests/test/oracle_kzg.asm b/cannon/mipsevm/open_mips_tests/test/oracle_kzg.asm index 8faf349bf..86da7c61f 100644 --- a/cannon/mipsevm/open_mips_tests/test/oracle_kzg.asm +++ b/cannon/mipsevm/open_mips_tests/test/oracle_kzg.asm @@ -5,36 +5,38 @@ .ent test # load hash at 0x30001000 -# point evaluation precompile input - 01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a -# 0x0a44472c cb798bc5 954fc466 e6ee2c31 e1ca8a87 d000966c 629d679a 4a29921f = keccak(address(0xa) ++ precompile_input) -# 0x0644472c cb798bc5 954fc466 e6ee2c31 e1ca8a87 d000966c 629d679a 4a29921f = keccak(address(0xa) ++ precompile_input).key (precompile) +# requiredGas is 50_000 +# point evaluation precompile input (requiredGas ++ precompileInput) - 000000000000c35001e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a +# 0x3efd5c3c 1c555298 0c63aee5 4570c276 cbff7532 796b4d75 3132d51a 6bedf0c6 = keccak(address(0xa) ++ required_gas ++ precompile_input) +# 0x06fd5c3c 1c555298 0c63aee5 4570c276 cbff7532 796b4d75 3132d51a 6bedf0c6 = keccak(address(0xa) ++ required_gas ++ precompile_input).key (precompile) + test: lui $s0, 0x3000 ori $s0, 0x1000 - lui $t0, 0x0644 - ori $t0, 0x472c + lui $t0, 0x06fd + ori $t0, 0x5c3c sw $t0, 0($s0) - lui $t0, 0xcb79 - ori $t0, 0x8bc5 + lui $t0, 0x1c55 + ori $t0, 0x5298 sw $t0, 4($s0) - lui $t0, 0x954f - ori $t0, 0xc466 + lui $t0, 0x0c63 + ori $t0, 0xaee5 sw $t0, 8($s0) - lui $t0, 0xe6ee - ori $t0, 0x2c31 + lui $t0, 0x4570 + ori $t0, 0xc276 sw $t0, 0xc($s0) - lui $t0, 0xe1ca - ori $t0, 0x8a87 + lui $t0, 0xcbff + ori $t0, 0x7532 sw $t0, 0x10($s0) - lui $t0, 0xd000 - ori $t0, 0x966c + lui $t0, 0x796b + ori $t0, 0x4d75 sw $t0, 0x14($s0) - lui $t0, 0x629d - ori $t0, 0x679a + lui $t0, 0x3132 + ori $t0, 0xd51a sw $t0, 0x18($s0) - lui $t0, 0x4a29 - ori $t0, 0x921f + lui $t0, 0x6bed + ori $t0, 0xf0c6 sw $t0, 0x1c($s0) # preimage request - write(fdPreimageWrite, preimageData, 32) diff --git a/cannon/mipsevm/state_test.go b/cannon/mipsevm/state_test.go index 40097ec3a..696cdb84d 100644 --- a/cannon/mipsevm/state_test.go +++ b/cannon/mipsevm/state_test.go @@ -292,7 +292,7 @@ func staticPrecompileOracle(t *testing.T, precompile common.Address, input []byt func selectOracleFixture(t *testing.T, programName string) PreimageOracle { if strings.HasPrefix(programName, "oracle_kzg") { precompile := common.BytesToAddress([]byte{0xa}) - input := common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a") + input := common.FromHex("000000000000c35001e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a") blobPrecompileReturnValue := common.FromHex("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001") return staticPrecompileOracle(t, precompile, input, append([]byte{0x1}, blobPrecompileReturnValue...)) } else if strings.HasPrefix(programName, "oracle") {