Commit 17281374 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

Merge pull request #5114 from ethereum-optimism/clabby/ctb/speedy-diff-testing

feat(ctb): Optimize differential testing
parents 6a429a39 26868d3a
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
......@@ -114,7 +115,16 @@ func (n *nodeAPI) OutputAtBlock(ctx context.Context, number hexutil.Uint64) (*et
}
var l2OutputRootVersion eth.Bytes32 // it's zero for now
l2OutputRoot := rollup.ComputeL2OutputRoot(l2OutputRootVersion, head.Hash(), head.Root(), proof.StorageHash)
l2OutputRoot, err := rollup.ComputeL2OutputRoot(&bindings.TypesOutputRootProof{
Version: l2OutputRootVersion,
StateRoot: head.Root(),
MessagePasserStorageRoot: proof.StorageHash,
LatestBlockhash: head.Hash(),
})
if err != nil {
n.log.Error("Error computing L2 output root, nil ptr passed to hashing function")
return nil, err
}
return &eth.OutputResponse{
Version: l2OutputRootVersion,
......
package rollup
import (
"errors"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// ComputeL2OutputRoot computes the L2 output root
func ComputeL2OutputRoot(l2OutputRootVersion eth.Bytes32, blockHash common.Hash, blockRoot common.Hash, storageRoot common.Hash) eth.Bytes32 {
digest := crypto.Keccak256Hash(
l2OutputRootVersion[:],
blockRoot.Bytes(),
storageRoot[:],
blockHash.Bytes(),
)
return eth.Bytes32(digest)
}
var NilProof = errors.New("Output root proof is nil")
// ComputeL2OutputRoot computes the L2 output root by hashing an output root proof.
func ComputeL2OutputRoot(proofElements *bindings.TypesOutputRootProof) (eth.Bytes32, error) {
if proofElements == nil {
return eth.Bytes32{}, NilProof
}
// HashOutputRootProof computes the hash of the output root proof
func HashOutputRootProof(proof *bindings.TypesOutputRootProof) eth.Bytes32 {
return ComputeL2OutputRoot(
proof.Version,
proof.StateRoot,
proof.MessagePasserStorageRoot,
proof.LatestBlockhash,
digest := crypto.Keccak256Hash(
proofElements.Version[:],
proofElements.StateRoot[:],
proofElements.MessagePasserStorageRoot[:],
proofElements.LatestBlockhash[:],
)
return eth.Bytes32(digest), nil
}
......@@ -266,9 +266,9 @@ OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutp
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() (gas: 207520)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() (gas: 41753)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() (gas: 199464)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() (gas: 206360)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() (gas: 205818)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() (gas: 180229)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReentrancy_reverts() (gas: 244377)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReentrancy_reverts() (gas: 243835)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReplay_reverts() (gas: 245528)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_paused_reverts() (gas: 53555)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_provenWithdrawalHash_succeeds() (gas: 234941)
......
......@@ -12,3 +12,4 @@ deployments/mainnet-forked
deploy-config/mainnet-forked.json
test-case-generator/fuzz
.resource-metering.csv
scripts/differential-testing/differential-testing
......@@ -477,16 +477,15 @@ contract FFIInterface is Test {
bytes[] memory
)
{
string[] memory cmds = new string[](9);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "getProveWithdrawalTransactionInputs";
cmds[3] = vm.toString(_tx.nonce);
cmds[4] = vm.toString(_tx.sender);
cmds[5] = vm.toString(_tx.target);
cmds[6] = vm.toString(_tx.value);
cmds[7] = vm.toString(_tx.gasLimit);
cmds[8] = vm.toString(_tx.data);
string[] memory cmds = new string[](8);
cmds[0] = "scripts/differential-testing/differential-testing";
cmds[1] = "getProveWithdrawalTransactionInputs";
cmds[2] = vm.toString(_tx.nonce);
cmds[3] = vm.toString(_tx.sender);
cmds[4] = vm.toString(_tx.target);
cmds[5] = vm.toString(_tx.value);
cmds[6] = vm.toString(_tx.gasLimit);
cmds[7] = vm.toString(_tx.data);
bytes memory result = vm.ffi(cmds);
(
......@@ -508,16 +507,15 @@ contract FFIInterface is Test {
uint256 _gasLimit,
bytes memory _data
) external returns (bytes32) {
string[] memory cmds = new string[](9);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "hashCrossDomainMessage";
cmds[3] = vm.toString(_nonce);
cmds[4] = vm.toString(_sender);
cmds[5] = vm.toString(_target);
cmds[6] = vm.toString(_value);
cmds[7] = vm.toString(_gasLimit);
cmds[8] = vm.toString(_data);
string[] memory cmds = new string[](8);
cmds[0] = "scripts/differential-testing/differential-testing";
cmds[1] = "hashCrossDomainMessage";
cmds[2] = vm.toString(_nonce);
cmds[3] = vm.toString(_sender);
cmds[4] = vm.toString(_target);
cmds[5] = vm.toString(_value);
cmds[6] = vm.toString(_gasLimit);
cmds[7] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
......@@ -531,16 +529,15 @@ contract FFIInterface is Test {
uint256 _gasLimit,
bytes memory _data
) external returns (bytes32) {
string[] memory cmds = new string[](9);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "hashWithdrawal";
cmds[3] = vm.toString(_nonce);
cmds[4] = vm.toString(_sender);
cmds[5] = vm.toString(_target);
cmds[6] = vm.toString(_value);
cmds[7] = vm.toString(_gasLimit);
cmds[8] = vm.toString(_data);
string[] memory cmds = new string[](8);
cmds[0] = "scripts/differential-testing/differential-testing";
cmds[1] = "hashWithdrawal";
cmds[2] = vm.toString(_nonce);
cmds[3] = vm.toString(_sender);
cmds[4] = vm.toString(_target);
cmds[5] = vm.toString(_value);
cmds[6] = vm.toString(_gasLimit);
cmds[7] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
......@@ -552,14 +549,13 @@ contract FFIInterface is Test {
bytes32 _messagePasserStorageRoot,
bytes32 _latestBlockhash
) external returns (bytes32) {
string[] memory cmds = new string[](7);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "hashOutputRootProof";
cmds[3] = Strings.toHexString(uint256(_version));
cmds[4] = Strings.toHexString(uint256(_stateRoot));
cmds[5] = Strings.toHexString(uint256(_messagePasserStorageRoot));
cmds[6] = Strings.toHexString(uint256(_latestBlockhash));
string[] memory cmds = new string[](6);
cmds[0] = "scripts/differential-testing/differential-testing";
cmds[1] = "hashOutputRootProof";
cmds[2] = Strings.toHexString(uint256(_version));
cmds[3] = Strings.toHexString(uint256(_stateRoot));
cmds[4] = Strings.toHexString(uint256(_messagePasserStorageRoot));
cmds[5] = Strings.toHexString(uint256(_latestBlockhash));
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
......@@ -572,20 +568,19 @@ contract FFIInterface is Test {
uint256 _value,
uint64 _gas,
bytes memory _data,
uint256 _logIndex
uint64 _logIndex
) external returns (bytes32) {
string[] memory cmds = new string[](11);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "hashDepositTransaction";
cmds[3] = "0x0000000000000000000000000000000000000000000000000000000000000000";
cmds[4] = vm.toString(_logIndex);
cmds[5] = vm.toString(_from);
cmds[6] = vm.toString(_to);
cmds[7] = vm.toString(_mint);
cmds[8] = vm.toString(_value);
cmds[9] = vm.toString(_gas);
cmds[10] = vm.toString(_data);
string[] memory cmds = new string[](10);
cmds[0] = "scripts/differential-testing/differential-testing";
cmds[1] = "hashDepositTransaction";
cmds[2] = "0x0000000000000000000000000000000000000000000000000000000000000000";
cmds[3] = vm.toString(_logIndex);
cmds[4] = vm.toString(_from);
cmds[5] = vm.toString(_to);
cmds[6] = vm.toString(_mint);
cmds[7] = vm.toString(_value);
cmds[8] = vm.toString(_gas);
cmds[9] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
......@@ -595,19 +590,18 @@ contract FFIInterface is Test {
external
returns (bytes memory)
{
string[] memory cmds = new string[](12);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "encodeDepositTransaction";
cmds[3] = vm.toString(txn.from);
cmds[4] = vm.toString(txn.to);
cmds[5] = vm.toString(txn.value);
cmds[6] = vm.toString(txn.mint);
cmds[7] = vm.toString(txn.gasLimit);
cmds[8] = vm.toString(txn.isCreation);
cmds[9] = vm.toString(txn.data);
cmds[10] = vm.toString(txn.l1BlockHash);
cmds[11] = vm.toString(txn.logIndex);
string[] memory cmds = new string[](11);
cmds[0] = "scripts/differential-testing/differential-testing";
cmds[1] = "encodeDepositTransaction";
cmds[2] = vm.toString(txn.from);
cmds[3] = vm.toString(txn.to);
cmds[4] = vm.toString(txn.value);
cmds[5] = vm.toString(txn.mint);
cmds[6] = vm.toString(txn.gasLimit);
cmds[7] = vm.toString(txn.isCreation);
cmds[8] = vm.toString(txn.data);
cmds[9] = vm.toString(txn.l1BlockHash);
cmds[10] = vm.toString(txn.logIndex);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes));
......@@ -621,27 +615,25 @@ contract FFIInterface is Test {
uint256 _gasLimit,
bytes memory _data
) external returns (bytes memory) {
string[] memory cmds = new string[](9);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "encodeCrossDomainMessage";
cmds[3] = vm.toString(_nonce);
cmds[4] = vm.toString(_sender);
cmds[5] = vm.toString(_target);
cmds[6] = vm.toString(_value);
cmds[7] = vm.toString(_gasLimit);
cmds[8] = vm.toString(_data);
string[] memory cmds = new string[](8);
cmds[0] = "scripts/differential-testing/differential-testing";
cmds[1] = "encodeCrossDomainMessage";
cmds[2] = vm.toString(_nonce);
cmds[3] = vm.toString(_sender);
cmds[4] = vm.toString(_target);
cmds[5] = vm.toString(_value);
cmds[6] = vm.toString(_gasLimit);
cmds[7] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes));
}
function decodeVersionedNonce(uint256 nonce) external returns (uint256, uint256) {
string[] memory cmds = new string[](4);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "decodeVersionedNonce";
cmds[3] = vm.toString(nonce);
string[] memory cmds = new string[](3);
cmds[0] = "scripts/differential-testing/differential-testing";
cmds[1] = "decodeVersionedNonce";
cmds[2] = vm.toString(nonce);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (uint256, uint256));
......
......@@ -91,7 +91,7 @@ contract Encoding_Test is CommonTest {
uint64 _gas,
bool isCreate,
bytes memory _data,
uint256 _logIndex
uint64 _logIndex
) external {
Types.UserDepositTransaction memory t = Types.UserDepositTransaction(
_from,
......
......@@ -129,7 +129,7 @@ contract Hashing_hashDepositTransaction_Test is CommonTest {
uint256 _value,
uint64 _gas,
bytes memory _data,
uint256 _logIndex
uint64 _logIndex
) external {
assertEq(
Hashing.hashDepositTransaction(
......
......@@ -17,7 +17,7 @@
"bindings": "cd ../../op-bindings && make",
"build:forge": "forge build",
"build:with-metadata": "FOUNDRY_PROFILE=echidna yarn build:forge",
"build:differential": "tsc scripts/differential-testing.ts --outDir dist --moduleResolution node --esModuleInterop",
"build:differential": "go build -o ./scripts/differential-testing/differential-testing ./scripts/differential-testing",
"build:fuzz": "(cd test-case-generator && go build ./cmd/fuzz.go)",
"prebuild": "yarn ts-node scripts/verify-foundry-install.ts",
"build": "hardhat compile && yarn autogen:artifacts && yarn build:ts && yarn typechain",
......@@ -59,8 +59,6 @@
},
"devDependencies": {
"@eth-optimism/hardhat-deploy-config": "^0.2.5",
"@ethereumjs/trie": "^5.0.0-beta.1",
"@ethereumjs/util": "^8.0.0-beta.1",
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/abstract-signer": "^5.7.0",
"ethereumjs-wallet": "^1.0.2",
......
import { BigNumber, utils, constants } from 'ethers'
import {
decodeVersionedNonce,
hashCrossDomainMessage,
DepositTx,
SourceHashDomain,
encodeCrossDomainMessage,
hashWithdrawal,
hashOutputRootProof,
} from '@eth-optimism/core-utils'
import { SecureTrie } from '@ethereumjs/trie'
import { Account, Address, toBuffer, bufferToHex } from '@ethereumjs/util'
import { predeploys } from '../src'
const { hexZeroPad, keccak256 } = utils
const args = process.argv.slice(2)
const command = args[0]
;(async () => {
switch (command) {
case 'decodeVersionedNonce': {
const input = BigNumber.from(args[1])
const { nonce, version } = decodeVersionedNonce(input)
const output = utils.defaultAbiCoder.encode(
['uint256', 'uint256'],
[nonce.toHexString(), version.toHexString()]
)
process.stdout.write(output)
break
}
case 'encodeCrossDomainMessage': {
const nonce = BigNumber.from(args[1])
const sender = args[2]
const target = args[3]
const value = BigNumber.from(args[4])
const gasLimit = BigNumber.from(args[5])
const data = args[6]
const encoding = encodeCrossDomainMessage(
nonce,
sender,
target,
value,
gasLimit,
data
)
const output = utils.defaultAbiCoder.encode(['bytes'], [encoding])
process.stdout.write(output)
break
}
case 'hashCrossDomainMessage': {
const nonce = BigNumber.from(args[1])
const sender = args[2]
const target = args[3]
const value = BigNumber.from(args[4])
const gasLimit = BigNumber.from(args[5])
const data = args[6]
const hash = hashCrossDomainMessage(
nonce,
sender,
target,
value,
gasLimit,
data
)
const output = utils.defaultAbiCoder.encode(['bytes32'], [hash])
process.stdout.write(output)
break
}
case 'hashDepositTransaction': {
// The solidity transaction hash computation currently only works with
// user deposits. System deposit transaction hashing is not supported.
const l1BlockHash = args[1]
const logIndex = BigNumber.from(args[2])
const from = args[3]
const to = args[4]
const mint = BigNumber.from(args[5])
const value = BigNumber.from(args[6])
const gas = BigNumber.from(args[7])
const data = args[8]
const tx = new DepositTx({
l1BlockHash,
logIndex,
from,
to,
mint,
value,
gas,
data,
isSystemTransaction: false,
domain: SourceHashDomain.UserDeposit,
})
const digest = tx.hash()
const output = utils.defaultAbiCoder.encode(['bytes32'], [digest])
process.stdout.write(output)
break
}
case 'encodeDepositTransaction': {
const from = args[1]
const to = args[2]
const value = BigNumber.from(args[3])
const mint = BigNumber.from(args[4])
const gasLimit = BigNumber.from(args[5])
const isCreate = args[6] === 'true' ? true : false
const data = args[7]
const l1BlockHash = args[8]
const logIndex = BigNumber.from(args[9])
const tx = new DepositTx({
from,
to: isCreate ? null : to,
value,
mint,
gas: gasLimit,
data,
l1BlockHash,
logIndex,
domain: SourceHashDomain.UserDeposit,
})
const raw = tx.encode()
const output = utils.defaultAbiCoder.encode(['bytes'], [raw])
process.stdout.write(output)
break
}
case 'hashWithdrawal': {
const nonce = BigNumber.from(args[1])
const sender = args[2]
const target = args[3]
const value = BigNumber.from(args[4])
const gas = BigNumber.from(args[5])
const data = args[6]
const hash = hashWithdrawal(nonce, sender, target, value, gas, data)
const output = utils.defaultAbiCoder.encode(['bytes32'], [hash])
process.stdout.write(output)
break
}
case 'hashOutputRootProof': {
const version = hexZeroPad(BigNumber.from(args[1]).toHexString(), 32)
const stateRoot = hexZeroPad(BigNumber.from(args[2]).toHexString(), 32)
const messagePasserStorageRoot = hexZeroPad(
BigNumber.from(args[3]).toHexString(),
32
)
const latestBlockhash = hexZeroPad(
BigNumber.from(args[4]).toHexString(),
32
)
const hash = hashOutputRootProof({
version,
stateRoot,
messagePasserStorageRoot,
latestBlockhash,
})
const output = utils.defaultAbiCoder.encode(['bytes32'], [hash])
process.stdout.write(output)
break
}
case 'getProveWithdrawalTransactionInputs': {
const nonce = BigNumber.from(args[1])
const sender = args[2]
const target = args[3]
const value = BigNumber.from(args[4])
const gas = BigNumber.from(args[5])
const data = args[6]
// Compute the withdrawalHash
const withdrawalHash = hashWithdrawal(
nonce,
sender,
target,
value,
gas,
data
)
// Compute the storage slot the withdrawalHash will be stored in
const slot = utils.defaultAbiCoder.encode(
['bytes32', 'bytes32'],
[withdrawalHash, utils.hexZeroPad('0x', 32)]
)
const key = keccak256(slot)
// Create the account storage trie
const storage = new SecureTrie()
// Put a bool "true" into storage
await storage.put(toBuffer(key), toBuffer('0x01'))
// Put the storage root into the L2ToL1MessagePasser storage
const address = Address.fromString(predeploys.L2ToL1MessagePasser)
const account = Account.fromAccountData({
nonce: 0,
balance: 0,
stateRoot: storage.root,
})
const world = new SecureTrie()
await world.put(address.toBuffer(), account.serialize())
const proof = await SecureTrie.createProof(storage, toBuffer(key))
const outputRoot = hashOutputRootProof({
version: constants.HashZero,
stateRoot: bufferToHex(world.root),
messagePasserStorageRoot: bufferToHex(storage.root),
latestBlockhash: constants.HashZero,
})
const output = utils.defaultAbiCoder.encode(
['bytes32', 'bytes32', 'bytes32', 'bytes32', 'bytes[]'],
[world.root, storage.root, outputRoot, withdrawalHash, proof]
)
process.stdout.write(output)
break
}
}
})().catch((err: Error) => {
console.error(err)
process.stdout.write('')
})
package main
import (
"bytes"
"fmt"
"math/big"
"os"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/trie"
)
// ABI types
var (
// Plain dynamic dynBytes type
dynBytes, _ = abi.NewType("bytes", "", nil)
bytesArgs = abi.Arguments{
{Type: dynBytes},
}
// Plain fixed bytes32 type
fixedBytes, _ = abi.NewType("bytes32", "", nil)
fixedBytesArgs = abi.Arguments{
{Type: fixedBytes},
}
// Decoded nonce tuple (nonce, version)
decodedNonce, _ = abi.NewType("tuple", "DecodedNonce", []abi.ArgumentMarshaling{
{Name: "nonce", Type: "uint256"},
{Name: "version", Type: "uint256"},
})
decodedNonceArgs = abi.Arguments{
{Name: "encodedNonce", Type: decodedNonce},
}
// WithdrawalHash slot tuple (bytes32, bytes32)
withdrawalSlot, _ = abi.NewType("tuple", "SlotHash", []abi.ArgumentMarshaling{
{Name: "withdrawalHash", Type: "bytes32"},
{Name: "zeroPadding", Type: "bytes32"},
})
withdrawalSlotArgs = abi.Arguments{
{Name: "slotHash", Type: withdrawalSlot},
}
// Prove withdrawal inputs tuple (bytes32, bytes32, bytes32, bytes32, bytes[])
proveWithdrawalInputs, _ = abi.NewType("tuple", "ProveWithdrawalInputs", []abi.ArgumentMarshaling{
{Name: "worldRoot", Type: "bytes32"},
{Name: "stateRoot", Type: "bytes32"},
{Name: "outputRoot", Type: "bytes32"},
{Name: "withdrawalHash", Type: "bytes32"},
{Name: "proof", Type: "bytes[]"},
})
proveWithdrawalInputsArgs = abi.Arguments{
{Name: "inputs", Type: proveWithdrawalInputs},
}
)
func main() {
args := os.Args[1:]
// This command requires arguments
if len(args) == 0 {
panic("Error: No arguments provided")
}
switch args[0] {
case "decodeVersionedNonce":
// Parse input arguments
input, ok := new(big.Int).SetString(args[1], 10)
checkOk(ok)
// Decode versioned nonce
nonce, version := crossdomain.DecodeVersionedNonce(input)
// ABI encode output
packArgs := struct {
Nonce *big.Int
Version *big.Int
}{
nonce,
version,
}
packed, err := decodedNonceArgs.Pack(&packArgs)
checkErr(err, "Error encoding output")
fmt.Print(hexutil.Encode(packed))
case "encodeCrossDomainMessage":
// Parse input arguments
nonce, ok := new(big.Int).SetString(args[1], 10)
checkOk(ok)
sender := common.HexToAddress(args[2])
target := common.HexToAddress(args[3])
value, ok := new(big.Int).SetString(args[4], 10)
checkOk(ok)
gasLimit, ok := new(big.Int).SetString(args[5], 10)
checkOk(ok)
data := common.FromHex(args[6])
// Encode cross domain message
encoded, err := encodeCrossDomainMessage(nonce, sender, target, value, gasLimit, data)
checkErr(err, "Error encoding cross domain message")
// Pack encoded cross domain message
packed, err := bytesArgs.Pack(&encoded)
checkErr(err, "Error encoding output")
fmt.Print(hexutil.Encode(packed))
case "hashCrossDomainMessage":
// Parse input arguments
nonce, ok := new(big.Int).SetString(args[1], 10)
checkOk(ok)
sender := common.HexToAddress(args[2])
target := common.HexToAddress(args[3])
value, ok := new(big.Int).SetString(args[4], 10)
checkOk(ok)
gasLimit, ok := new(big.Int).SetString(args[5], 10)
checkOk(ok)
data := common.FromHex(args[6])
// Encode cross domain message
encoded, err := encodeCrossDomainMessage(nonce, sender, target, value, gasLimit, data)
checkErr(err, "Error encoding cross domain message")
// Hash encoded cross domain message
hash := crypto.Keccak256Hash(encoded)
// Pack hash
packed, err := fixedBytesArgs.Pack(&hash)
checkErr(err, "Error encoding output")
fmt.Print(hexutil.Encode(packed))
case "hashDepositTransaction":
// Parse input arguments
l1BlockHash := common.HexToHash(args[1])
logIndex, ok := new(big.Int).SetString(args[2], 10)
checkOk(ok)
from := common.HexToAddress(args[3])
to := common.HexToAddress(args[4])
mint, ok := new(big.Int).SetString(args[5], 10)
checkOk(ok)
value, ok := new(big.Int).SetString(args[6], 10)
checkOk(ok)
gasLimit, ok := new(big.Int).SetString(args[7], 10)
checkOk(ok)
data := common.FromHex(args[8])
// Create deposit transaction
depositTx := makeDepositTx(from, to, value, mint, gasLimit, false, data, l1BlockHash, logIndex)
// RLP encode deposit transaction
encoded, err := types.NewTx(&depositTx).MarshalBinary()
checkErr(err, "Error encoding deposit transaction")
// Hash encoded deposit transaction
hash := crypto.Keccak256Hash(encoded)
// Pack hash
packed, err := fixedBytesArgs.Pack(&hash)
checkErr(err, "Error encoding output")
fmt.Print(hexutil.Encode(packed))
case "encodeDepositTransaction":
// Parse input arguments
from := common.HexToAddress(args[1])
to := common.HexToAddress(args[2])
value, ok := new(big.Int).SetString(args[3], 10)
checkOk(ok)
mint, ok := new(big.Int).SetString(args[4], 10)
checkOk(ok)
gasLimit, ok := new(big.Int).SetString(args[5], 10)
checkOk(ok)
isCreate := args[6] == "true"
data := common.FromHex(args[7])
l1BlockHash := common.HexToHash(args[8])
logIndex, ok := new(big.Int).SetString(args[9], 10)
checkOk(ok)
depositTx := makeDepositTx(from, to, value, mint, gasLimit, isCreate, data, l1BlockHash, logIndex)
// RLP encode deposit transaction
encoded, err := types.NewTx(&depositTx).MarshalBinary()
checkErr(err, "Failed to RLP encode deposit transaction")
// Pack rlp encoded deposit transaction
packed, err := bytesArgs.Pack(&encoded)
checkErr(err, "Error encoding output")
fmt.Print(hexutil.Encode(packed))
case "hashWithdrawal":
// Parse input arguments
nonce, ok := new(big.Int).SetString(args[1], 10)
checkOk(ok)
sender := common.HexToAddress(args[2])
target := common.HexToAddress(args[3])
value, ok := new(big.Int).SetString(args[4], 10)
checkOk(ok)
gasLimit, ok := new(big.Int).SetString(args[5], 10)
checkOk(ok)
data := common.FromHex(args[6])
// Hash withdrawal
hash, err := hashWithdrawal(nonce, sender, target, value, gasLimit, data)
checkErr(err, "Error hashing withdrawal")
// Pack hash
packed, err := fixedBytesArgs.Pack(&hash)
checkErr(err, "Error encoding output")
fmt.Print(hexutil.Encode(packed))
case "hashOutputRootProof":
// Parse input arguments
version := common.HexToHash(args[1])
stateRoot := common.HexToHash(args[2])
messagePasserStorageRoot := common.HexToHash(args[3])
latestBlockHash := common.HexToHash(args[4])
// Hash the output root proof
hash, err := hashOutputRootProof(version, stateRoot, messagePasserStorageRoot, latestBlockHash)
checkErr(err, "Error hashing output root proof")
// Pack hash
packed, err := fixedBytesArgs.Pack(&hash)
checkErr(err, "Error encoding output")
fmt.Print(hexutil.Encode(packed))
case "getProveWithdrawalTransactionInputs":
// Parse input arguments
nonce, ok := new(big.Int).SetString(args[1], 10)
checkOk(ok)
sender := common.HexToAddress(args[2])
target := common.HexToAddress(args[3])
value, ok := new(big.Int).SetString(args[4], 10)
checkOk(ok)
gasLimit, ok := new(big.Int).SetString(args[5], 10)
checkOk(ok)
data := common.FromHex(args[6])
wdHash, err := hashWithdrawal(nonce, sender, target, value, gasLimit, data)
checkErr(err, "Error hashing withdrawal")
// Compute the storage slot the withdrawalHash will be stored in
slot := struct {
WithdrawalHash common.Hash
ZeroPadding common.Hash
}{
WithdrawalHash: wdHash,
ZeroPadding: common.Hash{},
}
packed, err := withdrawalSlotArgs.Pack(&slot)
checkErr(err, "Error packing withdrawal slot")
// Compute the storage slot the withdrawalHash will be stored in
hash := crypto.Keccak256Hash(packed)
// Create a secure trie for state
state, err := trie.NewStateTrie(
trie.TrieID(types.EmptyRootHash),
trie.NewDatabase(rawdb.NewMemoryDatabase()),
)
checkErr(err, "Error creating secure trie")
// Put a "true" bool in the storage slot
state.Update(hash.Bytes(), []byte{0x01})
// Create a secure trie for the world state
world, err := trie.NewStateTrie(
trie.TrieID(types.EmptyRootHash),
trie.NewDatabase(rawdb.NewMemoryDatabase()),
)
checkErr(err, "Error creating secure trie")
// Put the put the rlp encoded account in the world trie
account := types.StateAccount{
Nonce: 0,
Balance: big.NewInt(0),
Root: state.Hash(),
}
writer := new(bytes.Buffer)
checkErr(account.EncodeRLP(writer), "Error encoding account")
world.Update(predeploys.L2ToL1MessagePasserAddr.Bytes(), writer.Bytes())
// Get the proof
var proof proofList
checkErr(state.Prove(predeploys.L2ToL1MessagePasserAddr.Bytes(), 0, &proof), "Error getting proof")
// Get the output root
outputRoot, err := hashOutputRootProof(common.Hash{}, world.Hash(), state.Hash(), common.Hash{})
checkErr(err, "Error hashing output root proof")
// Pack the output
output := struct {
WorldRoot common.Hash
StateRoot common.Hash
OutputRoot common.Hash
WithdrawalHash common.Hash
Proof proofList
}{
WorldRoot: world.Hash(),
StateRoot: state.Hash(),
OutputRoot: outputRoot,
WithdrawalHash: wdHash,
Proof: proof,
}
packed, err = proveWithdrawalInputsArgs.Pack(&output)
checkErr(err, "Error encoding output")
// Print the output
fmt.Print(hexutil.Encode(packed[32:]))
default:
panic(fmt.Errorf("Unknown command: %s", args[0]))
}
}
package main
import (
"errors"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
var UnknownNonceVersion = errors.New("Unknown nonce version")
// checkOk checks if ok is false, and panics if so.
// Shorthand to ease go's god awful error handling
func checkOk(ok bool) {
if !ok {
panic(fmt.Errorf("checkOk failed"))
}
}
// checkErr checks if err is not nil, and throws if so.
// Shorthand to ease go's god awful error handling
func checkErr(err error, failReason string) {
if err != nil {
panic(fmt.Errorf("%s: %s", failReason, err))
}
}
// encodeCrossDomainMessage encodes a versioned cross domain message into a byte array.
func encodeCrossDomainMessage(nonce *big.Int, sender common.Address, target common.Address, value *big.Int, gasLimit *big.Int, data []byte) ([]byte, error) {
_, version := crossdomain.DecodeVersionedNonce(nonce)
var encoded []byte
var err error
if version.Cmp(big.NewInt(0)) == 0 {
// Encode cross domain message V0
encoded, err = crossdomain.EncodeCrossDomainMessageV0(target, sender, data, nonce)
} else if version.Cmp(big.NewInt(1)) == 0 {
// Encode cross domain message V1
encoded, err = crossdomain.EncodeCrossDomainMessageV1(nonce, sender, target, value, gasLimit, data)
} else {
return nil, UnknownNonceVersion
}
return encoded, err
}
// hashWithdrawal hashes a withdrawal transaction.
func hashWithdrawal(nonce *big.Int, sender common.Address, target common.Address, value *big.Int, gasLimit *big.Int, data []byte) (common.Hash, error) {
wd := crossdomain.Withdrawal{
Nonce: nonce,
Sender: &sender,
Target: &target,
Value: value,
GasLimit: gasLimit,
Data: data,
}
return wd.Hash()
}
// hashOutputRootProof hashes an output root proof.
func hashOutputRootProof(version common.Hash, stateRoot common.Hash, messagePasserStorageRoot common.Hash, latestBlockHash common.Hash) (common.Hash, error) {
hash, err := rollup.ComputeL2OutputRoot(&bindings.TypesOutputRootProof{
Version: version,
StateRoot: stateRoot,
MessagePasserStorageRoot: messagePasserStorageRoot,
LatestBlockhash: latestBlockHash,
})
if err != nil {
return common.Hash{}, err
}
return common.Hash(hash), nil
}
// makeDepositTx creates a deposit transaction type.
func makeDepositTx(
from common.Address,
to common.Address,
value *big.Int,
mint *big.Int,
gasLimit *big.Int,
isCreate bool,
data []byte,
l1BlockHash common.Hash,
logIndex *big.Int,
) types.DepositTx {
// Create deposit transaction source
udp := derive.UserDepositSource{
L1BlockHash: l1BlockHash,
LogIndex: logIndex.Uint64(),
}
// Create deposit transaction
depositTx := types.DepositTx{
SourceHash: udp.SourceHash(),
From: from,
Value: value,
Gas: gasLimit.Uint64(),
IsSystemTransaction: false, // This will never be a system transaction in the tests.
Data: data,
}
// Fill optional fields
if mint.Cmp(big.NewInt(0)) == 1 {
depositTx.Mint = mint
}
if !isCreate {
depositTx.To = &to
}
return depositTx
}
// Custom type to write the generated proof to
type proofList [][]byte
func (n *proofList) Put(key []byte, value []byte) error {
*n = append(*n, value)
return nil
}
func (n *proofList) Delete(key []byte) error {
panic("not supported")
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment