diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0d7e8b79..780d174d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,9 +41,16 @@ jobs: uses: actions/setup-go@v5 with: go-version: '1.22.x' + - name: Install solc + run: | + sudo apt-get update + sudo apt-get install -y python3 python3-pip + pip3 install solc-select + solc-select install 0.8.15 + solc-select use 0.8.15 - name: Build FFI - run: go build - working-directory: rvgo/scripts/go-ffi + run: make build-ffi && make build-parse-diff-ffi + working-directory: rvgo - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - name: Run foundry tests diff --git a/.gitignore b/.gitignore index d5be4f6b..cc5345fd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ bin rvgo/bin rvgo/scripts/go-ffi/go-ffi +rvgo/scripts/parse-diff-ffi/slow rvgo/test/testdata rvsol/cache diff --git a/Makefile b/Makefile index 81e739c8..f7611385 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,11 @@ fuzz-mac: fuzz \ fuzz-mac +fuzz-ffi: build + make -C ./rvgo build-parse-diff-ffi + make -C ./rvsol fuzz-ffi +.PHONY: fuzz-ffi + OP_PROGRAM_PATH ?= $(MONOREPO_ROOT)/op-program/bin-riscv/op-program-client-riscv.elf prestate: build-rvgo op-program-riscv diff --git a/rvgo/Makefile b/rvgo/Makefile index eb8825dc..4b15432f 100644 --- a/rvgo/Makefile +++ b/rvgo/Makefile @@ -16,6 +16,10 @@ build-ffi: env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./scripts/go-ffi/go-ffi ./scripts/go-ffi .PHONY: build-ffi +build-parse-diff-ffi: + env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./scripts/parse-diff-ffi/slow ./scripts/parse-diff-ffi +.PHONY: build-parse-diff-ffi + clean: rm -rf ./bin rm ./scripts/go-ffi/go-ffi diff --git a/rvgo/scripts/parse-diff-ffi/parse_slow.go b/rvgo/scripts/parse-diff-ffi/parse_slow.go new file mode 100644 index 00000000..abefdf5e --- /dev/null +++ b/rvgo/scripts/parse-diff-ffi/parse_slow.go @@ -0,0 +1,95 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/holiman/uint256" + + "github.com/ethereum-optimism/asterisc/rvgo/slow" +) + +func main() { + function := flag.String("fuzz", "ParseTypeI", "fuzz function") + input := flag.Int64("number", 0, "input to parse") + flag.Parse() + + number := *input + + //u256_input := slow.ShortToU256(uint16(number)) + + toU64 := slow.U64(*uint256.NewInt(uint64(number))) + + switch *function { + case "ParseTypeI": + { + resultU64 := slow.ParseImmTypeI(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseTypeS": + { + resultU64 := slow.ParseImmTypeS(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseTypeB": + { + resultU64 := slow.ParseImmTypeB(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseTypeU": + { + resultU64 := slow.ParseImmTypeU(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseTypeJ": + { + resultU64 := slow.ParseImmTypeJ(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseOpcode": + { + resultU64 := slow.ParseOpcode(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseRd": + { + resultU64 := slow.ParseRd(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseFunct3": + { + resultU64 := slow.ParseFunct3(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseRs1": + { + resultU64 := slow.ParseRs1(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseRs2": + { + resultU64 := slow.ParseRs2(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + case "ParseFunct7": + { + resultU64 := slow.ParseFunct7(toU64) // type should be U64 + h := fmt.Sprintf("%064x", slow.Val(resultU64)) + fmt.Print(h) + } + default: + { + panic("unknown input") + } + } +} diff --git a/rvgo/slow/slow-parse.go b/rvgo/slow/slow-parse.go new file mode 100644 index 00000000..24c5af1a --- /dev/null +++ b/rvgo/slow/slow-parse.go @@ -0,0 +1,49 @@ +package slow + +func ParseImmTypeI(instr U64) U64 { + return parseImmTypeI(instr) +} + +func ParseImmTypeS(instr U64) U64 { + return parseImmTypeS(instr) +} + +func ParseImmTypeB(instr U64) U64 { + return parseImmTypeB(instr) +} + +func ParseImmTypeU(instr U64) U64 { + return parseImmTypeU(instr) +} + +func ParseImmTypeJ(instr U64) U64 { + return parseImmTypeJ(instr) +} + +func ParseOpcode(instr U64) U64 { + return parseOpcode(instr) +} + +func ParseRd(instr U64) U64 { + return parseRd(instr) +} + +func ParseFunct3(instr U64) U64 { + return parseFunct3(instr) +} + +func ParseRs1(instr U64) U64 { + return parseRs1(instr) +} + +func ParseRs2(instr U64) U64 { + return parseRs2(instr) +} + +func ParseFunct7(instr U64) U64 { + return parseFunct7(instr) +} + +func Val(v U64) uint64 { + return v.val() +} diff --git a/rvsol/Makefile b/rvsol/Makefile index a9b107eb..b084c891 100644 --- a/rvsol/Makefile +++ b/rvsol/Makefile @@ -11,9 +11,13 @@ test: .PHONY: test lint-fix: - forge fmt + forge fmt ./**/*.sol .PHONY: lint-fix lint-check: - forge fmt && git diff --exit-code + forge fmt ./**/*.sol && git diff --exit-code .PHONY: lint-check + +fuzz-ffi: + forge test --match-path ./test/Yul64Test.sol +.PHONY: fuzz-ffi diff --git a/rvsol/src/YulDeployer.sol b/rvsol/src/YulDeployer.sol new file mode 100644 index 00000000..a4483b9e --- /dev/null +++ b/rvsol/src/YulDeployer.sol @@ -0,0 +1,33 @@ +pragma solidity 0.8.15; + +import "forge-std/Test.sol"; + +contract YulDeployer is Test { + /** + * @notice Deploys a Yul contract and returns the address where the contract was deployed + * @param fileName - The file name of the Yul contract (e.g., "Example.yul" becomes "Example") + * @return deployedAddress - The address where the contract was deployed + */ + function deployContract(string memory fileName) public returns (address) { + string memory bashCommand = string.concat( + 'cast abi-encode "f(bytes)" $(solc --strict-assembly ./src/yul/', + string.concat(fileName, ".yul --bin | grep '^[0-9a-fA-Z]*$')") + ); + + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[2] = bashCommand; + + bytes memory bytecode = abi.decode(vm.ffi(inputs), (bytes)); + + address deployedAddress; + assembly { + deployedAddress := create(0, add(bytecode, 0x20), mload(bytecode)) + } + + require(deployedAddress != address(0), "YulDeployer could not deploy contract"); + + return deployedAddress; + } +} diff --git a/rvsol/src/yul/Yul64.yul b/rvsol/src/yul/Yul64.yul new file mode 100644 index 00000000..09948e1a --- /dev/null +++ b/rvsol/src/yul/Yul64.yul @@ -0,0 +1,310 @@ +object "RISCV" { + code { + // Deploy the contract + datacopy(0, dataoffset("runtime"), datasize("runtime")) + return(0, datasize("runtime")) + } + object "runtime" { + code { + // Dispatcher + switch selector() + case 0xb00c8ce3 /* parseImmTypeI(uint64)->uint64*/ { + returnUint(parseImmTypeI(toU64(decodeAsUint64(0)))) + } + case 0xbf5b9caa /* parseImmTypeS(uint64)->uint256*/ { + returnUint(parseImmTypeS(decodeAsUint64(0))) + } + case 0x40233f6d /* parseImmTypeB(uint64)->uint256*/{ + returnUint(parseImmTypeB(decodeAsUint64(0))) + } + case 0x9039dd19 /* parseImmTypeU(uint64)->uint256*/ { + returnUint(parseImmTypeU(decodeAsUint64(0))) + } + case 0x6933e90c /* parseImmTypeJ(uint64)->uint256*/ { + returnUint(parseImmTypeJ(decodeAsUint64(0))) + } + case 0xb488c140 /* ParseOpcode(uint64)returns(uint256)" */ { + returnUint(parseOpcode(decodeAsUint64(0))) + } + case 0x2c8fcf96 /* ParseRd(uint64)returns(uint256) */ { + returnUint(parseRd(decodeAsUint64(0))) + } + case 0x0596de79 /* ParseFunct3(uint64)returns(uint256) */ { + returnUint(parseFunct3(decodeAsUint64(0))) + } + case 0xa2494672 /* ParseRs1(uint64)returns(uint256)*/ { + returnUint(parseRs1(decodeAsUint64(0))) + } + case 0xb3bc5703 /* ParseRs2(uint64)returns(uint256) */ { + returnUint(parseRs2(decodeAsUint64(0))) + } + case 0xf80141d6 /* ParseFunct7(uint64)returns(uint256) */ { + returnUint(parseFunct7(decodeAsUint64(0))) + } + default { + revert(0, 0) + } + + + /* ---------- calldata decoding functions ----------- */ + function selector() -> s { + s := div(calldataload(0), 0x100000000000000000000000000000000000000000000000000000000) + } + + function decodeAsUint64(offset) -> v { + let pos := add(4, mul(offset, 0x8)) // We use 0x8 instead of 0x20 since uint64 is 8 bytes + if lt(calldatasize(), add(pos, 0x8)) { // Check if the calldata size is sufficient for 8 bytes + revert(0, 0) + } + v := calldataload(pos) // Load 8 bytes (uint64) + v := and(v, 0xFFFFFFFFFFFFFFFF) // Mask to ensure we only get the lower 8 bytes (uint64) + } + /* ---------- calldata encoding functions ---------- */ + function returnUint(v) { + mstore(0, v) + return(0, 0x20) + } + + /* ---------- Parse - functions to parse RISC-V instructions ---------- */ + /* ---------- maps to parse.go in golang ---------- */ + function parseImmTypeI(instr) -> out { + out := signExtend64(shr64(toU64(20), instr), toU64(11)) + } + + function parseImmTypeS(instr) -> out { + out := + signExtend64( + or64(shl64(toU64(5), shr64(toU64(25), instr)), and64(shr64(toU64(7), instr), toU64(0x1F))), + toU64(11) + ) + } + + function parseImmTypeB(instr) -> out { + out := + signExtend64( + or64( + or64( + shl64(toU64(1), and64(shr64(toU64(8), instr), toU64(0xF))), + shl64(toU64(5), and64(shr64(toU64(25), instr), toU64(0x3F))) + ), + or64( + shl64(toU64(11), and64(shr64(toU64(7), instr), toU64(1))), + shl64(toU64(12), shr64(toU64(31), instr)) + ) + ), + toU64(12) + ) + } + + function parseImmTypeU(instr) -> out { + out := signExtend64(shr64(toU64(12), instr), toU64(19)) + } + + function parseImmTypeJ(instr) -> out { + out := + signExtend64( + or64( + or64( + and64(shr64(toU64(21), instr), shortToU64(0x3FF)), // 10 bits for index 0:9 + shl64(toU64(10), and64(shr64(toU64(20), instr), toU64(1))) // 1 bit for index 10 + ), + or64( + shl64(toU64(11), and64(shr64(toU64(12), instr), toU64(0xFF))), // 8 bits for index 11:18 + shl64(toU64(19), shr64(toU64(31), instr)) // 1 bit for index 19 + ) + ), + toU64(19) + ) + } + + function parseOpcode(instr) -> out { + out := and64(instr, toU64(0x7F)) + } + + function parseRd(instr) -> out { + out := and64(shr64(toU64(7), instr), toU64(0x1F)) + } + + function parseFunct3(instr) -> out { + out := and64(shr64(toU64(12), instr), toU64(0x7)) + } + + function parseRs1(instr) -> out { + out := and64(shr64(toU64(15), instr), toU64(0x1F)) + } + + function parseRs2(instr) -> out { + out := and64(shr64(toU64(20), instr), toU64(0x1F)) + } + + function parseFunct7(instr) -> out { + out := shr64(toU64(25), instr) + } + + /* ---------- Yul64 - functions to implement yul ---------- */ + /* ---------- maps to yul64.go in golang ---------- */ + function u64Mask() -> out { + // max uint64 + out := shr(192, not(0)) // 256-64 = 192 + } + + function u32Mask() -> out { + out := U64(shr(toU256(224), not(0))) // 256-32 = 224 + } + + function toU64(v) -> out { + out := v + } + + function shortToU64(v) -> out { + out := v + } + + function shortToU256(v) -> out { + out := v + } + + function longToU256(v) -> out { + out := v + } + + function u256ToU64(v) -> out { + out := and(v, U256(u64Mask())) + } + + function u64ToU256(v) -> out { + out := v + } + + function mask32Signed64(v) -> out { + out := signExtend64(and64(v, u32Mask()), toU64(31)) + } + + function u64Mod() -> out { + // 1 << 64 + out := shl(toU256(64), toU256(1)) + } + + function u64TopBit() -> out { + // 1 << 63 + out := shl(toU256(63), toU256(1)) + } + + function signExtend64(v, bit) -> out { + switch and(v, shl(bit, 1)) + case 0 { + // fill with zeroes, by masking + out := U64(and(U256(v), shr(sub(toU256(63), bit), U256(u64Mask())))) + } + default { + // fill with ones, by or-ing + out := U64(or(U256(v), shl(bit, shr(bit, U256(u64Mask()))))) + } + } + + function signExtend64To256(v) -> out { + switch and(U256(v), u64TopBit()) + case 0 { out := v } + default { out := or(shl(toU256(64), not(0)), v) } + } + + function add64(x, y) -> out { + out := U64(mod(add(U256(x), U256(y)), u64Mod())) + } + + function sub64(x, y) -> out { + out := U64(mod(sub(U256(x), U256(y)), u64Mod())) + } + + function mul64(x, y) -> out { + out := u256ToU64(mul(U256(x), U256(y))) + } + + function div64(x, y) -> out { + out := u256ToU64(div(U256(x), U256(y))) + } + + function sdiv64(x, y) -> out { + // note: signed overflow semantics are the same between Go and EVM assembly + out := u256ToU64(sdiv(signExtend64To256(x), signExtend64To256(y))) + } + + function mod64(x, y) -> out { + out := U64(mod(U256(x), U256(y))) + } + + function smod64(x, y) -> out { + out := u256ToU64(smod(signExtend64To256(x), signExtend64To256(y))) + } + + function not64(x) -> out { + out := u256ToU64(not(U256(x))) + } + + function lt64(x, y) -> out { + out := U64(lt(U256(x), U256(y))) + } + + function gt64(x, y) -> out { + out := U64(gt(U256(x), U256(y))) + } + + function slt64(x, y) -> out { + out := U64(slt(signExtend64To256(x), signExtend64To256(y))) + } + + function sgt64(x, y) -> out { + out := U64(sgt(signExtend64To256(x), signExtend64To256(y))) + } + + function eq64(x, y) -> out { + out := U64(eq(U256(x), U256(y))) + } + + function iszero64(x) -> out { + out := iszero(U256(x)) + } + + function and64(x, y) -> out { + out := U64(and(U256(x), U256(y))) + } + + function or64(x, y) -> out { + out := U64(or(U256(x), U256(y))) + } + + function xor64(x, y) -> out { + out := U64(xor(U256(x), U256(y))) + } + + function shl64(x, y) -> out { + out := u256ToU64(shl(U256(x), U256(y))) + } + + function shr64(x, y) -> out { + out := U64(shr(U256(x), U256(y))) + } + + function sar64(x, y) -> out { + out := u256ToU64(sar(U256(x), signExtend64To256(y))) + } + + // type casts, no-op in yul + function b32asBEWord(v) -> out { + out := v + } + function beWordAsB32(v) -> out { + out := v + } + function U64(v) -> out { + out := v + } + function U256(v) -> out { + out := v + } + function toU256(v) -> out { + out := v + } + } + } +} \ No newline at end of file diff --git a/rvsol/test/ForgeDifferentialParsingTests.md b/rvsol/test/ForgeDifferentialParsingTests.md new file mode 100644 index 00000000..0b59b57f --- /dev/null +++ b/rvsol/test/ForgeDifferentialParsingTests.md @@ -0,0 +1,61 @@ +# Rvsol Diff Tests + +To run diff tests directly: + +```bash +make fuzz-ffi +``` + +or + +```bash +cd rvsol +forge test --match-path ./test/Yul64Test.sol -vvvvv +``` + +`slow` should be built into the `./rvgo/scripts/parse-diff/ffi` directory. If changes here are necessary, ensure that `diff.go` print with no new lines, else Foundry will interpret the ffi result differently. A `/// @dev` note regarding this has been added to the `generateCommand` function in `Yul64Test.sol`. + +## Setup + +solc 0.8.15 is required to run forge diff parsing tests. + +You can use solc-select to download solc with the following script. +```bash + # Create a python virtual environment + python3 -m venv path/to/venv + source path/to/venv/bin/activate + # Install solc-select + python3 -m pip install solc-select + # Install solc 0.8.15 + solc-select install 0.8.15 + # Set 0.8.15 as version in use + solc-select use 0.8.15 +``` + +# Structure + +## `Yul64Test.sol` + +This is the entrypoint to implementing differential testing between golang and EVM. This contract is in charge of: +- setting up the contract calls +- defining the fuzz functions +- calling EVM implementations +- calling golang implementations +- asserting that the two implementations behave similarly + +If one implementation reverts and the other is successful, or if the result from golang does not match that of Solidity, these assertions will throw an error. + +## `Yul64.yul` + +This is the `yul` contract defined to be able to invoke parsing functions directly. The yul contract has the following properties: +- The selectors match those defined in `RISCV.sol` +- Functions consistently take `uint64` as input, and return `uint256` +- Encoding function is used to return `uint256` +- Decoding function is used to decode input as `uint64` +- If selector does not match expected parse function, Solidity reverts + +## `slow` + +This is the output from `rvgo/scripts/parse-diff-ffi` - which also allows us to call the golang functions directly for parsing functions. + +One notable difference between this and Solidity is the input – golang **only** provides a `toU64` function to convert a number into the representation required to invoke the `ParseX` functions - but this needs to truncate, unlike Solidity. This is likely the cause of a few differences that can be seen while running the tests. diff --git a/rvsol/test/Yul64Test.sol b/rvsol/test/Yul64Test.sol new file mode 100644 index 00000000..517288b3 --- /dev/null +++ b/rvsol/test/Yul64Test.sol @@ -0,0 +1,186 @@ +pragma solidity 0.8.15; + +import "forge-std/Test.sol"; +import "../src/YulDeployer.sol"; +import { console } from "forge-std/console.sol"; + +interface Yul64 { } + +// Run with: forge test --match-path ./test/Yul64Test.sol -vvvvv +contract Yul64Test is Test { + YulDeployer yulDeployer = new YulDeployer(); + Yul64Test yul64; + mapping(string => string) public slowToEVM; + string[11] slowFunctions; + + /// @notice deploys yul wrapper and sets up string for slow (golang) and mapping for solidity + function setUp() public { + yul64 = Yul64Test(yulDeployer.deployContract("Yul64")); + + slowFunctions[0] = "ParseTypeI"; + slowToEVM[slowFunctions[0]] = "parseImmTypeI(uint64)"; + + slowFunctions[1] = "ParseTypeS"; + slowToEVM[slowFunctions[1]] = "parseImmTypeS(uint64)"; + + slowFunctions[2] = "ParseTypeB"; + slowToEVM[slowFunctions[2]] = "parseImmTypeB(uint64)"; + + slowFunctions[3] = "ParseTypeU"; + slowToEVM[slowFunctions[3]] = "parseImmTypeU(uint64)"; + + slowFunctions[4] = "ParseTypeJ"; + slowToEVM[slowFunctions[4]] = "parseImmTypeJ(uint64)"; + + slowFunctions[5] = "ParseOpcode"; + slowToEVM[slowFunctions[5]] = "parseOpcode(uint64)"; + + slowFunctions[6] = "ParseRd"; + slowToEVM[slowFunctions[6]] = "parseRd(uint64)"; + + slowFunctions[7] = "ParseFunct3"; + slowToEVM[slowFunctions[7]] = "parseFunct3(uint64)"; + + slowFunctions[8] = "ParseRs1"; + slowToEVM[slowFunctions[8]] = "parseRs1(uint64)"; + + slowFunctions[9] = "ParseRs2"; + slowToEVM[slowFunctions[9]] = "parseRs2(uint64)"; + + slowFunctions[10] = "ParseFunct7"; + slowToEVM[slowFunctions[10]] = "parseFunct7(uint64)"; + } + + function testFuzz_parseTypeI(uint32 input) public { + runDiffTest(slowFunctions[0], input); + } + + function testFuzz_parseTypeS(uint32 input) public { + runDiffTest(slowFunctions[1], input); + } + + function testFuzz_parseTypeB(uint32 input) public { + runDiffTest(slowFunctions[2], input); + } + + function testFuzz_parseTypeU(uint32 input) public { + runDiffTest(slowFunctions[3], input); + } + + function testFuzz_parseTypeJ(uint32 input) public { + runDiffTest(slowFunctions[4], input); + } + + function testFuzz_parseOpcode(uint32 input) public { + runDiffTest(slowFunctions[5], input); + } + + function testFuzz_parseRd(uint32 input) public { + runDiffTest(slowFunctions[6], input); + } + + function testFuzz_parseFunct3(uint32 input) public { + runDiffTest(slowFunctions[7], input); + } + + function testFuzz_parseRs1(uint32 input) public { + runDiffTest(slowFunctions[8], input); + } + + function testFuzz_parseRs2(uint32 input) public { + runDiffTest(slowFunctions[9], input); + } + + function testFuzz_parseFunct7(uint32 input) public { + runDiffTest(slowFunctions[10], input); + } + + // Helper functions + + /// @notice Executes evm code then ffi code and checks consistent behavior + /// @dev Used by all testFuzz_ functions given funcToCall + /// @param funcToCall Defines function under test + /// @param input Defines uint32 input to represent instruction + function runDiffTest(string memory funcToCall, uint64 input) private { + (bool evmSuccess, bytes memory evmOutput) = executeEVM(funcToCall, input); + bytes memory ffiOutput = executeFFI(funcToCall, input); + assertConsistent(evmSuccess, evmOutput, ffiOutput); + } + + /// @notice Generates calldata given signature and input, and calls yul64 wrapper + /// @dev Helper function is used by all testFuzz_functions in runDiffTest + /// @param funcToCall key to slowToEVM mapping to define which function to test + /// @param input Defines uint32 input to represent instruction + function executeEVM( + string memory funcToCall, + uint64 input + ) + private + returns (bool evmSuccess, bytes memory evmOutput) + { + // generate calldata for EVM call + bytes memory callDataBytes = abi.encodeWithSignature(slowToEVM[funcToCall], input); + (evmSuccess, evmOutput) = address(yul64).call(callDataBytes); + } + /// @notice Generates ffi command to invoke ./rvsol/test/slow with arguments + /// @dev Required to build diff.go prior to use + /// @param funcToCall key to slowToEVM mapping to define which function to test + /// @param input Defines uint32 input to represent instruction + + function executeFFI(string memory funcToCall, uint64 input) private returns (bytes memory ffiOutput) { + string[] memory inputs = generateCommand(funcToCall, input); + ffiOutput = vm.ffi(inputs); + } + + /// @notice Asserts false if evm and ffi results are unaligned + /// @param evmSuccess True if EVM call was successful (did not revert) + /// @param evmOutput Result of EVM parse function + /// @param ffiOutput Result of slow parse function + function assertConsistent(bool evmSuccess, bytes memory evmOutput, bytes memory ffiOutput) private pure { + console.logBool(didPanic(ffiOutput)); + if (didPanic(ffiOutput)) { + // if slow vm determined there was an invalid value + console.logString("SLOW VM // invalid value"); + if (!evmSuccess) { + // and so did EVM, consider non-deviating behaviour + console.logString("EVM // expected revert"); + } else { + // EVM should have been successful + console.logString("! EVM FAIL ! // should have failed"); + assert(false); + } + } else { + console.logString("SLOW VM // successful"); + if (!evmSuccess) { + console.log("! EVM FAIL ! // EVM failed"); + assert(false); + } else { + assertEq(ffiOutput, evmOutput); + } + } + } + + /// @notice Generates ffi command to run slow. + /// @dev This requires slow to print nothing else when run except result. If changed, ensure script does not print + /// with a newline + /// @param functionToCall Defines function to call with ffi + /// @param numberInput Specifies input from fuzzer to call with + /// @return inputs list of strings with the full bash command to run ffi + function generateCommand(string memory functionToCall, uint64 numberInput) private pure returns (string[] memory) { + string memory bashCommand = string.concat( + "../rvgo/scripts/parse-diff-ffi/slow -fuzz=", functionToCall, " -number=", (vm.toString(numberInput)) + ); + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[2] = bashCommand; + return inputs; + } + + /// @notice Returns true if didPanic panic string + function didPanic(bytes memory whatBytes) private pure returns (bool found) { + string memory err = + hex"70616E69633A20696E76616C69642076616C75650A0A676F726F7574696E652031205B72756E6E696E675D3A0A6D61696E2E6D61696E28290A"; + return keccak256(abi.encodePacked(string(whatBytes))) == keccak256(abi.encodePacked(err)); + } +}