Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ __pycache__/
# direnv
.envrc
.direnv/

# Optimism test artifacts
crates/optimism/tests/proofs/contracts/artifacts
crates/optimism/tests/proofs/contracts/cache
9 changes: 7 additions & 2 deletions crates/optimism/tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ DEVNET := simple-historical-proof
GO_PKG_NAME := proofs
SOURCE_DIR := $(shell pwd)

.PHONY: all build run clean help
.PHONY: all build build-contracts run clean help

# Default target
all: build run
Expand All @@ -26,8 +26,13 @@ run:
fi; \
kurtosis run $(KURTOSIS_PACKAGE) --args-file $$DEVNET_PATH --enclave $(DEVNET)

# Build smart contract artifacts with Foundry
build-contracts:
@echo "Building contracts with forge..."
@cd "$(SOURCE_DIR)/proofs/contracts" && forge build || { echo "forge build failed"; exit 1; }

# Run E2E tests using Kurtosis
test-e2e-kurtosis:
test-e2e-kurtosis: build-contracts
@echo "Running E2E tests with Kurtosis for $(DEVNET)"
@DEVNET_PATH="$(SOURCE_DIR)/devnets/$(DEVNET).yaml"; \
if [ ! -z "$(DEVNET_CUSTOM_PATH)" ]; then \
Expand Down
3 changes: 3 additions & 0 deletions crates/optimism/tests/proofs/contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[profile.default]
src = "src"
out = "artifacts"
10 changes: 10 additions & 0 deletions crates/optimism/tests/proofs/contracts/src/SimpleStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SimpleStorage {
uint256 public value;

function setValue(uint256 newValue) external {
value = newValue;
}
}
4 changes: 2 additions & 2 deletions crates/optimism/tests/proofs/init_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package l2reorg
package proofs

