diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml index f075b89fb1bca..0043b5df160cc 100644 --- a/.semgrep/rules/sol-rules.yaml +++ b/.semgrep/rules/sol-rules.yaml @@ -165,8 +165,6 @@ rules: exclude: - packages/contracts-bedrock/src/libraries/Bytes.sol - packages/contracts-bedrock/src/legacy/LegacyMintableERC20.sol - - packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol - - packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol - id: sol-style-malformed-revert languages: [solidity] @@ -180,9 +178,6 @@ rules: - pattern-not-regex: string\.concat\(\"(\w+:\s[^"]*)\"\,.+\) - pattern-not-regex: \"([a-zA-Z0-9\s]+-[a-zA-Z0-9\s]+)\" - pattern-not-regex: \"([a-zA-Z0-9\s]+-[a-zA-Z0-9\s]+-[a-zA-Z0-9\s]+)\" - paths: - exclude: - - packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol - id: sol-style-use-abi-encodecall languages: [solidity] diff --git a/cannon/mipsevm/versions/version.go b/cannon/mipsevm/versions/version.go index 5ef48a779ab16..df7a73dd57189 100644 --- a/cannon/mipsevm/versions/version.go +++ b/cannon/mipsevm/versions/version.go @@ -96,9 +96,21 @@ func GetStateVersionStrings() []string { return vers } +// GetCurrentVersion returns the current default version. +func GetCurrentVersion() StateVersion { + return VersionMultiThreaded64_v4 +} + +// GetExperimentalVersion returns the newest in-development version of Cannon if it exists, otherwise returns the same +// value as GetCurrentVersion. +func GetExperimentalVersion() StateVersion { + lastVersionIndex := len(StateVersionTypes) - 1 + return StateVersionTypes[lastVersionIndex] +} + // IsSupportedMultiThreaded64 returns true if the state version is a 64-bit multithreaded VM that is currently supported func IsSupportedMultiThreaded64(ver StateVersion) bool { - return ver == VersionMultiThreaded64_v4 + return ver == GetCurrentVersion() || ver == GetExperimentalVersion() } // IsSupported returns true if the state version is currently supported diff --git a/cannon/mipsevm/versions/version_test.go b/cannon/mipsevm/versions/version_test.go index b43aa5cfecd15..fafa1efa6e98c 100644 --- a/cannon/mipsevm/versions/version_test.go +++ b/cannon/mipsevm/versions/version_test.go @@ -1,6 +1,7 @@ package versions import ( + "slices" "testing" "github.com/stretchr/testify/require" @@ -51,3 +52,30 @@ func TestIsSupported(t *testing.T) { }) } } + +func TestGetCurrentVersion(t *testing.T) { + require.True(t, IsSupported(int(GetCurrentVersion()))) +} + +func TestGetExperimentalVersion(t *testing.T) { + require.True(t, IsSupported(int(GetExperimentalVersion()))) + + require.GreaterOrEqual(t, GetExperimentalVersion(), GetCurrentVersion()) + + // Experimental version should be equal to the latest version + expectedValue := slices.Max(StateVersionTypes) + require.Equal(t, expectedValue, GetExperimentalVersion()) +} + +func TestStateVersionTypes(t *testing.T) { + // Versions should be in ascending order + lastVersion := StateVersion(0) + for i, version := range StateVersionTypes { + if i == 0 { + require.GreaterOrEqual(t, version, lastVersion) + } else { + require.Greater(t, version, lastVersion) + } + lastVersion = version + } +} diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 1ddd035a23e4b..15124248da500 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" ) @@ -76,7 +77,7 @@ func (recipe *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, er ChallengePeriodSeconds: big.NewInt(120), ProofMaturityDelaySeconds: big.NewInt(12), DisputeGameFinalityDelaySeconds: big.NewInt(6), - MipsVersion: big.NewInt(6), + MipsVersion: big.NewInt(int64(versions.GetExperimentalVersion())), }, UseInterop: true, }, diff --git a/op-deployer/pkg/deployer/state/chain_intent.go b/op-deployer/pkg/deployer/state/chain_intent.go index c671db4cc3f04..6fb79e659181c 100644 --- a/op-deployer/pkg/deployer/state/chain_intent.go +++ b/op-deployer/pkg/deployer/state/chain_intent.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" ) @@ -21,9 +22,9 @@ const ( func (v VMType) MipsVersion() uint64 { switch v { case VMTypeCannon: - return 7 + return uint64(versions.GetCurrentVersion()) case VMTypeCannonNext: - return 7 + return uint64(versions.GetExperimentalVersion()) default: // Not a mips VM - return empty value return 0 diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 6ed1600aadb11..4e8b6503335c4 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -19,6 +19,7 @@ import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { DeploySuperchain2 } from "scripts/deploy/DeploySuperchain2.s.sol"; import { DeployImplementations2 } from "scripts/deploy/DeployImplementations2.s.sol"; import { DeployAltDA2 } from "scripts/deploy/DeployAltDA2.s.sol"; +import { StandardConstants } from "scripts/deploy/StandardConstants.sol"; // Libraries import { Types } from "scripts/libraries/Types.sol"; @@ -268,7 +269,7 @@ contract Deploy is Deployer { challengePeriodSeconds: cfg.preimageOracleChallengePeriod(), proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(), disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds(), - mipsVersion: 6, + mipsVersion: StandardConstants.MIPS_VERSION, l1ContractsRelease: "dev", superchainConfigProxy: superchainConfig, protocolVersionsProxy: IProtocolVersions(artifacts.mustGetAddress("ProtocolVersionsProxy")), diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 82435755f67ce..19ae10105818f 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -132,8 +132,8 @@ "sourceCodeHash": "0x734a6b2aa6406bc145d848ad6071d3af1d40852aeb8f4b2f6f51beaad476e2d3" }, "src/cannon/MIPS64.sol:MIPS64": { - "initCodeHash": "0xcbe1c834c7f1c954ccad3e613f440fed6732dccc0a8786dac8d831752d5613f3", - "sourceCodeHash": "0x59352159a7c46f8cf9a408b20f90e6802ad78ee65bbc215cb825ecfef7beebc1" + "initCodeHash": "0x10aad63177f6592bcd3d42d7ebd7d784c01e518f5189b8a543fc096ab9be69a0", + "sourceCodeHash": "0x4ca87ab2c6c2356c329587f5b69fac3ddc2fc76e389e81faea1a616a3d841403" }, "src/cannon/PreimageOracle.sol:PreimageOracle": { "initCodeHash": "0x6af5b0e83b455aab8d0946c160a4dc049a4e03be69f8a2a9e87b574f27b25a66", diff --git a/packages/contracts-bedrock/src/cannon/MIPS64.sol b/packages/contracts-bedrock/src/cannon/MIPS64.sol index 13739f3fb6343..734b257dc3f36 100644 --- a/packages/contracts-bedrock/src/cannon/MIPS64.sol +++ b/packages/contracts-bedrock/src/cannon/MIPS64.sol @@ -66,8 +66,8 @@ contract MIPS64 is ISemver { } /// @notice The semantic version of the MIPS64 contract. - /// @custom:semver 1.2.1 - string public constant version = "1.2.1"; + /// @custom:semver 1.3.0 + string public constant version = "1.3.0"; /// @notice The preimage oracle contract. IPreimageOracle internal immutable ORACLE; @@ -92,8 +92,8 @@ contract MIPS64 is ISemver { /// @param _oracle The address of the preimage oracle contract. constructor(IPreimageOracle _oracle, uint256 _stateVersion) { - // Supports VersionMultiThreaded64_v3 (6) and VersionMultiThreaded64_v4 (7) - if (_stateVersion != 6 && _stateVersion != 7) { + // Supports VersionMultiThreaded64_v4 (7) + if (_stateVersion != 7) { revert UnsupportedStateVersion(); } ORACLE = _oracle; diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol deleted file mode 100644 index b7b61c260dad7..0000000000000 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol +++ /dev/null @@ -1,737 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; -import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; - -library MIPSInstructions { - uint32 internal constant OP_LOAD_LINKED = 0x30; - uint32 internal constant OP_STORE_CONDITIONAL = 0x38; - uint32 internal constant REG_RA = 31; - - struct CoreStepLogicParams { - /// @param opcode The opcode value parsed from insn_. - st.CpuScalars cpu; - /// @param registers The CPU registers. - uint32[32] registers; - /// @param memRoot The current merkle root of the memory. - bytes32 memRoot; - /// @param memProofOffset The offset in calldata specify where the memory merkle proof is located. - uint256 memProofOffset; - /// @param insn The current 32-bit instruction at the pc. - uint32 insn; - /// @param cpu The CPU scalar fields. - uint32 opcode; - /// @param fun The function value parsed from insn_. - uint32 fun; - } - - /// @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. - /// @return newMemRoot_ The updated merkle root of memory after any modifications, may be unchanged. - /// @return memUpdated_ True if memory was modified. - /// @return memAddr_ Holds the memory address that was updated if memUpdated_ is true. - function execMipsCoreStepLogic(CoreStepLogicParams memory _args) - internal - pure - returns (bytes32 newMemRoot_, bool memUpdated_, uint32 memAddr_) - { - unchecked { - newMemRoot_ = _args.memRoot; - memUpdated_ = false; - memAddr_ = 0; - - // j-type j/jal - if (_args.opcode == 2 || _args.opcode == 3) { - // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset - uint32 target = (_args.cpu.nextPC & 0xF0000000) | (_args.insn & 0x03FFFFFF) << 2; - handleJump(_args.cpu, _args.registers, _args.opcode == 2 ? 0 : 31, target); - return (newMemRoot_, memUpdated_, memAddr_); - } - - // register fetch - uint32 rs = 0; // source register 1 value - uint32 rt = 0; // source register 2 / temp value - uint32 rtReg = (_args.insn >> 16) & 0x1F; - - // R-type or I-type (stores rt) - rs = _args.registers[(_args.insn >> 21) & 0x1F]; - uint32 rdReg = rtReg; - - if (_args.opcode == 0 || _args.opcode == 0x1c) { - // R-type (stores rd) - rt = _args.registers[rtReg]; - rdReg = (_args.insn >> 11) & 0x1F; - } else if (_args.opcode < 0x20) { - // rt is SignExtImm - // don't sign extend for andi, ori, xori - if (_args.opcode == 0xC || _args.opcode == 0xD || _args.opcode == 0xe) { - // ZeroExtImm - rt = _args.insn & 0xFFFF; - } else { - // SignExtImm - rt = signExtend(_args.insn & 0xFFFF, 16); - } - } else if (_args.opcode >= 0x28 || _args.opcode == 0x22 || _args.opcode == 0x26) { - // store rt value with store - rt = _args.registers[rtReg]; - - // store actual rt with lwl and lwr - rdReg = rtReg; - } - - if ((_args.opcode >= 4 && _args.opcode < 8) || _args.opcode == 1) { - handleBranch({ - _cpu: _args.cpu, - _registers: _args.registers, - _opcode: _args.opcode, - _insn: _args.insn, - _rtReg: rtReg, - _rs: rs - }); - return (newMemRoot_, memUpdated_, memAddr_); - } - - uint32 storeAddr = 0xFF_FF_FF_FF; - // memory fetch (all I-type) - // we do the load for stores also - uint32 mem = 0; - if (_args.opcode >= 0x20) { - // M[R[rs]+SignExtImm] - rs += signExtend(_args.insn & 0xFFFF, 16); - uint32 addr = rs & 0xFFFFFFFC; - mem = MIPSMemory.readMem(_args.memRoot, addr, _args.memProofOffset); - if (_args.opcode >= 0x28) { - // 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(_args.insn, _args.opcode, _args.fun, rs, rt, mem) & 0xffFFffFF; - - if (_args.opcode == 0 && _args.fun >= 8 && _args.fun < 0x1c) { - if (_args.fun == 8 || _args.fun == 9) { - // jr/jalr - handleJump(_args.cpu, _args.registers, _args.fun == 8 ? 0 : rdReg, rs); - return (newMemRoot_, memUpdated_, memAddr_); - } - - if (_args.fun == 0xa) { - // movz - handleRd(_args.cpu, _args.registers, rdReg, rs, rt == 0); - return (newMemRoot_, memUpdated_, memAddr_); - } - if (_args.fun == 0xb) { - // movn - handleRd(_args.cpu, _args.registers, rdReg, rs, rt != 0); - return (newMemRoot_, memUpdated_, memAddr_); - } - - // lo and hi registers - // can write back - if (_args.fun >= 0x10 && _args.fun < 0x1c) { - handleHiLo({ - _cpu: _args.cpu, - _registers: _args.registers, - _fun: _args.fun, - _rs: rs, - _rt: rt, - _storeReg: rdReg - }); - return (newMemRoot_, memUpdated_, memAddr_); - } - } - - // write memory - if (storeAddr != 0xFF_FF_FF_FF) { - newMemRoot_ = MIPSMemory.writeMem(storeAddr, _args.memProofOffset, val); - memUpdated_ = true; - memAddr_ = storeAddr; - } - - // write back the value to destination register - handleRd(_args.cpu, _args.registers, rdReg, val, true); - - return (newMemRoot_, memUpdated_, memAddr_); - } - } - - function signExtendImmediate(uint32 _insn) internal pure returns (uint32 offset_) { - unchecked { - return signExtend(_insn & 0xFFFF, 16); - } - } - - /// @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 selectSubWord(_rs, _mem, 1, true); - } - // lh - else if (_opcode == 0x21) { - return selectSubWord(_rs, _mem, 2, true); - } - // 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 selectSubWord(_rs, _mem, 4, true); - } - // lbu - else if (_opcode == 0x24) { - return selectSubWord(_rs, _mem, 1, false); - } - // lhu - else if (_opcode == 0x25) { - return selectSubWord(_rs, _mem, 2, false); - } - // 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) { - return updateSubWord(_rs, _mem, 1, _rt); - } - // sh - else if (_opcode == 0x29) { - return updateSubWord(_rs, _mem, 2, _rt); - } - // 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 updateSubWord(_rs, _mem, 4, _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; - } 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)) & 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; - } - // bltzal - if (rtv == 0x10) { - shouldBranch = int32(_rs) < 0; - _registers[31] = _cpu.pc + 8; // always set regardless of branch taken - } - if (rtv == 1) { - shouldBranch = int32(_rs) >= 0; - } - // bgezal (i.e. bal mnemonic) - if (rtv == 0x11) { - shouldBranch = int32(_rs) >= 0; - _registers[REG_RA] = _cpu.pc + 8; // always set regardless of branch taken - } - } - - // 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; - } - } - - /// @notice Selects a subword of byteLength size contained in memWord based on the low-order bits of vaddr - /// @param _vaddr The virtual address of the the subword. - /// @param _memWord The full word to select a subword from. - /// @param _byteLength The size of the subword. - /// @param _signExtend Whether to sign extend the selected subwrod. - function selectSubWord( - uint32 _vaddr, - uint32 _memWord, - uint32 _byteLength, - bool _signExtend - ) - internal - pure - returns (uint32 retval_) - { - (uint32 dataMask, uint32 bitOffset, uint32 bitLength) = calculateSubWordMaskAndOffset(_vaddr, _byteLength); - retval_ = (_memWord >> bitOffset) & dataMask; - if (_signExtend) { - retval_ = signExtend(retval_, bitLength); - } - return retval_; - } - - /// @notice Returns a word that has been updated by the specified subword at bit positions determined by the virtual - /// address - /// @param _vaddr The virtual address of the subword. - /// @param _memWord The full word to update. - /// @param _byteLength The size of the subword. - /// @param _value The subword that updates _memWord. - function updateSubWord( - uint32 _vaddr, - uint32 _memWord, - uint32 _byteLength, - uint32 _value - ) - internal - pure - returns (uint32 word_) - { - (uint32 dataMask, uint32 bitOffset,) = calculateSubWordMaskAndOffset(_vaddr, _byteLength); - uint32 subWordValue = dataMask & _value; - uint32 memUpdateMask = dataMask << bitOffset; - return subWordValue << bitOffset | (~memUpdateMask) & _memWord; - } - - function calculateSubWordMaskAndOffset( - uint32 _vaddr, - uint32 _byteLength - ) - internal - pure - returns (uint32 dataMask_, uint32 bitOffset_, uint32 bitLength_) - { - uint32 bitLength = _byteLength << 3; - uint32 dataMask = ~uint32(0) >> (32 - bitLength); - - // Figure out sub-word index based on the low-order bits in vaddr - uint32 byteIndexMask = _vaddr & 0x3 & ~(_byteLength - 1); - uint32 maxByteShift = 4 - _byteLength; - uint32 byteIndex = _vaddr & byteIndexMask; - uint32 bitOffset = (maxByteShift - byteIndex) << 3; - - return (dataMask, bitOffset, bitLength); - } -} diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol deleted file mode 100644 index 1d3942f705096..0000000000000 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol +++ /dev/null @@ -1,158 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { InvalidMemoryProof } from "src/cannon/libraries/CannonErrors.sol"; - -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_) { - bool valid; - (out_, valid) = readMemUnchecked(_memRoot, _addr, _proofOffset); - if (!valid) { - revert InvalidMemoryProof(); - } - } - - /// @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. - /// valid_ Whether the proof is valid. - function readMemUnchecked( - bytes32 _memRoot, - uint32 _addr, - uint256 _proofOffset - ) - internal - pure - returns (uint32 out_, bool valid_) - { - 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. - valid_ := eq(node, _memRoot) - if valid_ { - // 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 Verifies a memory proof. - /// @param _memRoot The expected memory root - /// @param _addr The _addr proven. - /// @param _proofOffset The offset of the memory proof in calldata. - /// @return valid_ True iff it is a valid proof. - function isValidProof(bytes32 _memRoot, uint32 _addr, uint256 _proofOffset) internal pure returns (bool valid_) { - (, valid_) = readMemUnchecked(_memRoot, _addr, _proofOffset); - } - - /// @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/contracts-bedrock/src/cannon/libraries/MIPSState.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSState.sol deleted file mode 100644 index b2982b5b16afd..0000000000000 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSState.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { InvalidExitedValue } from "src/cannon/libraries/CannonErrors.sol"; - -library MIPSState { - struct CpuScalars { - uint32 pc; - uint32 nextPC; - uint32 lo; - uint32 hi; - } - - function assertExitedIsValid(uint32 _exited) internal pure { - if (_exited > 1) { - revert InvalidExitedValue(); - } - } -} diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol deleted file mode 100644 index cf788a10793a5..0000000000000 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol +++ /dev/null @@ -1,430 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; -import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; -import { IPreimageOracle } from "interfaces/cannon/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; - } - - /// @custom:field _a0 The file descriptor. - /// @custom:field _a1 The memory address to read from. - /// @custom:field _a2 The number of bytes to read. - /// @custom:field _preimageKey The current preimaageKey. - /// @custom:field _preimageOffset The current preimageOffset. - /// @custom:field _proofOffset The offset of the memory proof in calldata. - /// @custom:field _memRoot The current memory root. - struct SysWriteParams { - uint32 _a0; - uint32 _a1; - uint32 _a2; - bytes32 _preimageKey; - uint32 _preimageOffset; - uint256 _proofOffset; - 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; - uint32 internal constant SYS_CLOCKGETTIME = 4263; - uint32 internal constant SYS_GETPID = 4020; - // unused syscalls - uint32 internal constant SYS_MUNMAP = 4091; - 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_STAT = 4106; - uint32 internal constant SYS_FSTAT = 4108; - 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; - uint32 internal constant SYS_GETRLIMIT = 4076; - uint32 internal constant SYS_LSEEK = 4019; - uint32 internal constant SYS_EVENTFD2 = 4325; - - // 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 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 SCHED_QUANTUM = 100_000; - uint32 internal constant HZ = 10_000_000; - uint32 internal constant CLOCK_GETTIME_REALTIME_FLAG = 0; - uint32 internal constant CLOCK_GETTIME_MONOTONIC_FLAG = 1; - /// @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; - - // FYI: https://en.wikibooks.org/wiki/MIPS_Assembly/Register_File - // https://refspecs.linuxfoundation.org/elf/mipsabi.pdf - uint32 internal constant REG_V0 = 2; - uint32 internal constant REG_A0 = 4; - uint32 internal constant REG_A1 = 5; - uint32 internal constant REG_A2 = 6; - uint32 internal constant REG_A3 = 7; - uint32 internal constant REG_SP = 29; - - // FYI: https://web.archive.org/web/20231223163047/https://www.linux-mips.org/wiki/Syscall - uint32 internal constant REG_SYSCALL_NUM = REG_V0; - uint32 internal constant REG_SYSCALL_ERRNO = REG_A3; - uint32 internal constant REG_SYSCALL_RET1 = REG_V0; - uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0; - uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1; - uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2; - - /// @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. - function getSyscallArgs(uint32[32] memory _registers) - internal - pure - returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_) - { - unchecked { - sysCallNum_ = _registers[REG_SYSCALL_NUM]; - - a0_ = _registers[REG_SYSCALL_PARAM1]; - a1_ = _registers[REG_SYSCALL_PARAM2]; - a2_ = _registers[REG_SYSCALL_PARAM3]; - - return (sysCallNum_, a0_, a1_, a2_); - } - } - - /// @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_, - bool memUpdated_, - uint32 memAddr_ - ) - { - unchecked { - v0_ = uint32(0); - v1_ = uint32(0); - newMemRoot_ = _args.memRoot; - newPreimageOffset_ = _args.preimageOffset; - memUpdated_ = false; - memAddr_ = 0; - - // 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) { - uint32 effAddr = _args.a1 & 0xFFffFFfc; - // verify proof is correct, and get the existing memory. - // mask the addr to align it to 4 bytes - uint32 mem = MIPSMemory.readMem(_args.memRoot, effAddr, _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(effAddr, _args.proofOffset, mem); - memUpdated_ = true; - memAddr_ = effAddr; - 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_, memUpdated_, memAddr_); - } - } - - /// @notice Like a Linux write syscall. Splits unaligned writes into aligned writes. - /// @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(SysWriteParams memory _args) - 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_ = _args._preimageKey; - newPreimageOffset_ = _args._preimageOffset; - - if (_args._a0 == FD_STDOUT || _args._a0 == FD_STDERR || _args._a0 == FD_HINT_WRITE) { - v0_ = _args._a2; // tell program we have written everything - } - // pre-image oracle - else if (_args._a0 == FD_PREIMAGE_WRITE) { - // mask the addr to align it to 4 bytes - uint32 mem = MIPSMemory.readMem(_args._memRoot, _args._a1 & 0xFFffFFfc, _args._proofOffset); - bytes32 key = _args._preimageKey; - - // Construct pre-image key from 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, _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 - } - _args._a2 = _a2; - - // Write pre-image key to oracle - newPreimageKey_ = key; - newPreimageOffset_ = 0; // reset offset, to read new pre-image data from the start - v0_ = _args._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 commands are F_GETFD and 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 == 1) { - // F_GETFD: get file descriptor flags - if ( - _a0 == FD_STDIN || _a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_PREIMAGE_READ - || _a0 == FD_HINT_READ || _a0 == FD_PREIMAGE_WRITE || _a0 == FD_HINT_WRITE - ) { - v0_ = 0; // No flags set - } else { - v0_ = 0xFFffFFff; - v1_ = EBADF; - } - } else if (_a1 == 3) { - // F_GETFL: get file status 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[REG_SYSCALL_RET1] = _v0; - _registers[REG_SYSCALL_ERRNO] = _v1; - - // Update the PC and nextPC - _cpu.pc = _cpu.nextPC; - _cpu.nextPC = _cpu.nextPC + 4; - } - } -} diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 458fc214732d1..4c60bcecbf85d 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -13,6 +13,7 @@ import { DeployOPChainInput } from "scripts/deploy/DeployOPChain.s.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Deploy } from "scripts/deploy/Deploy.s.sol"; import { Config } from "scripts/libraries/Config.sol"; +import { StandardConstants } from "scripts/deploy/StandardConstants.sol"; // Libraries import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Blueprint } from "src/libraries/Blueprint.sol"; @@ -949,7 +950,9 @@ contract OPContractsManager_TestInit is Test { }), mipsImpl: DeployUtils.create1({ _name: "MIPS64", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IMIPS2.__constructor__, (oracle, 6))) + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IMIPS2.__constructor__, (oracle, StandardConstants.MIPS_VERSION)) + ) }) }); diff --git a/packages/contracts-bedrock/test/cannon/MIPS64.t.sol b/packages/contracts-bedrock/test/cannon/MIPS64.t.sol new file mode 100644 index 0000000000000..0f7d33a46857a --- /dev/null +++ b/packages/contracts-bedrock/test/cannon/MIPS64.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Test } from "forge-std/Test.sol"; + +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; +import { UnsupportedStateVersion } from "src/cannon/libraries/CannonErrors.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; +import { IMIPS2 } from "interfaces/cannon/IMIPS2.sol"; + +contract MIPS64_Test is Test { + IPreimageOracle oracle; + + // Store some data about acceptable versions + uint256[1] validVersions = [7]; + mapping(uint256 => bool) public isValidVersion; + uint256 maxValidVersion; + + /// @notice Sets up the testing suite. + function setUp() public virtual { + oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ); + vm.label(address(oracle), "PreimageOracle"); + + // Store some metadata about versions + for (uint256 i = 0; i < validVersions.length; i++) { + uint256 validVersion = validVersions[i]; + isValidVersion[validVersion] = true; + if (validVersion > maxValidVersion) { + maxValidVersion = validVersion; + } + } + } + + /// @notice Test the we can deploy MIPS64 with a valid version parameter. + function test_deploy_supportedVersions_succeeds() external { + for (uint256 i = 0; i < validVersions.length; i++) { + uint256 version = validVersions[i]; + IMIPS2 mips = deployVm(version); + assertNotEq(address(mips), address(0)); + } + } + + /// @notice Test that deploying MIPS64 with an invalid version reverts with expected error. + function test_deploy_unsupportedVersions_fails() external { + for (uint256 ver = 0; ver <= maxValidVersion + 2; ver++) { + if (isValidVersion[ver]) { + continue; + } + + vm.expectRevert(abi.encodeWithSelector(UnsupportedStateVersion.selector)); + deployVm(ver); + } + } + + /// @notice Deploys new MIPS64 contract with the given version parameter. + function deployVm(uint256 version) internal returns (IMIPS2) { + return IMIPS2( + DeployUtils.create1({ + _name: "MIPS64", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IMIPS2.__constructor__, (oracle, version))) + }) + ); + } +}