Commit 7a943032 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into refcell/resolve-tx

parents 8b2b0288 2ae5877f
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"changelog": ["@changesets/changelog-github", { "repo": "ethereum-optimism/optimism" }], "changelog": ["@changesets/changelog-github", { "repo": "ethereum-optimism/optimism" }],
"commit": false, "commit": false,
"fixed": [], "fixed": [],
"linked": [], "linked": [["contracts-bedrock", "contracts-ts"]],
"access": "public", "access": "public",
"baseBranch": "develop", "baseBranch": "develop",
"updateInternalDependencies": "patch", "updateInternalDependencies": "patch",
......
...@@ -143,6 +143,7 @@ jobs: ...@@ -143,6 +143,7 @@ jobs:
- "packages/fault-detector/node_modules" - "packages/fault-detector/node_modules"
- "packages/replica-healthcheck/node_modules" - "packages/replica-healthcheck/node_modules"
- "packages/sdk/node_modules" - "packages/sdk/node_modules"
- "packages/contracts-ts/node_modules"
- run: - run:
name: print forge version name: print forge version
command: forge --version command: forge --version
...@@ -709,6 +710,24 @@ jobs: ...@@ -709,6 +710,24 @@ jobs:
name: Upload coverage name: Upload coverage
command: codecov --verbose --clean --flags <<parameters.coverage_flag>> command: codecov --verbose --clean --flags <<parameters.coverage_flag>>
contracts-ts-tests:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
resource_class: large
steps:
- checkout
- attach_workspace: { at: "." }
- restore_cache:
name: Restore pnpm Package Cache
keys:
- pnpm-packages-v2-{{ checksum "pnpm.lock.yaml" }}
- check-changed:
patterns: sdk,contracts-bedrock,contracts
- run:
name: Check generated and build
command: pnpm generate:check
working_directory: packages/contracts-ts
sdk-next-tests: sdk-next-tests:
docker: docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
...@@ -1336,6 +1355,13 @@ workflows: ...@@ -1336,6 +1355,13 @@ workflows:
dependencies: "(common-ts|core-utils|sdk)" dependencies: "(common-ts|core-utils|sdk)"
requires: requires:
- pnpm-monorepo - pnpm-monorepo
- js-lint-test:
name: contracts-ts-tests
coverage_flag: contracts-ts-tests
package_name: contracts-ts
dependencies: '(contracts-bedrock|contracts-ts)'
requires:
- pnpm-monorepo
- js-lint-test: - js-lint-test:
name: sdk-next-tests name: sdk-next-tests
coverage_flag: sdk-next-tests coverage_flag: sdk-next-tests
......
...@@ -49,7 +49,8 @@ jobs: ...@@ -49,7 +49,8 @@ jobs:
id: changesets id: changesets
with: with:
createGithubReleases: false createGithubReleases: false
publish: pnpm release publish: pnpm release:publish
version: pnpm release:version
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
......
...@@ -56,6 +56,9 @@ ...@@ -56,6 +56,9 @@
"inputs": ["default", "testing", "^production"], "inputs": ["default", "testing", "^production"],
"dependsOn": ["^build"] "dependsOn": ["^build"]
}, },
"generate": {
"dependsOn": ["^build"]
},
"build:contracts": { "build:contracts": {
"inputs": [ "inputs": [
"configsProject", "configsProject",
......
...@@ -28,3 +28,7 @@ $ make devtools ...@@ -28,3 +28,7 @@ $ make devtools
``` ```
The geth docs for `abigen` can be found [here](https://geth.ethereum.org/docs/dapp/native-bindings). The geth docs for `abigen` can be found [here](https://geth.ethereum.org/docs/dapp/native-bindings).
## See also
TypeScript bindings are also generated in [@eth-optimism/contracts-ts](../packages/contracts-ts/)
This diff is collapsed.
...@@ -2,6 +2,9 @@ package predeploys ...@@ -2,6 +2,9 @@ package predeploys
import "github.com/ethereum/go-ethereum/common" import "github.com/ethereum/go-ethereum/common"
// TODO - we should get a single toml yaml or json file source of truth in @eth-optimism/bedrock package
// This needs to be kept in sync with @eth-optimism/contracts-ts/wagmi.config.ts which also specifies this
// To improve robustness and maintainability contracts-bedrock should export all addresses
const ( const (
L2ToL1MessagePasser = "0x4200000000000000000000000000000000000016" L2ToL1MessagePasser = "0x4200000000000000000000000000000000000016"
DeployerWhitelist = "0x4200000000000000000000000000000000000002" DeployerWhitelist = "0x4200000000000000000000000000000000000002"
......
...@@ -20,35 +20,3 @@ to see a list of available options. ...@@ -20,35 +20,3 @@ to see a list of available options.
`op-challenger` is configurable via command line flags and environment variables. The help menu `op-challenger` is configurable via command line flags and environment variables. The help menu
shows the available config options and can be accessed by running `./op-challenger --help`. shows the available config options and can be accessed by running `./op-challenger --help`.
Note that there are many global options, but the most important ones are:
- `OP_CHALLENGER_L1_ETH_RPC`: An L1 Ethereum RPC URL
- `OP_CHALLENGER_ROLLUP_RPC`: A Rollup Node RPC URL
- `OP_CHALLENGER_L2OO_ADDRESS`: The L2OutputOracle Contract Address
- `OP_CHALLENGER_DGF_ADDRESS`: Dispute Game Factory Contract Address
Here is a reduced output from running `./op-challenger --help`:
```bash
NAME:
op-challenger - Modular Challenger Agent
USAGE:
main [global options] command [command options] [arguments...]
VERSION:
1.0.0
DESCRIPTION:
A modular op-stack challenge agent for output dispute games written in golang.
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--l1-eth-rpc value HTTP provider URL for L1. [$OP_CHALLENGER_L1_ETH_RPC]
--rollup-rpc value HTTP provider URL for the rollup node. [$OP_CHALLENGER_ROLLUP_RPC]
--l2oo-address value Address of the L2OutputOracle contract. [$OP_CHALLENGER_L2OO_ADDRESS]
--dgf-address value Address of the DisputeGameFactory contract. [$OP_CHALLENGER_DGF_ADDRESS]
...
--help, -h show help
--version, -v print the version
```
...@@ -35,16 +35,21 @@ func Main(logger log.Logger, cfg *config.Config) error { ...@@ -35,16 +35,21 @@ func Main(logger log.Logger, cfg *config.Config) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to create the responder: %w", err) return fmt.Errorf("failed to create the responder: %w", err)
} }
gameDepth := 4 trace := fault.NewAlphabetProvider(cfg.AlphabetTrace, uint64(cfg.GameDepth))
trace := fault.NewAlphabetProvider(cfg.AlphabetTrace, uint64(gameDepth))
agent := fault.NewAgent(loader, gameDepth, trace, responder, cfg.AgreeWithProposedOutput, logger) agent := fault.NewAgent(loader, cfg.GameDepth, trace, responder, cfg.AgreeWithProposedOutput, logger)
caller, err := fault.NewFaultCallerFromBindings(cfg.GameAddress, client, logger)
if err != nil {
return fmt.Errorf("failed to bind the fault contract: %w", err)
}
logger.Info("Fault game started") logger.Info("Fault game started")
for { for {
logger.Info("Performing action") logger.Info("Performing action")
_ = agent.Act() _ = agent.Act()
caller.LogGameInfo()
time.Sleep(300 * time.Millisecond) time.Sleep(300 * time.Millisecond)
} }
} }
...@@ -14,4 +14,11 @@ MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715" ...@@ -14,4 +14,11 @@ MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d" FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
./bin/op-challenger --l1-eth-rpc http://localhost:8545 --alphabet "abcdefgh" --game-address $FAULT_GAME_ADDRESS --private-key $CHARLIE_KEY --num-confirmations 1 --agree-with-proposed-output=true ./bin/op-challenger \
--l1-eth-rpc http://localhost:8545 \
--alphabet "abcdefgh" \
--game-address $FAULT_GAME_ADDRESS \
--private-key $CHARLIE_KEY \
--num-confirmations 1 \
--game-depth 4 \
--agree-with-proposed-output=true
...@@ -16,6 +16,7 @@ var ( ...@@ -16,6 +16,7 @@ var (
gameAddressValue = "0xaa00000000000000000000000000000000000000" gameAddressValue = "0xaa00000000000000000000000000000000000000"
alphabetTrace = "abcdefghijz" alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true" agreeWithProposedOutput = "true"
gameDepth = "4"
) )
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
...@@ -35,12 +36,12 @@ func TestLogLevel(t *testing.T) { ...@@ -35,12 +36,12 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs()) cfg := configForArgs(t, addRequiredArgs())
defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace, true) defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace, true, 4)
require.Equal(t, defaultCfg, cfg) require.Equal(t, defaultCfg, cfg)
} }
func TestDefaultConfigIsValid(t *testing.T) { func TestDefaultConfigIsValid(t *testing.T) {
cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace, true) cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace, true, 4)
require.NoError(t, cfg.Check()) require.NoError(t, cfg.Check())
} }
...@@ -109,6 +110,18 @@ func TestAgreeWithProposedOutput(t *testing.T) { ...@@ -109,6 +110,18 @@ func TestAgreeWithProposedOutput(t *testing.T) {
}) })
} }
func TestGameDepth(t *testing.T) {
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag game-depth is required", addRequiredArgsExcept("--game-depth"))
})
t.Run("Valid", func(t *testing.T) {
value := "4"
cfg := configForArgs(t, addRequiredArgsExcept("--game-depth", "--game-depth="+value))
require.Equal(t, value, fmt.Sprint(cfg.GameDepth))
})
}
func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) { func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, _, err := runWithArgs(cliArgs) _, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains) require.ErrorContains(t, err, messageContains)
...@@ -146,6 +159,7 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string { ...@@ -146,6 +159,7 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string {
func requiredArgs() map[string]string { func requiredArgs() map[string]string {
return map[string]string{ return map[string]string{
"--game-depth": gameDepth,
"--agree-with-proposed-output": agreeWithProposedOutput, "--agree-with-proposed-output": agreeWithProposedOutput,
"--l1-eth-rpc": l1EthRpc, "--l1-eth-rpc": l1EthRpc,
"--game-address": gameAddressValue, "--game-address": gameAddressValue,
......
...@@ -25,6 +25,7 @@ type Config struct { ...@@ -25,6 +25,7 @@ type Config struct {
GameAddress common.Address // Address of the fault game GameAddress common.Address // Address of the fault game
AlphabetTrace string // String for the AlphabetTraceProvider AlphabetTrace string // String for the AlphabetTraceProvider
AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output
GameDepth int // Depth of the game tree
TxMgrConfig txmgr.CLIConfig TxMgrConfig txmgr.CLIConfig
} }
...@@ -34,6 +35,7 @@ func NewConfig( ...@@ -34,6 +35,7 @@ func NewConfig(
GameAddress common.Address, GameAddress common.Address,
AlphabetTrace string, AlphabetTrace string,
AgreeWithProposedOutput bool, AgreeWithProposedOutput bool,
GameDepth int,
) Config { ) Config {
return Config{ return Config{
L1EthRpc: l1EthRpc, L1EthRpc: l1EthRpc,
...@@ -41,6 +43,7 @@ func NewConfig( ...@@ -41,6 +43,7 @@ func NewConfig(
AlphabetTrace: AlphabetTrace, AlphabetTrace: AlphabetTrace,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc), TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc),
AgreeWithProposedOutput: AgreeWithProposedOutput, AgreeWithProposedOutput: AgreeWithProposedOutput,
GameDepth: GameDepth,
} }
} }
...@@ -78,6 +81,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { ...@@ -78,6 +81,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
GameAddress: dgfAddress, GameAddress: dgfAddress,
AlphabetTrace: ctx.String(flags.AlphabetFlag.Name), AlphabetTrace: ctx.String(flags.AlphabetFlag.Name),
AgreeWithProposedOutput: ctx.Bool(flags.AgreeWithProposedOutputFlag.Name), AgreeWithProposedOutput: ctx.Bool(flags.AgreeWithProposedOutputFlag.Name),
GameDepth: ctx.Int(flags.GameDepthFlag.Name),
TxMgrConfig: txMgrConfig, TxMgrConfig: txMgrConfig,
}, nil }, nil
} }
...@@ -13,10 +13,11 @@ var ( ...@@ -13,10 +13,11 @@ var (
validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139") validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validAlphabetTrace = "abcdefgh" validAlphabetTrace = "abcdefgh"
agreeWithProposedOutput = true agreeWithProposedOutput = true
gameDepth = 4
) )
func validConfig() Config { func validConfig() Config {
cfg := NewConfig(validL1EthRpc, validGameAddress, validAlphabetTrace, agreeWithProposedOutput) cfg := NewConfig(validL1EthRpc, validGameAddress, validAlphabetTrace, agreeWithProposedOutput, gameDepth)
return cfg return cfg
} }
......
...@@ -31,6 +31,7 @@ func setupFaultDisputeGame() (common.Address, *bind.TransactOpts, *backends.Simu ...@@ -31,6 +31,7 @@ func setupFaultDisputeGame() (common.Address, *bind.TransactOpts, *backends.Simu
backend, backend,
[32]byte{0x01}, [32]byte{0x01},
big.NewInt(15), big.NewInt(15),
uint64(604800), // 7 days
common.Address{0xdd}, common.Address{0xdd},
) )
if err != nil { if err != nil {
......
...@@ -95,7 +95,8 @@ func (a *Agent) step(claim Claim, game Game) error { ...@@ -95,7 +95,8 @@ func (a *Agent) step(claim Claim, game Game) error {
return nil return nil
} }
if game.AgreeWithClaimLevel(claim) { agreeWithClaimLevel := game.AgreeWithClaimLevel(claim)
if agreeWithClaimLevel {
a.log.Warn("Agree with leaf claim, skipping step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth) a.log.Warn("Agree with leaf claim, skipping step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
return nil return nil
} }
...@@ -106,7 +107,7 @@ func (a *Agent) step(claim Claim, game Game) error { ...@@ -106,7 +107,7 @@ func (a *Agent) step(claim Claim, game Game) error {
} }
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth) a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(claim) step, err := a.solver.AttemptStep(claim, agreeWithClaimLevel)
if err != nil { if err != nil {
a.log.Warn("Failed to get a step", "err", err) a.log.Warn("Failed to get a step", "err", err)
return err return err
......
package fault
import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
type FaultDisputeGameCaller interface {
Status(opts *bind.CallOpts) (uint8, error)
ClaimDataLen(opts *bind.CallOpts) (*big.Int, error)
}
type FaultCaller struct {
FaultDisputeGameCaller
log log.Logger
fdgAddr common.Address
}
func NewFaultCaller(fdgAddr common.Address, caller FaultDisputeGameCaller, log log.Logger) *FaultCaller {
return &FaultCaller{
caller,
log,
fdgAddr,
}
}
func NewFaultCallerFromBindings(fdgAddr common.Address, client *ethclient.Client, log log.Logger) (*FaultCaller, error) {
caller, err := bindings.NewFaultDisputeGameCaller(fdgAddr, client)
if err != nil {
return nil, err
}
return &FaultCaller{
caller,
log,
fdgAddr,
}, nil
}
// LogGameInfo logs the game info.
func (fc *FaultCaller) LogGameInfo() {
status, err := fc.GetGameStatus(context.Background())
if err != nil {
fc.log.Error("failed to get game status", "err", err)
return
}
claimLen, err := fc.GetClaimDataLength(context.Background())
if err != nil {
fc.log.Error("failed to get claim count", "err", err)
return
}
fc.log.Info("Game info", "addr", fc.fdgAddr, "claims", claimLen, "status", GameStatusString(status))
}
// GetGameStatus returns the current game status.
// 0: In Progress
// 1: Challenger Won
// 2: Defender Won
func (fc *FaultCaller) GetGameStatus(ctx context.Context) (uint8, error) {
return fc.Status(&bind.CallOpts{Context: ctx})
}
func (fc *FaultCaller) LogGameStatus() {
status, err := fc.GetGameStatus(context.Background())
if err != nil {
fc.log.Error("failed to get game status", "err", err)
return
}
fc.log.Info("Game status", "status", GameStatusString(status))
}
// GetClaimDataLength returns the number of claims in the game.
func (fc *FaultCaller) GetClaimDataLength(ctx context.Context) (*big.Int, error) {
return fc.ClaimDataLen(&bind.CallOpts{Context: ctx})
}
func (fc *FaultCaller) LogClaimDataLength() {
claimLen, err := fc.GetClaimDataLength(context.Background())
if err != nil {
fc.log.Error("failed to get claim count", "err", err)
return
}
fc.log.Info("Number of claims", "length", claimLen)
}
// GameStatusString returns the current game status as a string.
func GameStatusString(status uint8) string {
switch status {
case 0:
return "In Progress"
case 1:
return "Challenger Won"
case 2:
return "Defender Won"
default:
return "Unknown"
}
}
package fault
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
var (
testAddr = common.HexToAddress("0x1234567890123456789012345678901234567890")
errMock = errors.New("mock error")
)
type mockFaultDisputeGameCaller struct {
status uint8
errStatus bool
claimDataLen *big.Int
errClaimDataLen bool
}
func (m *mockFaultDisputeGameCaller) Status(opts *bind.CallOpts) (uint8, error) {
if m.errStatus {
return 0, errMock
}
return m.status, nil
}
func (m *mockFaultDisputeGameCaller) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) {
if m.errClaimDataLen {
return nil, errMock
}
return m.claimDataLen, nil
}
func TestFaultCaller_GetGameStatus(t *testing.T) {
tests := []struct {
name string
caller FaultDisputeGameCaller
expectedStatus uint8
expectedErr error
}{
{
name: "success",
caller: &mockFaultDisputeGameCaller{
status: 1,
},
expectedStatus: 1,
expectedErr: nil,
},
{
name: "error",
caller: &mockFaultDisputeGameCaller{
errStatus: true,
},
expectedStatus: 0,
expectedErr: errMock,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fc := NewFaultCaller(testAddr, test.caller, nil)
status, err := fc.GetGameStatus(context.Background())
require.Equal(t, test.expectedStatus, status)
require.Equal(t, test.expectedErr, err)
})
}
}
func TestFaultCaller_GetClaimDataLength(t *testing.T) {
tests := []struct {
name string
caller FaultDisputeGameCaller
expectedClaimDataLen *big.Int
expectedErr error
}{
{
name: "success",
caller: &mockFaultDisputeGameCaller{
claimDataLen: big.NewInt(1),
},
expectedClaimDataLen: big.NewInt(1),
expectedErr: nil,
},
{
name: "error",
caller: &mockFaultDisputeGameCaller{
errClaimDataLen: true,
},
expectedClaimDataLen: nil,
expectedErr: errMock,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fc := NewFaultCaller(testAddr, test.caller, nil)
claimDataLen, err := fc.GetClaimDataLength(context.Background())
require.Equal(t, test.expectedClaimDataLen, claimDataLen)
require.Equal(t, test.expectedErr, err)
})
}
}
...@@ -71,10 +71,13 @@ type StepData struct { ...@@ -71,10 +71,13 @@ type StepData struct {
// AttemptStep determines what step should occur for a given leaf claim. // AttemptStep determines what step should occur for a given leaf claim.
// An error will be returned if the claim is not at the max depth. // An error will be returned if the claim is not at the max depth.
func (s *Solver) AttemptStep(claim Claim) (StepData, error) { func (s *Solver) AttemptStep(claim Claim, agreeWithClaimLevel bool) (StepData, error) {
if claim.Depth() != s.gameDepth { if claim.Depth() != s.gameDepth {
return StepData{}, errors.New("cannot step on non-leaf claims") return StepData{}, errors.New("cannot step on non-leaf claims")
} }
if agreeWithClaimLevel {
return StepData{}, errors.New("cannot step on claims we agree with")
}
claimCorrect, err := s.agreeWithClaim(claim.ClaimData) claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil { if err != nil {
return StepData{}, err return StepData{}, err
......
...@@ -111,18 +111,29 @@ func TestAttemptStep(t *testing.T) { ...@@ -111,18 +111,29 @@ func TestAttemptStep(t *testing.T) {
}, },
} }
step, err := solver.AttemptStep(bottom) step, err := solver.AttemptStep(bottom, false)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, bottom, step.LeafClaim) require.Equal(t, bottom, step.LeafClaim)
require.True(t, step.IsAttack) require.True(t, step.IsAttack)
require.Equal(t, step.PreState, BuildAlphabetPreimage(3, "d")) require.Equal(t, step.PreState, BuildAlphabetPreimage(3, "d"))
_, err = solver.AttemptStep(middle) _, err = solver.AttemptStep(middle, false)
require.Error(t, err) require.Error(t, err)
step, err = solver.AttemptStep(zero) step, err = solver.AttemptStep(zero, false)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, zero, step.LeafClaim) require.Equal(t, zero, step.LeafClaim)
require.True(t, step.IsAttack) require.True(t, step.IsAttack)
require.Equal(t, canonicalProvider.AbsolutePreState(), step.PreState) require.Equal(t, canonicalProvider.AbsolutePreState(), step.PreState)
} }
func TestAttempStep_AgreeWithClaimLevel_Fails(t *testing.T) {
maxDepth := 3
canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(maxDepth))
solver := NewSolver(maxDepth, canonicalProvider)
_, _, middle, _ := createTestClaims()
step, err := solver.AttemptStep(middle, true)
require.Error(t, err)
require.Equal(t, StepData{}, step)
}
...@@ -38,6 +38,11 @@ var ( ...@@ -38,6 +38,11 @@ var (
Usage: "Temporary hardcoded flag if we agree or disagree with the proposed output.", Usage: "Temporary hardcoded flag if we agree or disagree with the proposed output.",
EnvVars: prefixEnvVars("AGREE_WITH_PROPOSED_OUTPUT"), EnvVars: prefixEnvVars("AGREE_WITH_PROPOSED_OUTPUT"),
} }
GameDepthFlag = &cli.IntFlag{
Name: "game-depth",
Usage: "Depth of the game tree.",
EnvVars: prefixEnvVars("GAME_DEPTH"),
}
// Optional Flags // Optional Flags
) )
...@@ -47,6 +52,7 @@ var requiredFlags = []cli.Flag{ ...@@ -47,6 +52,7 @@ var requiredFlags = []cli.Flag{
DGFAddressFlag, DGFAddressFlag,
AlphabetFlag, AlphabetFlag,
AgreeWithProposedOutputFlag, AgreeWithProposedOutputFlag,
GameDepthFlag,
} }
// optionalFlags is a list of unchecked cli flags // optionalFlags is a list of unchecked cli flags
......
...@@ -4,7 +4,6 @@ set -euo pipefail ...@@ -4,7 +4,6 @@ set -euo pipefail
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd) DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$DIR" cd "$DIR"
# build the challenger to keep it up to date
make make
cd .. cd ..
...@@ -21,21 +20,17 @@ CHARLIE_KEY="74feb147d72bfae943e6b4e483410933d9e447d5dc47d52432dcc2c1454dabb7" ...@@ -21,21 +20,17 @@ CHARLIE_KEY="74feb147d72bfae943e6b4e483410933d9e447d5dc47d52432dcc2c1454dabb7"
MALLORY_ADDRESS="0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4" MALLORY_ADDRESS="0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4"
MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715" MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
echo "----------------------------------------------------------------" echo "----------------------------------------------------------------"
echo " - Fetching balance of the sponsor" echo " - Fetching balance of the sponsor"
echo " - Balance: $(cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)" echo " - Balance: $(cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)"
echo "----------------------------------------------------------------" echo "----------------------------------------------------------------"
echo "Funding Charlie" echo "Funding Charlie"
cast send $CHARLIE_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR cast send $CHARLIE_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR
echo "Funding Mallory" echo "Funding Mallory"
cast send $MALLORY_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR cast send $MALLORY_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR
# Fault game type = 0 # Fault game type = 0
GAME_TYPE=0 GAME_TYPE=0
# Root claim commits to the entire trace. # Root claim commits to the entire trace.
...@@ -45,8 +40,6 @@ ROOT_CLAIM=$(cast keccak $(cast abi-encode "f(uint256,uint256)" 15 122)) ...@@ -45,8 +40,6 @@ ROOT_CLAIM=$(cast keccak $(cast abi-encode "f(uint256,uint256)" 15 122))
# Doesn't matter right now since we're not deleting outputs, so just set it to 1 # Doesn't matter right now since we're not deleting outputs, so just set it to 1
EXTRA_DATA=$(cast to-bytes32 1) EXTRA_DATA=$(cast to-bytes32 1)
echo "Initializing the game" echo "Initializing the game"
cast call --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA cast call --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA
cast send --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA cast send --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd) DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$DIR" cd "$DIR"
...@@ -15,4 +14,11 @@ MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715" ...@@ -15,4 +14,11 @@ MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d" FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
./bin/op-challenger --l1-eth-rpc http://localhost:8545 --alphabet "abcdexyz" --game-address $FAULT_GAME_ADDRESS --private-key $MALLORY_KEY --num-confirmations 1 --agree-with-proposed-output=false ./bin/op-challenger \
--l1-eth-rpc http://localhost:8545 \
--alphabet "abcdexyz" \
--game-address $FAULT_GAME_ADDRESS \
--private-key $MALLORY_KEY \
--num-confirmations 1 \
--game-depth 4 \
--agree-with-proposed-output=false
package op_e2e
import (
"time"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
)
// fakePoS is a testing-only utility to attach to Geth,
// to build a fake proof-of-stake L1 chain with fixed block time and basic lagging safe/finalized blocks.
type fakePoS struct {
clock clock.Clock
eth *eth.Ethereum
log log.Logger
blockTime uint64
finalizedDistance uint64
safeDistance uint64
engineAPI *catalyst.ConsensusAPI
sub ethereum.Subscription
}
func (f *fakePoS) Start() error {
if advancing, ok := f.clock.(*clock.AdvancingClock); ok {
advancing.Start()
}
f.sub = event.NewSubscription(func(quit <-chan struct{}) error {
// poll every half a second: enough to catch up with any block time when ticks are missed
t := f.clock.NewTicker(time.Second / 2)
for {
select {
case now := <-t.Ch():
chain := f.eth.BlockChain()
head := chain.CurrentBlock()
finalized := chain.CurrentFinalBlock()
if finalized == nil { // fallback to genesis if nothing is finalized
finalized = chain.Genesis().Header()
}
safe := chain.CurrentSafeBlock()
if safe == nil { // fallback to finalized if nothing is safe
safe = finalized
}
if head.Number.Uint64() > f.finalizedDistance { // progress finalized block, if we can
finalized = f.eth.BlockChain().GetHeaderByNumber(head.Number.Uint64() - f.finalizedDistance)
}
if head.Number.Uint64() > f.safeDistance { // progress safe block, if we can
safe = f.eth.BlockChain().GetHeaderByNumber(head.Number.Uint64() - f.safeDistance)
}
// start building the block as soon as we are past the current head time
if head.Time >= uint64(now.Unix()) {
continue
}
newBlockTime := head.Time + f.blockTime
if time.Unix(int64(newBlockTime), 0).Add(5 * time.Minute).Before(f.clock.Now()) {
// We're a long way behind, let's skip some blocks...
newBlockTime = uint64(f.clock.Now().Unix())
}
res, err := f.engineAPI.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{
HeadBlockHash: head.Hash(),
SafeBlockHash: safe.Hash(),
FinalizedBlockHash: finalized.Hash(),
}, &engine.PayloadAttributes{
Timestamp: newBlockTime,
Random: common.Hash{},
SuggestedFeeRecipient: head.Coinbase,
})
if err != nil {
f.log.Error("failed to start building L1 block", "err", err)
continue
}
if res.PayloadID == nil {
f.log.Error("failed to start block building", "res", res)
continue
}
// wait with sealing, if we are not behind already
delay := time.Unix(int64(newBlockTime), 0).Sub(f.clock.Now())
tim := f.clock.NewTimer(delay)
select {
case <-tim.Ch():
// no-op
case <-quit:
tim.Stop()
return nil
}
payload, err := f.engineAPI.GetPayloadV1(*res.PayloadID)
if err != nil {
f.log.Error("failed to finish building L1 block", "err", err)
continue
}
if _, err := f.engineAPI.NewPayloadV1(*payload); err != nil {
f.log.Error("failed to insert built L1 block", "err", err)
continue
}
if _, err := f.engineAPI.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{
HeadBlockHash: payload.BlockHash,
SafeBlockHash: safe.Hash(),
FinalizedBlockHash: finalized.Hash(),
}, nil); err != nil {
f.log.Error("failed to make built L1 block canonical", "err", err)
continue
}
case <-quit:
return nil
}
}
})
return nil
}
func (f *fakePoS) Stop() error {
f.sub.Unsubscribe()
if advancing, ok := f.clock.(*clock.AdvancingClock); ok {
advancing.Stop()
}
return nil
}
package op_e2e
import (
"context"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/stretchr/testify/require"
)
func TestTimeTravel(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
delete(cfg.Nodes, "verifier")
cfg.SupportL1TimeTravel = true
sys, err := cfg.Start()
require.Nil(t, err, "Error starting up system")
defer sys.Close()
l1Client := sys.Clients["l1"]
preTravel, err := l1Client.BlockByNumber(context.Background(), nil)
require.NoError(t, err)
sys.TimeTravelClock.AdvanceTime(24 * time.Hour)
// Check that the L1 chain reaches the new time reasonably quickly (ie without taking a week)
// It should be able to jump straight to the new time with just a single block
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()
err = e2eutils.WaitFor(ctx, time.Second, func() (bool, error) {
postTravel, err := l1Client.BlockByNumber(context.Background(), nil)
if err != nil {
return false, err
}
diff := time.Duration(postTravel.Time()-preTravel.Time()) * time.Second
return diff.Hours() > 23, nil
})
require.NoError(t, err)
}
...@@ -9,9 +9,9 @@ import ( ...@@ -9,9 +9,9 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
...@@ -21,7 +21,6 @@ import ( ...@@ -21,7 +21,6 @@ import (
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
...@@ -110,7 +109,7 @@ func waitForBlock(number *big.Int, client *ethclient.Client, timeout time.Durati ...@@ -110,7 +109,7 @@ func waitForBlock(number *big.Int, client *ethclient.Client, timeout time.Durati
} }
} }
func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, opts ...GethOption) (*node.Node, *eth.Ethereum, error) { func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, c clock.Clock, opts ...GethOption) (*node.Node, *eth.Ethereum, error) {
ethConfig := &ethconfig.Config{ ethConfig := &ethconfig.Config{
NetworkId: cfg.DeployConfig.L1ChainID, NetworkId: cfg.DeployConfig.L1ChainID,
Genesis: genesis, Genesis: genesis,
...@@ -134,6 +133,7 @@ func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, opts ...GethOption) (* ...@@ -134,6 +133,7 @@ func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, opts ...GethOption) (*
// Instead of running a whole beacon node, we run this fake-proof-of-stake sidecar that sequences L1 blocks using the Engine API. // Instead of running a whole beacon node, we run this fake-proof-of-stake sidecar that sequences L1 blocks using the Engine API.
l1Node.RegisterLifecycle(&fakePoS{ l1Node.RegisterLifecycle(&fakePoS{
clock: c,
eth: l1Eth, eth: l1Eth,
log: log.Root(), // geth logger is global anyway. Would be nice to replace with a local logger though. log: log.Root(), // geth logger is global anyway. Would be nice to replace with a local logger though.
blockTime: cfg.DeployConfig.L1BlockTime, blockTime: cfg.DeployConfig.L1BlockTime,
...@@ -146,104 +146,6 @@ func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, opts ...GethOption) (* ...@@ -146,104 +146,6 @@ func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, opts ...GethOption) (*
return l1Node, l1Eth, nil return l1Node, l1Eth, nil
} }
// fakePoS is a testing-only utility to attach to Geth,
// to build a fake proof-of-stake L1 chain with fixed block time and basic lagging safe/finalized blocks.
type fakePoS struct {
eth *eth.Ethereum
log log.Logger
blockTime uint64
finalizedDistance uint64
safeDistance uint64
engineAPI *catalyst.ConsensusAPI
sub ethereum.Subscription
}
func (f *fakePoS) Start() error {
f.sub = event.NewSubscription(func(quit <-chan struct{}) error {
// poll every half a second: enough to catch up with any block time when ticks are missed
t := time.NewTicker(time.Second / 2)
for {
select {
case now := <-t.C:
chain := f.eth.BlockChain()
head := chain.CurrentBlock()
finalized := chain.CurrentFinalBlock()
if finalized == nil { // fallback to genesis if nothing is finalized
finalized = chain.Genesis().Header()
}
safe := chain.CurrentSafeBlock()
if safe == nil { // fallback to finalized if nothing is safe
safe = finalized
}
if head.Number.Uint64() > f.finalizedDistance { // progress finalized block, if we can
finalized = f.eth.BlockChain().GetHeaderByNumber(head.Number.Uint64() - f.finalizedDistance)
}
if head.Number.Uint64() > f.safeDistance { // progress safe block, if we can
safe = f.eth.BlockChain().GetHeaderByNumber(head.Number.Uint64() - f.safeDistance)
}
// start building the block as soon as we are past the current head time
if head.Time >= uint64(now.Unix()) {
continue
}
res, err := f.engineAPI.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{
HeadBlockHash: head.Hash(),
SafeBlockHash: safe.Hash(),
FinalizedBlockHash: finalized.Hash(),
}, &engine.PayloadAttributes{
Timestamp: head.Time + f.blockTime,
Random: common.Hash{},
SuggestedFeeRecipient: head.Coinbase,
})
if err != nil {
f.log.Error("failed to start building L1 block", "err", err)
continue
}
if res.PayloadID == nil {
f.log.Error("failed to start block building", "res", res)
continue
}
// wait with sealing, if we are not behind already
delay := time.Until(time.Unix(int64(head.Time+f.blockTime), 0))
tim := time.NewTimer(delay)
select {
case <-tim.C:
// no-op
case <-quit:
tim.Stop()
return nil
}
payload, err := f.engineAPI.GetPayloadV1(*res.PayloadID)
if err != nil {
f.log.Error("failed to finish building L1 block", "err", err)
continue
}
if _, err := f.engineAPI.NewPayloadV1(*payload); err != nil {
f.log.Error("failed to insert built L1 block", "err", err)
continue
}
if _, err := f.engineAPI.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{
HeadBlockHash: payload.BlockHash,
SafeBlockHash: safe.Hash(),
FinalizedBlockHash: finalized.Hash(),
}, nil); err != nil {
f.log.Error("failed to make built L1 block canonical", "err", err)
continue
}
case <-quit:
return nil
}
}
})
return nil
}
func (f *fakePoS) Stop() error {
f.sub.Unsubscribe()
return nil
}
func defaultNodeConfig(name string, jwtPath string) *node.Config { func defaultNodeConfig(name string, jwtPath string) *node.Config {
return &node.Config{ return &node.Config{
Name: name, Name: name,
......
...@@ -241,6 +241,9 @@ type SystemConfig struct { ...@@ -241,6 +241,9 @@ type SystemConfig struct {
// Target L1 tx size for the batcher transactions // Target L1 tx size for the batcher transactions
BatcherTargetL1TxSizeBytes uint64 BatcherTargetL1TxSizeBytes uint64
// SupportL1TimeTravel determines if the L1 node supports quickly skipping forward in time
SupportL1TimeTravel bool
} }
type System struct { type System struct {
...@@ -258,6 +261,13 @@ type System struct { ...@@ -258,6 +261,13 @@ type System struct {
L2OutputSubmitter *l2os.L2OutputSubmitter L2OutputSubmitter *l2os.L2OutputSubmitter
BatchSubmitter *bss.BatchSubmitter BatchSubmitter *bss.BatchSubmitter
Mocknet mocknet.Mocknet Mocknet mocknet.Mocknet
// TimeTravelClock is nil unless SystemConfig.SupportL1TimeTravel was set to true
// It provides access to the clock instance used by the L1 node. Calling TimeTravelClock.AdvanceBy
// allows tests to quickly time travel L1 into the future.
// Note that this time travel may occur in a single block, creating a very large difference in the Time
// on sequential blocks.
TimeTravelClock *clock.AdvancingClock
} }
func (sys *System) NodeEndpoint(name string) string { func (sys *System) NodeEndpoint(name string) string {
...@@ -339,6 +349,12 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { ...@@ -339,6 +349,12 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) {
} }
}() }()
c := clock.SystemClock
if cfg.SupportL1TimeTravel {
sys.TimeTravelClock = clock.NewAdvancingClock(100 * time.Millisecond)
c = sys.TimeTravelClock
}
l1Genesis, err := genesis.BuildL1DeveloperGenesis(cfg.DeployConfig) l1Genesis, err := genesis.BuildL1DeveloperGenesis(cfg.DeployConfig)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -412,7 +428,7 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { ...@@ -412,7 +428,7 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) {
sys.RollupConfig = &defaultConfig sys.RollupConfig = &defaultConfig
// Initialize nodes // Initialize nodes
l1Node, l1Backend, err := initL1Geth(&cfg, l1Genesis, cfg.GethOptions["l1"]...) l1Node, l1Backend, err := initL1Geth(&cfg, l1Genesis, c, cfg.GethOptions["l1"]...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
package clock
import (
"sync/atomic"
"time"
)
type AdvancingClock struct {
*DeterministicClock
systemTime Clock
ticker Ticker
advanceEvery time.Duration
quit chan interface{}
running atomic.Bool
lastTick time.Time
}
// NewAdvancingClock creates a clock that, when started, advances at the same rate as the system clock but
// can also be advanced arbitrary amounts using the AdvanceTime method.
// Unlike the system clock, time does not progress smoothly but only increments when AdvancedTime is called or
// approximately after advanceEvery duration has elapsed. When advancing based on the system clock, the total time
// the system clock has advanced is added to the current time, preventing time differences from building up over time.
func NewAdvancingClock(advanceEvery time.Duration) *AdvancingClock {
now := SystemClock.Now()
return &AdvancingClock{
DeterministicClock: NewDeterministicClock(now),
systemTime: SystemClock,
advanceEvery: advanceEvery,
quit: make(chan interface{}),
lastTick: now,
}
}
func (c *AdvancingClock) Start() {
if !c.running.CompareAndSwap(false, true) {
// Already running
return
}
c.ticker = c.systemTime.NewTicker(c.advanceEvery)
go func() {
for {
select {
case now := <-c.ticker.Ch():
c.onTick(now)
case <-c.quit:
return
}
}
}()
}
func (c *AdvancingClock) Stop() {
if !c.running.CompareAndSwap(true, false) {
// Already stopped
return
}
c.quit <- nil
}
func (c *AdvancingClock) onTick(now time.Time) {
if !now.After(c.lastTick) {
// Time hasn't progressed for some reason, so do nothing
return
}
// Advance time by however long it has been since the last update.
// Ensures we don't drift from system time by more and more over time
advanceBy := now.Sub(c.lastTick)
c.AdvanceTime(advanceBy)
c.lastTick = now
}
package clock
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestAdvancingClock_AdvancesByTimeBetweenTicks(t *testing.T) {
clock, realTime := newTestAdvancingClock(1 * time.Second)
clock.Start()
defer clock.Stop()
eventTicker := clock.NewTicker(1 * time.Second)
start := clock.Now()
realTime.AdvanceTime(1 * time.Second)
require.Equal(t, start.Add(1*time.Second), <-eventTicker.Ch(), "should trigger events when advancing")
require.Equal(t, start.Add(1*time.Second), clock.Now(), "Should advance on single tick")
start = clock.Now()
realTime.AdvanceTime(15 * time.Second)
require.Equal(t, start.Add(15*time.Second), <-eventTicker.Ch(), "should trigger events when advancing")
require.Equal(t, start.Add(15*time.Second), clock.Now(), "Should advance by time between ticks")
}
func TestAdvancingClock_Stop(t *testing.T) {
clock, realTime := newTestAdvancingClock(1 * time.Second)
clock.Start()
defer clock.Stop()
eventTicker := clock.NewTicker(1 * time.Second)
// Stop the clock again
clock.Stop()
start := clock.Now()
realTime.AdvanceTime(15 * time.Second)
clock.Start()
// Trigger the next tick
realTime.AdvanceTime(1 * time.Second)
// Time advances by the whole time the clock was stopped
// Note: if events were triggered while the clock was stopped, this event would be for the wrong time
require.Equal(t, start.Add(16*time.Second), <-eventTicker.Ch(), "should trigger events again after restarting")
require.Equal(t, start.Add(16*time.Second), clock.Now(), "Should advance by time between ticks after restarting")
}
func newTestAdvancingClock(advanceEvery time.Duration) (*AdvancingClock, *DeterministicClock) {
systemTime := NewDeterministicClock(time.UnixMilli(1000))
clock := &AdvancingClock{
DeterministicClock: NewDeterministicClock(time.UnixMilli(5000)),
systemTime: systemTime,
advanceEvery: advanceEvery,
quit: make(chan interface{}),
lastTick: systemTime.Now(),
}
return clock, systemTime
}
...@@ -13,6 +13,8 @@ type Clock interface { ...@@ -13,6 +13,8 @@ type Clock interface {
// It is equivalent to time.After // It is equivalent to time.After
After(d time.Duration) <-chan time.Time After(d time.Duration) <-chan time.Time
AfterFunc(d time.Duration, f func()) Timer
// NewTicker returns a new Ticker containing a channel that will send // NewTicker returns a new Ticker containing a channel that will send
// the current time on the channel after each tick. The period of the // the current time on the channel after each tick. The period of the
// ticks is specified by the duration argument. The ticker will adjust // ticks is specified by the duration argument. The ticker will adjust
...@@ -20,6 +22,10 @@ type Clock interface { ...@@ -20,6 +22,10 @@ type Clock interface {
// The duration d must be greater than zero; if not, NewTicker will // The duration d must be greater than zero; if not, NewTicker will
// panic. Stop the ticker to release associated resources. // panic. Stop the ticker to release associated resources.
NewTicker(d time.Duration) Ticker NewTicker(d time.Duration) Ticker
// NewTimer creates a new Timer that will send
// the current time on its channel after at least duration d.
NewTimer(d time.Duration) Timer
} }
// A Ticker holds a channel that delivers "ticks" of a clock at intervals // A Ticker holds a channel that delivers "ticks" of a clock at intervals
...@@ -38,6 +44,25 @@ type Ticker interface { ...@@ -38,6 +44,25 @@ type Ticker interface {
Reset(d time.Duration) Reset(d time.Duration)
} }
// Timer represents a single event.
type Timer interface {
// Ch returns the channel for the ticker. Equivalent to time.Timer.C
Ch() <-chan time.Time
// Stop prevents the Timer from firing.
// It returns true if the call stops the timer, false if the timer has already
// expired or been stopped.
// Stop does not close the channel, to prevent a read from the channel succeeding
// incorrectly.
//
// For a timer created with AfterFunc(d, f), if t.Stop returns false, then the timer
// has already expired and the function f has been started in its own goroutine;
// Stop does not wait for f to complete before returning.
// If the caller needs to know whether f is completed, it must coordinate
// with f explicitly.
Stop() bool
}
// SystemClock provides an instance of Clock that uses the system clock via methods in the time package. // SystemClock provides an instance of Clock that uses the system clock via methods in the time package.
var SystemClock Clock = systemClock{} var SystemClock Clock = systemClock{}
...@@ -63,3 +88,19 @@ func (t *SystemTicker) Ch() <-chan time.Time { ...@@ -63,3 +88,19 @@ func (t *SystemTicker) Ch() <-chan time.Time {
func (s systemClock) NewTicker(d time.Duration) Ticker { func (s systemClock) NewTicker(d time.Duration) Ticker {
return &SystemTicker{time.NewTicker(d)} return &SystemTicker{time.NewTicker(d)}
} }
func (s systemClock) NewTimer(d time.Duration) Timer {
return &SystemTimer{time.NewTimer(d)}
}
type SystemTimer struct {
*time.Timer
}
func (t *SystemTimer) Ch() <-chan time.Time {
return t.C
}
func (s systemClock) AfterFunc(d time.Duration, f func()) Timer {
return &SystemTimer{time.AfterFunc(d, f)}
}
...@@ -29,6 +29,43 @@ func (t task) fire(now time.Time) bool { ...@@ -29,6 +29,43 @@ func (t task) fire(now time.Time) bool {
return false return false
} }
type timer struct {
f func()
ch chan time.Time
due time.Time
stopped bool
run bool
sync.Mutex
}
func (t *timer) isDue(now time.Time) bool {
t.Lock()
defer t.Unlock()
return !t.due.After(now)
}
func (t *timer) fire(now time.Time) bool {
t.Lock()
defer t.Unlock()
if !t.stopped {
t.f()
t.run = true
}
return false
}
func (t *timer) Ch() <-chan time.Time {
return t.ch
}
func (t *timer) Stop() bool {
t.Lock()
defer t.Unlock()
r := !t.stopped && !t.run
t.stopped = true
return r
}
type ticker struct { type ticker struct {
c Clock c Clock
ch chan time.Time ch chan time.Time
...@@ -70,8 +107,12 @@ func (t *ticker) fire(now time.Time) bool { ...@@ -70,8 +107,12 @@ func (t *ticker) fire(now time.Time) bool {
if t.stopped { if t.stopped {
return false return false
} }
t.ch <- now // Publish without blocking and only update due time if we publish successfully
t.nextDue = now.Add(t.period) select {
case t.ch <- now:
t.nextDue = now.Add(t.period)
default:
}
return true return true
} }
...@@ -110,6 +151,18 @@ func (s *DeterministicClock) After(d time.Duration) <-chan time.Time { ...@@ -110,6 +151,18 @@ func (s *DeterministicClock) After(d time.Duration) <-chan time.Time {
return ch return ch
} }
func (s *DeterministicClock) AfterFunc(d time.Duration, f func()) Timer {
s.lock.Lock()
defer s.lock.Unlock()
timer := &timer{f: f, due: s.now.Add(d)}
if d.Nanoseconds() == 0 {
timer.fire(s.now)
} else {
s.addPending(timer)
}
return timer
}
func (s *DeterministicClock) NewTicker(d time.Duration) Ticker { func (s *DeterministicClock) NewTicker(d time.Duration) Ticker {
if d <= 0 { if d <= 0 {
panic("Continuously firing tickers are a really bad idea") panic("Continuously firing tickers are a really bad idea")
...@@ -127,6 +180,21 @@ func (s *DeterministicClock) NewTicker(d time.Duration) Ticker { ...@@ -127,6 +180,21 @@ func (s *DeterministicClock) NewTicker(d time.Duration) Ticker {
return t return t
} }
func (s *DeterministicClock) NewTimer(d time.Duration) Timer {
s.lock.Lock()
defer s.lock.Unlock()
ch := make(chan time.Time, 1)
t := &timer{
f: func() {
ch <- s.now
},
ch: ch,
due: s.now.Add(d),
}
s.addPending(t)
return t
}
func (s *DeterministicClock) addPending(t action) { func (s *DeterministicClock) addPending(t action) {
s.pending = append(s.pending, t) s.pending = append(s.pending, t)
select { select {
......
...@@ -2,6 +2,8 @@ package clock ...@@ -2,6 +2,8 @@ package clock
import ( import (
"context" "context"
"sync"
"sync/atomic"
"testing" "testing"
"time" "time"
...@@ -62,6 +64,64 @@ func TestAfter(t *testing.T) { ...@@ -62,6 +64,64 @@ func TestAfter(t *testing.T) {
}) })
} }
func TestAfterFunc(t *testing.T) {
t.Run("ZeroExecutesImmediately", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000))
ran := new(atomic.Bool)
timer := clock.AfterFunc(0, func() { ran.Store(true) })
require.True(t, ran.Load(), "duration should already have been reached")
require.False(t, timer.Stop(), "Stop should return false after executing")
})
t.Run("CompletesWhenTimeAdvances", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000))
ran := new(atomic.Bool)
timer := clock.AfterFunc(500*time.Millisecond, func() { ran.Store(true) })
require.False(t, ran.Load(), "should not complete immediately")
clock.AdvanceTime(499 * time.Millisecond)
require.False(t, ran.Load(), "should not complete before time is due")
clock.AdvanceTime(1 * time.Millisecond)
require.True(t, ran.Load(), "should complete when time is reached")
require.False(t, timer.Stop(), "Stop should return false after executing")
})
t.Run("CompletesWhenTimeAdvancesPastDue", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000))
ran := new(atomic.Bool)
timer := clock.AfterFunc(500*time.Millisecond, func() { ran.Store(true) })
require.False(t, ran.Load(), "should not complete immediately")
clock.AdvanceTime(9000 * time.Millisecond)
require.True(t, ran.Load(), "should complete when time is reached")
require.False(t, timer.Stop(), "Stop should return false after executing")
})
t.Run("RegisterAsPending", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000))
ran := new(atomic.Bool)
clock.AfterFunc(500*time.Millisecond, func() { ran.Store(true) })
ctx, cancelFunc := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFunc()
require.True(t, clock.WaitForNewPendingTask(ctx), "should have added a new pending task")
})
t.Run("DoNotRunIfStopped", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000))
ran := new(atomic.Bool)
timer := clock.AfterFunc(500*time.Millisecond, func() { ran.Store(true) })
require.False(t, ran.Load(), "should not complete immediately")
require.True(t, timer.Stop(), "Stop should return true on first call")
require.False(t, timer.Stop(), "Stop should return false on subsequent calls")
clock.AdvanceTime(9000 * time.Millisecond)
require.False(t, ran.Load(), "should not run when time is reached")
})
}
func TestNewTicker(t *testing.T) { func TestNewTicker(t *testing.T) {
t.Run("FiresAfterEachDuration", func(t *testing.T) { t.Run("FiresAfterEachDuration", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000)) clock := NewDeterministicClock(time.UnixMilli(1000))
...@@ -97,6 +157,38 @@ func TestNewTicker(t *testing.T) { ...@@ -97,6 +157,38 @@ func TestNewTicker(t *testing.T) {
require.Len(t, ticker.Ch(), 0, "should not fire until due again") require.Len(t, ticker.Ch(), 0, "should not fire until due again")
}) })
t.Run("SkipsFiringWhenProcessingIsSlow", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000))
ticker := clock.NewTicker(5 * time.Second)
// Fire once to fill the channel queue
clock.AdvanceTime(5 * time.Second)
firstEventTime := clock.Now()
var startProcessing sync.WaitGroup
startProcessing.Add(1)
processedTicks := make(chan time.Time)
go func() {
startProcessing.Wait()
// Read two events then exit
for i := 0; i < 2; i++ {
event := <-ticker.Ch()
processedTicks <- event
}
}()
// Advance time further before processing of events has started
// Can't publish any further events to the channel but shouldn't block
clock.AdvanceTime(30 * time.Second)
// Allow processing to start
startProcessing.Done()
require.Equal(t, firstEventTime, <-processedTicks, "Should process first event")
clock.AdvanceTime(5 * time.Second)
require.Equal(t, clock.Now(), <-processedTicks, "Should skip to latest time")
})
t.Run("StopFiring", func(t *testing.T) { t.Run("StopFiring", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000)) clock := NewDeterministicClock(time.UnixMilli(1000))
ticker := clock.NewTicker(5 * time.Second) ticker := clock.NewTicker(5 * time.Second)
...@@ -158,6 +250,46 @@ func TestNewTicker(t *testing.T) { ...@@ -158,6 +250,46 @@ func TestNewTicker(t *testing.T) {
}) })
} }
func TestNewTimer(t *testing.T) {
t.Run("FireOnceAfterDuration", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000))
timer := clock.NewTimer(5 * time.Second)
require.Len(t, timer.Ch(), 0, "should not fire immediately")
clock.AdvanceTime(4 * time.Second)
require.Len(t, timer.Ch(), 0, "should not fire before due")
clock.AdvanceTime(1 * time.Second)
require.Len(t, timer.Ch(), 1, "should fire when due")
require.Equal(t, clock.Now(), <-timer.Ch(), "should post current time")
clock.AdvanceTime(6 * time.Second)
require.Len(t, timer.Ch(), 0, "should not fire when due again")
})
t.Run("StopBeforeExecuted", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000))
timer := clock.NewTimer(5 * time.Second)
require.True(t, timer.Stop())
clock.AdvanceTime(10 * time.Second)
require.Len(t, timer.Ch(), 0, "should not fire after stop")
})
t.Run("StopAfterExecuted", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000))
timer := clock.NewTimer(5 * time.Second)
clock.AdvanceTime(10 * time.Second)
require.Len(t, timer.Ch(), 1, "should fire when due")
require.Equal(t, clock.Now(), <-timer.Ch(), "should post current time")
require.False(t, timer.Stop())
})
}
func TestWaitForPending(t *testing.T) { func TestWaitForPending(t *testing.T) {
t.Run("DoNotBlockWhenAlreadyPending", func(t *testing.T) { t.Run("DoNotBlockWhenAlreadyPending", func(t *testing.T) {
clock := NewDeterministicClock(time.UnixMilli(1000)) clock := NewDeterministicClock(time.UnixMilli(1000))
......
...@@ -19,3 +19,9 @@ test: ...@@ -19,3 +19,9 @@ test:
lint: lint:
go vet ./... go vet ./...
.PHONY: test .PHONY: test
tls:
kubectl get secrets op-ufm-client-tls -o yaml | yq '.data."tls.key"' | base64 --decode > tls/tls.key
kubectl get secrets op-ufm-client-tls -o yaml | yq '.data."tls.crt"' | base64 --decode > tls/tls.crt
kubectl get secrets op-ufm-client-tls -o yaml | yq '.data."ca.crt"' | base64 --decode > tls/ca.crt
.PHONY: tls
...@@ -3,6 +3,7 @@ module github.com/ethereum-optimism/optimism/op-ufm ...@@ -3,6 +3,7 @@ module github.com/ethereum-optimism/optimism/op-ufm
go 1.20 go 1.20
require ( require (
cloud.google.com/go/kms v1.12.1
github.com/BurntSushi/toml v1.3.2 github.com/BurntSushi/toml v1.3.2
github.com/ethereum-optimism/optimism/op-service v0.10.14 github.com/ethereum-optimism/optimism/op-service v0.10.14
github.com/ethereum-optimism/optimism/op-signer v0.1.1 github.com/ethereum-optimism/optimism/op-signer v0.1.1
...@@ -11,9 +12,13 @@ require ( ...@@ -11,9 +12,13 @@ require (
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_golang v1.16.0
github.com/rs/cors v1.9.0 github.com/rs/cors v1.9.0
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130
) )
require ( require (
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.0 // indirect
github.com/DataDog/zstd v1.5.2 // indirect github.com/DataDog/zstd v1.5.2 // indirect
github.com/VictoriaMetrics/fastcache v1.10.0 // indirect github.com/VictoriaMetrics/fastcache v1.10.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
...@@ -32,8 +37,13 @@ require ( ...@@ -32,8 +37,13 @@ require (
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect
...@@ -55,10 +65,18 @@ require ( ...@@ -55,10 +65,18 @@ require (
github.com/tklauser/numcpus v0.5.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/urfave/cli v1.22.9 // indirect github.com/urfave/cli v1.22.9 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.1.0 // indirect go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
golang.org/x/sys v0.8.0 // indirect golang.org/x/net v0.12.0 // indirect
golang.org/x/text v0.8.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
google.golang.org/api v0.132.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.56.2 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
) )
This diff is collapsed.
...@@ -5,8 +5,6 @@ import ( ...@@ -5,8 +5,6 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics" "github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
...@@ -33,9 +31,7 @@ func Dial(providerName string, url string) (*InstrumentedEthClient, error) { ...@@ -33,9 +31,7 @@ func Dial(providerName string, url string) (*InstrumentedEthClient, error) {
func (i *InstrumentedEthClient) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) { func (i *InstrumentedEthClient) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) {
start := time.Now() start := time.Now()
log.Debug(">> TransactionByHash", "hash", hash, "provider", i.providerName)
tx, isPending, err := i.c.TransactionByHash(ctx, hash) tx, isPending, err := i.c.TransactionByHash(ctx, hash)
log.Debug("<< TransactionByHash", "tx", tx, "isPending", isPending, "err", err, "hash", hash, "provider", i.providerName)
if err != nil { if err != nil {
if !i.ignorableErrors(err) { if !i.ignorableErrors(err) {
metrics.RecordError(i.providerName, "ethclient.TransactionByHash") metrics.RecordError(i.providerName, "ethclient.TransactionByHash")
......
package main
import (
"context"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"os"
kms "cloud.google.com/go/kms/apiv1"
"cloud.google.com/go/kms/apiv1/kmspb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func main() {
println("kmstool - usage: kmstool <key>")
if len(os.Args) < 2 {
panic("missing <key>")
}
keyName := os.Args[1]
ctx := context.Background()
client, err := kms.NewKeyManagementClient(ctx)
if err != nil {
panic(fmt.Sprintf("failed to create kms client: %w", err))
}
defer client.Close()
addr, err := resolveAddr(ctx, client, keyName)
if err != nil {
panic(fmt.Sprintf("failed to retrieve the key: %w", err))
}
fmt.Printf("ethereum addr: %s", addr)
println()
println()
}
func resolveAddr(ctx context.Context, client *kms.KeyManagementClient, keyName string) (common.Address, error) {
resp, err := client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: keyName})
if err != nil {
return common.Address{}, fmt.Errorf("google kms public key %q lookup: %w", keyName, err)
}
block, _ := pem.Decode([]byte(resp.Pem))
if block == nil {
return common.Address{}, fmt.Errorf("google kms public key %q pem empty: %.130q", keyName, resp.Pem)
}
var info struct {
AlgID pkix.AlgorithmIdentifier
Key asn1.BitString
}
_, err = asn1.Unmarshal(block.Bytes, &info)
if err != nil {
return common.Address{}, fmt.Errorf("google kms public key %q pem block %q: %v", keyName, block.Type, err)
}
wantAlg := asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
if gotAlg := info.AlgID.Algorithm; !gotAlg.Equal(wantAlg) {
return common.Address{}, fmt.Errorf("google kms public key %q asn.1 algorithm %s intead of %s", keyName, gotAlg, wantAlg)
}
return pubKeyAddr(info.Key.Bytes), nil
}
// PubKeyAddr returns the Ethereum address for the (uncompressed) key bytes.
func pubKeyAddr(bytes []byte) common.Address {
digest := crypto.Keccak256(bytes[1:])
var addr common.Address
copy(addr[:], digest[12:])
return addr
}
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
}, },
"scripts": { "scripts": {
"clean": "npx nx run-many --target=clean", "clean": "npx nx run-many --target=clean",
"bindings": "nx bindings @eth-optimism/contracts-bedrock",
"build": "npx nx run-many --target=build", "build": "npx nx run-many --target=build",
"test": "npx nx run-many --target=test", "test": "npx nx run-many --target=test",
"lint": "npx nx run-many --target=lint", "lint": "npx nx run-many --target=lint",
...@@ -26,7 +27,7 @@ ...@@ -26,7 +27,7 @@
"prepare": "husky install", "prepare": "husky install",
"release": "npx nx run-many --target=build --skip-nx-cache && pnpm changeset publish", "release": "npx nx run-many --target=build --skip-nx-cache && pnpm changeset publish",
"release:check": "changeset status --verbose --since=origin/main", "release:check": "changeset status --verbose --since=origin/main",
"release:publish": "npx nx run-many --target=build && pnpm install && pnpm build && changeset publish", "release:publish": "pnpm install --frozen-lockfile && npx nx run-many --target=build && pnpm build && changeset publish",
"release:version": "changeset version && pnpm install --lockfile-only", "release:version": "changeset version && pnpm install --lockfile-only",
"install:foundry": "curl -L https://foundry.paradigm.xyz | bash && pnpm update:foundry", "install:foundry": "curl -L https://foundry.paradigm.xyz | bash && pnpm update:foundry",
"update:foundry": "foundryup -C $(cat .foundryrc)" "update:foundry": "foundryup -C $(cat .foundryrc)"
......
...@@ -83,37 +83,36 @@ FaucetTest:test_nonAdmin_drip_fails() (gas: 262520) ...@@ -83,37 +83,36 @@ FaucetTest:test_nonAdmin_drip_fails() (gas: 262520)
FaucetTest:test_receive_succeeds() (gas: 17401) FaucetTest:test_receive_succeeds() (gas: 17401)
FaucetTest:test_withdraw_nonAdmin_reverts() (gas: 13145) FaucetTest:test_withdraw_nonAdmin_reverts() (gas: 13145)
FaucetTest:test_withdraw_succeeds() (gas: 78359) FaucetTest:test_withdraw_succeeds() (gas: 78359)
FaultDisputeGame_ResolvesCorrectly_Chaos:test_resolvesCorrectly_succeeds() (gas: 111450391) FaultDisputeGame_ResolvesCorrectly_CorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 498839)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 498867) FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 505685)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 505669) FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 502382)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 502410) FaultDisputeGame_ResolvesCorrectly_CorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 505561)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 505545) FaultDisputeGame_ResolvesCorrectly_CorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 504878)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 504906) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 497604)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 497632) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 504450)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 504434) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 501147)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 501175) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 502326)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 502310) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 501643)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 501671) FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17404)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17426)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17917) FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17917)
FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10315) FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10315)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8282) FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8260)
FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17669) FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17669)
FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 415981) FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 416029)
FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 26387) FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 26399)
FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13360) FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13360)
FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 103230) FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 103254)
FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 408100) FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 408148)
FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10968) FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10968)
FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24655) FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24655)
FaultDisputeGame_Test:test_move_simpleAttack_succeeds() (gas: 107344) FaultDisputeGame_Test:test_move_simpleAttack_succeeds() (gas: 107356)
FaultDisputeGame_Test:test_resolve_challengeContested_succeeds() (gas: 224784) FaultDisputeGame_Test:test_resolve_challengeContested_succeeds() (gas: 224820)
FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9657) FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9657)
FaultDisputeGame_Test:test_resolve_rootContested_succeeds() (gas: 109749) FaultDisputeGame_Test:test_resolve_rootContested_succeeds() (gas: 109773)
FaultDisputeGame_Test:test_resolve_rootUncontestedClockNotExpired_succeeds() (gas: 21422) FaultDisputeGame_Test:test_resolve_rootUncontestedClockNotExpired_succeeds() (gas: 21434)
FaultDisputeGame_Test:test_resolve_rootUncontested_succeeds() (gas: 27251) FaultDisputeGame_Test:test_resolve_rootUncontested_succeeds() (gas: 27263)
FaultDisputeGame_Test:test_resolve_teamDeathmatch_succeeds() (gas: 395442) FaultDisputeGame_Test:test_resolve_teamDeathmatch_succeeds() (gas: 395502)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8181) FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8225)
FeeVault_Test:test_constructor_succeeds() (gas: 18185) FeeVault_Test:test_constructor_succeeds() (gas: 18185)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352113) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352113)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2950320) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2950320)
......
...@@ -30,12 +30,12 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver { ...@@ -30,12 +30,12 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
/// @notice The max depth of the game. /// @notice The max depth of the game.
uint256 public immutable MAX_GAME_DEPTH; uint256 public immutable MAX_GAME_DEPTH;
/// @notice The duration of the game.
Duration public immutable GAME_DURATION;
/// @notice A hypervisor that performs single instruction steps on a fault proof program trace. /// @notice A hypervisor that performs single instruction steps on a fault proof program trace.
IBigStepper public immutable VM; IBigStepper public immutable VM;
/// @notice The duration of the game.
Duration internal constant GAME_DURATION = Duration.wrap(7 days);
/// @notice The root claim's position is always at gindex 1. /// @notice The root claim's position is always at gindex 1.
Position internal constant ROOT_POSITION = Position.wrap(1); Position internal constant ROOT_POSITION = Position.wrap(1);
...@@ -58,10 +58,12 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver { ...@@ -58,10 +58,12 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
constructor( constructor(
Claim _absolutePrestate, Claim _absolutePrestate,
uint256 _maxGameDepth, uint256 _maxGameDepth,
Duration _gameDuration,
IBigStepper _vm IBigStepper _vm
) Semver(0, 0, 3) { ) Semver(0, 0, 3) {
ABSOLUTE_PRESTATE = _absolutePrestate; ABSOLUTE_PRESTATE = _absolutePrestate;
MAX_GAME_DEPTH = _maxGameDepth; MAX_GAME_DEPTH = _maxGameDepth;
GAME_DURATION = _gameDuration;
VM = _vm; VM = _vm;
} }
......
...@@ -29,7 +29,12 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init { ...@@ -29,7 +29,12 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init {
function init(Claim rootClaim, Claim absolutePrestate) public { function init(Claim rootClaim, Claim absolutePrestate) public {
super.setUp(); super.setUp();
// Deploy an implementation of the fault game // Deploy an implementation of the fault game
gameImpl = new FaultDisputeGame(absolutePrestate, 4, new AlphabetVM(absolutePrestate)); gameImpl = new FaultDisputeGame(
absolutePrestate,
4,
Duration.wrap(7 days),
new AlphabetVM(absolutePrestate)
);
// Register the game implementation with the factory. // Register the game implementation with the factory.
factory.setImplementation(GAME_TYPE, gameImpl); factory.setImplementation(GAME_TYPE, gameImpl);
// Create a new game. // Create a new game.
......
...@@ -45,5 +45,6 @@ ...@@ -45,5 +45,6 @@
"l1StartingBlockTag": "earliest", "l1StartingBlockTag": "earliest",
"l2GenesisRegolithTimeOffset": "0x0", "l2GenesisRegolithTimeOffset": "0x0",
"faultGameAbsolutePrestate": 140, "faultGameAbsolutePrestate": 140,
"faultGameMaxDepth": 4 "faultGameMaxDepth": 4,
} "faultGameMaxDuration": 604800
\ No newline at end of file }
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
"contracts/**/*.sol" "contracts/**/*.sol"
], ],
"scripts": { "scripts": {
"bindings": "cd ../../op-bindings && make", "bindings": "pnpm bindings:ts && pnpm bindings:go",
"build": "npx nx build:contracts", "bindings:ts": "pnpm generate:addresses && nx generate @eth-optimism/contracts-ts",
"bindings:go": "cd ../../op-bindings && make",
"build": "nx build:contracts",
"prebuild:contracts": "./scripts/verify-foundry-install.sh", "prebuild:contracts": "./scripts/verify-foundry-install.sh",
"build:contracts": "pnpm build:forge", "build:contracts": "pnpm build:forge",
"build:forge": "forge build", "build:forge": "forge build",
......
...@@ -697,6 +697,7 @@ contract Deploy is Deployer { ...@@ -697,6 +697,7 @@ contract Deploy is Deployer {
factory.setImplementation(GameTypes.FAULT, new FaultDisputeGame({ factory.setImplementation(GameTypes.FAULT, new FaultDisputeGame({
_absolutePrestate: absolutePrestate, _absolutePrestate: absolutePrestate,
_maxGameDepth: cfg.faultGameMaxDepth(), _maxGameDepth: cfg.faultGameMaxDepth(),
_gameDuration: Duration.wrap(uint64(cfg.faultGameMaxDuration())),
_vm: faultVm _vm: faultVm
})); }));
console.log("DisputeGameFactory: set `FaultDisputeGame` implementation"); console.log("DisputeGameFactory: set `FaultDisputeGame` implementation");
......
...@@ -47,6 +47,7 @@ contract DeployConfig is Script { ...@@ -47,6 +47,7 @@ contract DeployConfig is Script {
uint256 public l2GenesisRegolithTimeOffset; uint256 public l2GenesisRegolithTimeOffset;
uint256 public faultGameAbsolutePrestate; uint256 public faultGameAbsolutePrestate;
uint256 public faultGameMaxDepth; uint256 public faultGameMaxDepth;
uint256 public faultGameMaxDuration;
constructor(string memory _path) { constructor(string memory _path) {
console.log("DeployConfig: reading file %s", _path); console.log("DeployConfig: reading file %s", _path);
...@@ -88,6 +89,7 @@ contract DeployConfig is Script { ...@@ -88,6 +89,7 @@ contract DeployConfig is Script {
if (block.chainid == 900) { if (block.chainid == 900) {
faultGameAbsolutePrestate = stdJson.readUint(_json, "$.faultGameAbsolutePrestate"); faultGameAbsolutePrestate = stdJson.readUint(_json, "$.faultGameAbsolutePrestate");
faultGameMaxDepth = stdJson.readUint(_json, "$.faultGameMaxDepth"); faultGameMaxDepth = stdJson.readUint(_json, "$.faultGameMaxDepth");
faultGameMaxDuration = stdJson.readUint(_json, "$.faultGameMaxDuration");
} }
} }
......
artifacts
cache
typechain
.deps
.envrc
.env
/dist/
coverage
artifacts
cache
typechain
.deps
.envrc
.env
/dist/
module.exports = {
...require('../../.prettierrc.js'),
}
# Code gen
Summary -
- This package is generated from [contracts-bedrock](../contracts-bedrock/)
- It's version is kept in sync with contracts bedrock via the [changeset config](../../.changeset/config.json) e.g. if contracts-bedrock is `4.2.0` this package will have the same version.
## Code gen instructions
To run the code gen run the `generate` script from [package.json](./package.json). Make sure node modules is installed.
```bash
pnpm i && pnpm generate
```
MIT License
Copyright (c) 2022 Optimism
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## Contracts TS
[![codecov](https://codecov.io/gh/ethereum-optimism/optimism/branch/develop/graph/badge.svg?token=0VTG7PG7YR&flag=contracts-bedrock-tests)](https://codecov.io/gh/ethereum-optimism/optimism)
ABI and Address constants + generated code from [@eth-optimism/contracts-bedrock/](../contracts-bedrock/) for use in TypeScript.
Much of this package is generated. See [CODE_GEN.md](./CODE_GEN.md) for instructions on how to generate.
#### @eth-optimism/contracts-ts
The main entrypoint exports constants related to contracts bedrock as const. As const allows it to be [used in TypeScript with stronger typing than importing JSON](https://github.com/microsoft/TypeScript/issues/32063).
- Exports contract abis.
- Exports contract addresses
```typescript
import {
l2OutputOracleProxyABI,
l2OutputOracleAddresses,
} from '@eth-optimism/contracts-ts'
console.log(l2OutputOracleAddresses[10], abi)
```
Addresses are also exported as an object for convenience.
```typescript
import { addresses } from '@eth-optimism/contracts-ts'
console.log(addresses.l2OutputOracle[10])
```
#### @eth-optimism/contracts-ts/react
- All [React hooks](https://wagmi.sh/cli/plugins/react) `@eth-optimism/contracts-ts/react`
```typescript
import { useAddressManagerAddress } from '@eth-optimism/contracts-ts/react'
const component = () => {
const { data, error, loading } = useAddressManagerAddress()
if (loading) {
return <div>Loading</div>
}
if (err) {
return <div>Error</div>
}
return <div>{data}</div>
}
```
#### @eth-optimism/contracts-ts/actions
- All [wagmi actions](https://wagmi.sh/react/actions) for use in Vanilla JS or non react code
```typescript
import { readSystemConfig } from '@eth-optimism/contracts-ts/actions'
console.log(await readSystemConfig())
```
#### See Also
- [Contracts bedrock specs](../../specs/)
- [Wagmi](https://wagmi.sh)
This diff is collapsed.
{
"AddressManager": {
"1": "0xdE1FCfB0851916CA5101820A69b13a4E276bd81F",
"5": "0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"
},
"AssetReceiver": {
"1": "0x15DdA60616Ffca20371ED1659dBB78E888f65556",
"10": "0x15DdA60616Ffca20371ED1659dBB78E888f65556"
},
"CheckBalanceHigh": {
"1": "0x7eC64a8a591bFf829ff6C8be76074D540ACb813F",
"5": "0x7eC64a8a591bFf829ff6C8be76074D540ACb813F",
"420": "0x5d7103853f12109A7d27F118e54BbC654ad847E9"
},
"CheckBalanceLow": {
"1": "0x381a4eFC2A2C914eA1889722bB4B44Fa6BD5b640",
"5": "0x381a4eFC2A2C914eA1889722bB4B44Fa6BD5b640",
"420": "0x7Ce13D154FAEE5C8B3E6b19d4Add16f21d884474"
},
"CheckGelatoLow": {
"1": "0x4f7CFc43f6D262a085F3b946cAC69E7a8E39BBAa",
"5": "0x4f7CFc43f6D262a085F3b946cAC69E7a8E39BBAa",
"420": "0xF9c8a4Cb4021f57F9f6d69799cA9BefF64524862"
},
"CheckTrue": {
"1": "0x5c741a38cb11424711231777D71689C458eE835D",
"5": "0x5c741a38cb11424711231777D71689C458eE835D",
"420": "0x47443D0C184e022F19BD1578F5bca6B8a9F58E32"
},
"Drippie": {
"1": "0x44b3A2a040057eBafC601A78647e805fd58B1f50"
},
"Drippie_goerli": {
"5": "0x44b3A2a040057eBafC601A78647e805fd58B1f50"
},
"Drippie_optimism-goerli": {
"420": "0x8D8d533C16D23847EB04EEB0925be8900Dd3af86"
},
"L1CrossDomainMessenger": {
"1": "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1",
"5": "0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"
},
"L1ERC721Bridge": {
"1": "0x5a7749f83b81B301cAb5f48EB8516B986DAef23D",
"5": "0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9"
},
"L1StandardBridge": {
"1": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1",
"5": "0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"
},
"L2OutputOracle": {
"1": "0xdfe97868233d1aa22e815a266982f2cf17685a27",
"5": "0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"
},
"OptimismMintableERC20Factory": {
"1": "0x4200000000000000000000000000000000000012",
"5": "0x4200000000000000000000000000000000000012",
"420": "0x4200000000000000000000000000000000000012"
},
"OptimismPortal": {
"1": "0xbEb5Fc579115071764c7423A4f12eDde41f106Ed",
"5": "0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"
},
"PortalSender": {
"1": "0x0A893d9576b9cFD9EF78595963dc973238E78210",
"5": "0xe7FACd39531ee3C313330E93B4d7a8B8A3c84Aa4"
},
"ProxyAdmin": {
"1": "0x4200000000000000000000000000000000000018",
"5": "0x4200000000000000000000000000000000000018"
},
"SystemConfig": {
"1": "0x229047fed2591dbec1eF1118d64F7aF3dB9EB290",
"5": "0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60"
},
"SystemDictator": {
"1": "0xB4453CEb33d2e67FA244A24acf2E50CEF31F53cB"
},
"SystemDictator_goerli": {
"5": "0x1f0613A44c9a8ECE7B3A2e0CdBdF0F5B47A50971"
},
"TeleportrWithdrawer": {
"1": "0x78A25524D90E3D0596558fb43789bD800a5c3007"
},
"AttestationStation": {
"10": "0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77",
"420": "0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77"
},
"L2ERC721Bridge": {
"10": "0x4200000000000000000000000000000000000014",
"420": "0x4200000000000000000000000000000000000014"
},
"MintManager": {
"10": "0x5C4e7Ba1E219E47948e6e3F55019A647bA501005",
"420": "0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76"
},
"OptimismMintableERC721Factory": {
"10": "0x4200000000000000000000000000000000000017"
},
"OptimismMintableERC721Factory_optimism-goerli": {
"420": "0x4200000000000000000000000000000000000017"
},
"Optimist": {
"10": "0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5",
"420": "0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5"
},
"OptimistAllowlist": {
"10": "0x482b1945D58f2E9Db0CEbe13c7fcFc6876b41180",
"420": "0x482b1945D58f2E9Db0CEbe13c7fcFc6876b41180"
},
"OptimistInviter": {
"10": "0x073031A1E1b8F5458Ed41Ce56331F5fd7e1de929",
"420": "0x073031A1E1b8F5458Ed41Ce56331F5fd7e1de929"
},
"BaseFeeVault": {
"420": "0x4200000000000000000000000000000000000019"
},
"GasPriceOracle": {
"420": "0x420000000000000000000000000000000000000F"
},
"L1Block": {
"420": "0x4200000000000000000000000000000000000015"
},
"L1FeeVault": {
"420": "0x420000000000000000000000000000000000001a"
},
"L2CrossDomainMessenger": {
"420": "0x4200000000000000000000000000000000000007"
},
"L2StandardBridge": {
"420": "0x4200000000000000000000000000000000000010"
},
"L2ToL1MessagePasser": {
"420": "0x4200000000000000000000000000000000000016"
},
"SequencerFeeVault": {
"420": "0x4200000000000000000000000000000000000011"
}
}
VITE_RPC_URL_L2_GOERLI=
VITE_RPC_URL_L2_MAINNET=
VITE_RPC_URL_L1_GOERLI=
VITE_RPC_URL_L1_MAINNET=
{
"name": "@eth-optimism/contracts-ts",
"version": "0.15.0",
"description": "TypeScript interface for Contracts Bedrock",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git",
"directory": "packages/contracts-ts"
},
"homepage": "https://optimism.io",
"type": "module",
"main": "dist/constants.js",
"module": "dist/constants.mjs",
"types": "src/constants.ts",
"exports": {
".": {
"types": "./src/constants.ts",
"import": "./dist/constants.js",
"require": "./dist/constants.cjs"
},
"./actions": {
"types": "./src/actions.ts",
"import": "./dist/actions.js",
"require": "./dist/actions.cjs"
},
"./react": {
"types": "./src/react.ts",
"import": "./dist/react.js",
"require": "./dist/react.cjs"
}
},
"files": [
"dist/",
"src/"
],
"scripts": {
"build": "tsup",
"generate": "wagmi generate && pnpm build && pnpm lint:fix",
"generate:check": "pnpm generate && git diff --exit-code ./addresses.json && git diff --exit-code ./abis.json",
"lint": "prettier --check .",
"lint:fix": "prettier --write .",
"test": "vitest",
"test:coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@eth-optimism/contracts-bedrock": "workspace:*",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react-hooks": "^8.0.1",
"@types/glob": "^8.1.0",
"@vitest/coverage-istanbul": "^0.33.0",
"@wagmi/cli": "^1.3.0",
"@wagmi/core": "^1.3.8",
"abitype": "^0.9.2",
"glob": "^10.3.3",
"isomorphic-fetch": "^3.0.0",
"jest-dom": "link:@types/@testing-library/jest-dom",
"jsdom": "^22.1.0",
"tsup": "^7.1.0",
"typescript": "^5.1.6",
"vite": "^4.4.4",
"vitest": "^0.33.0"
},
"peerDependencies": {
"@wagmi/core": ">1.0.0",
"wagmi": ">1.0.0"
},
"peerDependenciesMeta": {
"wagmi": {
"optional": true
},
"@wagmi/core": {
"optional": true
}
},
"dependencies": {
"@testing-library/react": "^14.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"viem": "^1.3.0"
}
}
import fetch from 'isomorphic-fetch'
// viem needs this
global.fetch = fetch
This diff is collapsed.
import { test, expect } from 'vitest'
import { addresses } from './constants'
import { readFileSync } from 'fs'
import { join } from 'path'
const jsonAddresses = JSON.parse(
readFileSync(join(__dirname, '../addresses.json'), 'utf8')
)
test('should have generated addresses', () => {
expect(addresses).toEqual(jsonAddresses)
})
This diff is collapsed.
import matchers from '@testing-library/jest-dom/matchers'
import { cleanup, waitFor } from '@testing-library/react'
import { renderHook } from '@testing-library/react-hooks'
import { afterEach, expect, test } from 'vitest'
import { useMintManagerOwner } from './react'
import { configureChains, createConfig, WagmiConfig } from 'wagmi'
import * as React from 'react'
import { optimism } from 'viem/chains'
import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'
expect.extend(matchers)
afterEach(() => {
cleanup()
})
const { publicClient } = configureChains(
[optimism],
[
jsonRpcProvider({
rpc: () => ({
http:
import.meta.env.VITE_RPC_URL_L2_MAINNET ??
'https://mainnet.optimism.io',
}),
}),
]
)
const config = createConfig({
publicClient: ({ chainId }) => publicClient({ chainId }),
})
const blockNumber = BigInt(106806163)
test('react hooks should work', async () => {
const hook = renderHook(
() => useMintManagerOwner({ chainId: 10, blockNumber }),
{
wrapper: ({ children }) => (
<WagmiConfig config={config}>{children}</WagmiConfig>
),
}
)
await waitFor(() => {
hook.rerender()
if (hook.result.current.error) throw hook.result.current.error
expect(hook.result.current?.data).toBeDefined()
})
const normalizedResult = {
...hook.result.current,
internal: {
...hook.result.current.internal,
dataUpdatedAt: 'SNAPSHOT_TEST_REMOVED!!!',
},
}
expect(normalizedResult).toMatchInlineSnapshot(`
{
"data": "0x2A82Ae142b2e62Cb7D10b55E323ACB1Cab663a26",
"error": null,
"fetchStatus": "idle",
"internal": {
"dataUpdatedAt": "SNAPSHOT_TEST_REMOVED!!!",
"errorUpdatedAt": 0,
"failureCount": 0,
"isFetchedAfterMount": true,
"isLoadingError": false,
"isPaused": false,
"isPlaceholderData": false,
"isPreviousData": false,
"isRefetchError": false,
"isStale": true,
"remove": [Function],
},
"isError": false,
"isFetched": true,
"isFetchedAfterMount": true,
"isFetching": false,
"isIdle": false,
"isLoading": false,
"isRefetching": false,
"isSuccess": true,
"refetch": [Function],
"status": "success",
}
`)
})
This diff is collapsed.
/// <reference types="vite/client" />
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"baseUrl": "./src",
"strict": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "node",
"jsx": "react",
"target": "ESNext",
"noEmit": true
},
"include": ["./src"]
}
import { defineConfig } from 'tsup'
import packageJson from './package.json'
export default defineConfig({
name: packageJson.name,
entry: ['src/constants.ts', 'src/actions.ts', 'src/react.ts'],
outDir: 'dist',
format: ['esm', 'cjs'],
splitting: false,
sourcemap: true,
clean: false,
})
import { defineConfig } from 'vitest/config'
// @see https://vitest.dev/config/
export default defineConfig({
test: {
setupFiles: './setupVitest.ts',
environment: 'jsdom',
coverage: {
provider: 'istanbul',
},
},
})
This diff is collapsed.
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