import (
"testing"
Expand All @@ -9,5 +9,5 @@ import (
// TestMain creates the test-setups against the shared backend
func TestMain(m *testing.M) {
// Other setups may be added here, hydrated from the same orchestrator
presets.DoMain(m, presets.WithMinimal())
presets.DoMain(m, presets.WithSingleChainMultiNode())
}
147 changes: 147 additions & 0 deletions crates/optimism/tests/proofs/simple_storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package proofs

import (
"context"
"encoding/json"
"fmt"
"math/big"
"os"
"strings"
"testing"

"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/dsl"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/txplan"
"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/types"
"github.com/stretchr/testify/require"
)

// minimal parts of artifact
type artifact struct {
ABI json.RawMessage `json:"abi"`
Bytecode struct {
Object string `json:"object"`
} `json:"bytecode"`
}

// loadArtifact reads the forge artifact JSON at artifactPath and returns the parsed ABI
// and the creation bytecode (as bytes). It prefers bytecode.object (creation) and falls
// back to deployedBytecode.object if needed.
Comment on lines +33 to +34
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states the function 'falls back to deployedBytecode.object if needed', but the actual implementation at line 49 returns an error when bytecode.object is empty instead of falling back to deployedBytecode.object.

Copilot uses AI. Check for mistakes.
func loadArtifact(artifactPath string) (abi.ABI, []byte, error) {
data, err := os.ReadFile(artifactPath)
if err != nil {
return abi.ABI{}, nil, err
}
var art artifact
if err := json.Unmarshal(data, &art); err != nil {
return abi.ABI{}, nil, err
}
parsedABI, err := abi.JSON(strings.NewReader(string(art.ABI)))
if err != nil {
return abi.ABI{}, nil, err
}
binHex := strings.TrimSpace(art.Bytecode.Object)
if binHex == "" {
return parsedABI, nil, fmt.Errorf("artifact missing bytecode")
}
return parsedABI, common.FromHex(binHex), nil
}

// deployContract deploys the contract creation bytecode from the given artifact.
// user must provide a Plan() method compatible with txplan.NewPlannedTx (kept generic).
func deployContract(ctx context.Context, user *dsl.EOA, bin []byte) (common.Address, uint64, error) {
tx := txplan.NewPlannedTx(user.Plan(), txplan.WithData(bin))
res, err := tx.Included.Eval(ctx)
if err != nil {
return common.Address{}, 0, fmt.Errorf("deployment eval: %w", err)
}
return res.ContractAddress, res.BlockNumber.Uint64(), nil
}

func TestStorageProofUsingSimpleStorageContract(gt *testing.T) {
t := devtest.SerialT(gt)
ctx := t.Ctx()

sys := presets.NewSingleChainMultiNode(t)
artifactPath := "contracts/artifacts/SimpleStorage.sol/SimpleStorage.json"
parsedABI, bin, err := loadArtifact(artifactPath)
if err != nil {
t.Error("failed to load artifact: %v", err)
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using t.Error with format specifiers doesn't format the error. Should use t.Errorf to properly format the error message with the %v verb.

Suggested change
t.Error("failed to load artifact: %v", err)
t.Errorf("failed to load artifact: %v", err)

Copilot uses AI. Check for mistakes.
t.FailNow()
}

user := sys.FunderL2.NewFundedEOA(eth.OneHundredthEther)

// deploy contract via helper
contractAddress, blockNum, err := deployContract(ctx, user, bin)
if err != nil {
t.Error("failed to deploy contract: %v", err)
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using t.Error with format specifiers doesn't format the error. Should use t.Errorf to properly format the error message with the %v verb.

Suggested change
t.Error("failed to deploy contract: %v", err)
t.Errorf("failed to deploy contract: %v", err)

Copilot uses AI. Check for mistakes.
t.FailNow()
}
t.Logf("contract deployed at address %s in L2 block %d", contractAddress.Hex(), blockNum)

type caseEntry struct {
Block uint64
Value *big.Int
}
var cases []caseEntry
for i := 1; i <= 5; i++ {
writeVal := big.NewInt(int64(i * 10))
callData, err := parsedABI.Pack("setValue", writeVal)
if err != nil {
t.Error("failed to pack set call data: %v", err)
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using t.Error with format specifiers doesn't format the error. Should use t.Errorf to properly format the error message with the %v verb.

Suggested change
t.Error("failed to pack set call data: %v", err)
t.Errorf("failed to pack set call data: %v", err)

Copilot uses AI. Check for mistakes.
t.FailNow()
}

callTx := txplan.NewPlannedTx(user.Plan(), txplan.WithTo(&contractAddress), txplan.WithData(callData))
callRes, err := callTx.Included.Eval(ctx)
if err != nil {
t.Error("failed to create set tx: %v", err)
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using t.Error with format specifiers doesn't format the error. Should use t.Errorf to properly format the error message with the %v verb.

Suggested change
t.Error("failed to create set tx: %v", err)
t.Errorf("failed to create set tx: %v", err)

Copilot uses AI. Check for mistakes.
t.FailNow()
}

if callRes.Status != types.ReceiptStatusSuccessful {
t.Error("set transaction failed")
t.FailNow()
}

cases = append(cases, caseEntry{
Block: callRes.BlockNumber.Uint64(),
Value: writeVal,
})
t.Logf("setValue transaction included in L2 block %d", callRes.BlockNumber)
}

// for each case, get proof and verify
for _, c := range cases {
gethProofRes, err := sys.L2EL.Escape().L2EthClient().GetProof(ctx, contractAddress, []common.Hash{common.HexToHash("0x0")}, hexutil.Uint64(c.Block).String())
if err != nil {
t.Errorf("failed to get proof from L2EL at block %d: %v", c.Block, err)
t.FailNow()
}

rethProofRes, err := sys.L2ELB.Escape().L2EthClient().GetProof(ctx, contractAddress, []common.Hash{common.HexToHash("0x0")}, hexutil.Uint64(c.Block).String())
if err != nil {
t.Errorf("failed to get proof from L2ELB at block %d: %v", c.Block, err)
t.FailNow()
}

require.Equal(t, gethProofRes, rethProofRes, "geth and reth proofs should match")

block, err := sys.L2EL.Escape().L2EthClient().InfoByNumber(ctx, c.Block)
if err != nil {
t.Errorf("failed to get L2 block %d: %v", c.Block, err)
t.FailNow()
}
err = rethProofRes.Verify(block.Root())
if err != nil {
t.Errorf("proof verification failed at block %d: %v", c.Block, err)
t.FailNow()
}
}
}
Loading