From a4c6758ea12b4fa326f2b666b75e94789c682dad Mon Sep 17 00:00:00 2001 From: corver <29249923+corverroos@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:57:57 +0200 Subject: [PATCH] test(solver): add ephemeral load gen (#2676) Adds simple periodic devapp deposit load generation to solver on ephemeral networks. issue: #2666 --- .../allocs/scripts/genallocs_internal_test.go | 4 +- e2e/app/setup.go | 29 +++- e2e/solve/devapp/deposits.go | 35 +++-- e2e/solve/devapp/target.go | 4 + e2e/solve/symbiotic/target.go | 4 + halo/genutil/genutil.go | 4 +- halo/genutil/testdata/TestMakeGenesis.golden | 10 +- lib/ethclient/ethbackend/backends.go | 15 ++ solver/app/app.go | 23 +++- solver/app/bindings.go | 2 +- solver/app/config.go | 5 +- solver/app/config.toml.tpl | 7 +- solver/app/config_test.go | 1 + solver/app/ephemeral.go | 129 ++++++++++++++++++ solver/app/procdeps.go | 6 +- solver/app/processor.go | 8 +- solver/app/processor_internal_test.go | 3 +- solver/app/testdata/default_solver.toml | 5 +- solver/cmd/flags.go | 3 +- solver/types/target.go | 3 + 20 files changed, 256 insertions(+), 44 deletions(-) create mode 100644 solver/app/ephemeral.go diff --git a/contracts/allocs/scripts/genallocs_internal_test.go b/contracts/allocs/scripts/genallocs_internal_test.go index 438b7a59c..0bed8d117 100644 --- a/contracts/allocs/scripts/genallocs_internal_test.go +++ b/contracts/allocs/scripts/genallocs_internal_test.go @@ -30,8 +30,8 @@ func TestBridgeBalance(t *testing.T) { mp = add(mp, th.TargetBalance()) } - mp = add(mp, ether(100)) // 100 OMNI: genesis validator 1 - mp = add(mp, ether(100)) // 100 OMNI: genesis validator 2 + mp = add(mp, ether(1000)) // 1000 OMNI: genesis validator 1 + mp = add(mp, ether(1000)) // 1000 OMNI: genesis validator 2 tests := []struct { name string diff --git a/e2e/app/setup.go b/e2e/app/setup.go index 836fd82d9..96b92181a 100644 --- a/e2e/app/setup.go +++ b/e2e/app/setup.go @@ -504,8 +504,9 @@ func writeSolverConfig(ctx context.Context, def Definition, logCfg log.Config) e confRoot := filepath.Join(def.Testnet.Dir, "solver") const ( - privKeyFile = "privatekey" - configFile = "solver.toml" + privKeyFile = "privatekey" + loadGenKeyFile = "loadgenkey" + configFile = "solver.toml" ) if err := os.MkdirAll(confRoot, 0o755); err != nil { @@ -518,24 +519,38 @@ func writeSolverConfig(ctx context.Context, def Definition, logCfg log.Config) e endpoints = ExternalEndpoints(def) } - // Save private key (use random keys for non-ephemeral) + // Save solver private key (use random keys for non-ephemeral) // TODO(corver): Switch to proper keys once ready. - privKey, err := ethcrypto.GenerateKey() + solverPrivKey, err := ethcrypto.GenerateKey() if err != nil { return errors.Wrap(err, "generate private key") } else if def.Testnet.Network.IsEphemeral() { - privKey, err = eoa.PrivateKey(ctx, def.Testnet.Network, eoa.RoleSolver) + solverPrivKey, err = eoa.PrivateKey(ctx, def.Testnet.Network, eoa.RoleSolver) if err != nil { return errors.Wrap(err, "get solver key") } } + if err := ethcrypto.SaveECDSA(filepath.Join(confRoot, privKeyFile), solverPrivKey); err != nil { + return errors.Wrap(err, "write private key") + } - if err := ethcrypto.SaveECDSA(filepath.Join(confRoot, privKeyFile), privKey); err != nil { + // Save loadgen private key (use random keys for non-ephemeral) + loadGenPrivKey, err := ethcrypto.GenerateKey() + if err != nil { + return errors.Wrap(err, "generate loadgen private key") + } else if def.Testnet.Network.IsEphemeral() { + loadGenPrivKey, err = eoa.PrivateKey(ctx, def.Testnet.Network, eoa.RoleXCaller) + if err != nil { + return errors.Wrap(err, "get loadgen key") + } + } + if err := ethcrypto.SaveECDSA(filepath.Join(confRoot, loadGenKeyFile), loadGenPrivKey); err != nil { return errors.Wrap(err, "write private key") } solverCfg := solverapp.DefaultConfig() - solverCfg.PrivateKey = privKeyFile + solverCfg.SolverPrivKey = privKeyFile + solverCfg.LoadGenPrivKey = loadGenKeyFile solverCfg.Network = def.Testnet.Network solverCfg.RPCEndpoints = endpoints diff --git a/e2e/solve/devapp/deposits.go b/e2e/solve/devapp/deposits.go index e8db64e4f..61519380b 100644 --- a/e2e/solve/devapp/deposits.go +++ b/e2e/solve/devapp/deposits.go @@ -18,6 +18,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" ) type DepositReq struct { @@ -53,7 +54,7 @@ func TestFlow(ctx context.Context, network netconf.Network, endpoints xchain.RPC } for deposit := range toCheck { - ok, err := isDeposited(ctx, backends, deposit) + ok, err := IsDeposited(ctx, backends, deposit) if err != nil { return err } else if ok { @@ -117,8 +118,11 @@ func makeTestDeposits(ctx context.Context, backends ethbackend.Backends) ([]Depo return nil, errors.Wrap(err, "make depositors") } + depositAmount := big.NewInt(1e18) + fundAmount := new(big.Int).Add(depositAmount, big.NewInt(params.GWei)) // Add gas + // fund for gas - if err := anvil.FundAccounts(ctx, backend, big.NewInt(1e18), depositors...); err != nil { + if err := anvil.FundAccounts(ctx, backend, fundAmount, depositors...); err != nil { return nil, errors.Wrap(err, "fund accounts") } @@ -127,7 +131,15 @@ func makeTestDeposits(ctx context.Context, backends ethbackend.Backends) ([]Depo return nil, errors.Wrap(err, "get addresses") } - reqs, err := requestDeposits(ctx, backend, addrs.SolveInbox, depositors) + var deposits []DepositArgs + for _, depositor := range depositors { + deposits = append(deposits, DepositArgs{ + OnBehalfOf: depositor, + Amount: depositAmount, + }) + } + + reqs, err := RequestDeposits(ctx, backend, addrs.SolveInbox, deposits...) if err != nil { return nil, errors.Wrap(err, "request deposits") } @@ -135,7 +147,7 @@ func makeTestDeposits(ctx context.Context, backends ethbackend.Backends) ([]Depo return reqs, nil } -func isDeposited(ctx context.Context, backends ethbackend.Backends, req DepositReq) (bool, error) { +func IsDeposited(ctx context.Context, backends ethbackend.Backends, req DepositReq) (bool, error) { app := MustGetApp(netconf.Devnet) backend, err := backends.Backend(app.L1.ChainID) @@ -209,14 +221,9 @@ func addRandomDepositors(n int, backend *ethbackend.Backend) ([]common.Address, return depositors, nil } -func requestDeposits(ctx context.Context, backend *ethbackend.Backend, inbox common.Address, depositors []common.Address) ([]DepositReq, error) { - var reqs []DepositReq - for _, depositor := range depositors { - deposit := DepositArgs{ - OnBehalfOf: depositor, - Amount: big.NewInt(1e18), - } - +func RequestDeposits(ctx context.Context, backend *ethbackend.Backend, inbox common.Address, deposits ...DepositArgs) ([]DepositReq, error) { + var resp []DepositReq + for _, deposit := range deposits { if err := mintAndApprove(ctx, backend, inbox, deposit); err != nil { return nil, errors.Wrap(err, "mint and approve") } @@ -226,10 +233,10 @@ func requestDeposits(ctx context.Context, backend *ethbackend.Backend, inbox com return nil, errors.Wrap(err, "request at inbox") } - reqs = append(reqs, req) + resp = append(resp, req) } - return reqs, nil + return resp, nil } func requestAtInbox(ctx context.Context, backend *ethbackend.Backend, addr common.Address, deposit DepositArgs) (DepositReq, error) { diff --git a/e2e/solve/devapp/target.go b/e2e/solve/devapp/target.go index 2e7edd08a..4f8c1ef7a 100644 --- a/e2e/solve/devapp/target.go +++ b/e2e/solve/devapp/target.go @@ -21,6 +21,10 @@ type DepositArgs struct { Amount *big.Int } +func (App) Name() string { + return "devapp" +} + func (App) ChainID() uint64 { return evmchain.IDMockL1 } diff --git a/e2e/solve/symbiotic/target.go b/e2e/solve/symbiotic/target.go index c401747ea..6b7b60b64 100644 --- a/e2e/solve/symbiotic/target.go +++ b/e2e/solve/symbiotic/target.go @@ -20,6 +20,10 @@ type DepositArgs struct { Amount *big.Int } +func (App) Name() string { + return "symbiotic" +} + func (t App) ChainID() uint64 { return t.L1.ChainID } diff --git a/halo/genutil/genutil.go b/halo/genutil/genutil.go index b4e3ca93d..571ca700e 100644 --- a/halo/genutil/genutil.go +++ b/halo/genutil/genutil.go @@ -46,8 +46,8 @@ import ( // since Omni block period (+-1s) is very fast, roughly 10x normal period of 10s. const slashingBlocksWindow = 1000 -// ValidatorPower is the default power assigned to genesis validators. -const ValidatorPower = 100 +// ValidatorPower is the default power assigned to ephemeral genesis validators. +const ValidatorPower = 1000 func MakeGenesis( network netconf.ID, diff --git a/halo/genutil/testdata/TestMakeGenesis.golden b/halo/genutil/testdata/TestMakeGenesis.golden index bb5102221..3b8d8844b 100644 --- a/halo/genutil/testdata/TestMakeGenesis.golden +++ b/halo/genutil/testdata/TestMakeGenesis.golden @@ -42,7 +42,7 @@ "coins": [ { "denom": "stake", - "amount": "100000000000000000000" + "amount": "1000000000000000000000" } ] }, @@ -51,7 +51,7 @@ "coins": [ { "denom": "stake", - "amount": "100000000000000000000" + "amount": "1000000000000000000000" } ] } @@ -59,7 +59,7 @@ "supply": [ { "denom": "stake", - "amount": "200000000000000000000" + "amount": "2000000000000000000000" } ], "denom_metadata": [], @@ -118,7 +118,7 @@ }, "value": { "denom": "stake", - "amount": "100000000000000000000" + "amount": "1000000000000000000000" } } ], @@ -165,7 +165,7 @@ }, "value": { "denom": "stake", - "amount": "100000000000000000000" + "amount": "1000000000000000000000" } } ], diff --git a/lib/ethclient/ethbackend/backends.go b/lib/ethclient/ethbackend/backends.go index 8ad6b4c00..9e76ea36d 100644 --- a/lib/ethclient/ethbackend/backends.go +++ b/lib/ethclient/ethbackend/backends.go @@ -189,6 +189,21 @@ func (b Backends) All() map[uint64]*Backend { return b.backends } +// AddAccount adds a in-memory private key account to all backends. +// Note this can be called even if other accounts are fireblocks based. +func (b Backends) AddAccount(privkey *ecdsa.PrivateKey) (common.Address, error) { + var addr common.Address + for _, backend := range b.backends { + var err error + addr, err = backend.AddAccount(privkey) + if err != nil { + return common.Address{}, err + } + } + + return addr, nil +} + func (b Backends) Clients() map[uint64]ethclient.Client { clients := make(map[uint64]ethclient.Client) for chainID, backend := range b.backends { diff --git a/solver/app/app.go b/solver/app/app.go index fbc40060d..927c3dd6d 100644 --- a/solver/app/app.go +++ b/solver/app/app.go @@ -27,7 +27,10 @@ import ( ) // confLevel of solver streamers. -const confLevel = xchain.ConfLatest +const ( + confLevel = xchain.ConfLatest + unknown = "unknown" +) func chainVerFromID(id uint64) xchain.ChainVersion { return xchain.ChainVersion{ID: id, ConfLevel: confLevel} @@ -52,10 +55,10 @@ func Run(ctx context.Context, cfg Config) error { return err } - if cfg.PrivateKey == "" { + if cfg.SolverPrivKey == "" { return errors.New("private key not set") } - privKey, err := ethcrypto.LoadECDSA(cfg.PrivateKey) + privKey, err := ethcrypto.LoadECDSA(cfg.SolverPrivKey) if err != nil { return errors.Wrap(err, "load private key") } @@ -67,6 +70,10 @@ func Run(ctx context.Context, cfg Config) error { return err } + if err := maybeStartLoadGen(ctx, cfg, network, backends); err != nil { + return err + } + xprov := xprovider.New(network, backends.Clients(), nil) db, err := newSolverDB(cfg.DBDir) @@ -223,6 +230,15 @@ func startEventStreams( return cursors.Set(ctx, chainVerFromID(chainID), height) } + targetNamer := func(req bindings.SolveRequest) string { + target, err := getTarget(network.ID, req.Call) + if err != nil { + return unknown + } + + return target.Name() + } + deps := procDeps{ ParseID: newIDParser(inboxContracts), GetRequest: newRequestGetter(inboxContracts), @@ -233,6 +249,7 @@ func startEventStreams( Claim: newClaimer(inboxContracts, backends, solverAddr), SetCursor: cursorSetter, ChainName: network.ChainName, + TargetName: targetNamer, } for _, chain := range inboxChains { diff --git a/solver/app/bindings.go b/solver/app/bindings.go index 0a30ed5dc..aeaa98cfb 100644 --- a/solver/app/bindings.go +++ b/solver/app/bindings.go @@ -110,7 +110,7 @@ func statusString(status uint8) string { case statusClaimed: return "claimed" default: - return "unknown" + return unknown } } diff --git a/solver/app/config.go b/solver/app/config.go index e80c63a20..dc73cf97b 100644 --- a/solver/app/config.go +++ b/solver/app/config.go @@ -19,13 +19,14 @@ type Config struct { RPCEndpoints xchain.RPCEndpoints Network netconf.ID MonitoringAddr string - PrivateKey string + SolverPrivKey string + LoadGenPrivKey string DBDir string } func DefaultConfig() Config { return Config{ - PrivateKey: "solver.key", + SolverPrivKey: "solver.key", MonitoringAddr: ":26660", DBDir: "./db", } diff --git a/solver/app/config.toml.tpl b/solver/app/config.toml.tpl index d5252f975..77e7d4f69 100644 --- a/solver/app/config.toml.tpl +++ b/solver/app/config.toml.tpl @@ -12,8 +12,11 @@ network = "{{ .Network }}" ### Solver Options ### ####################################################################### -# Path to the ethereum private key used to sign avs omni sync transactions. -private-key = "{{ .PrivateKey }}" +# Path to the ethereum private key used to for inbox and outbox request state transitions. +private-key = "{{ .SolverPrivKey }}" + +# Path to the ethereum private key used to generate deposit load on ephemeral networks only. +loadgen-key = "{{ .LoadGenPrivKey }}" # The address that the solver listens for metric scrape requests. monitoring-addr = "{{ .MonitoringAddr }}" diff --git a/solver/app/config_test.go b/solver/app/config_test.go index 77df5dc8d..1469add97 100644 --- a/solver/app/config_test.go +++ b/solver/app/config_test.go @@ -19,6 +19,7 @@ func TestDefaultConfigReference(t *testing.T) { tempDir := t.TempDir() cfg := solver.DefaultConfig() + cfg.LoadGenPrivKey = "loadgen.key" path := filepath.Join(tempDir, "solver.toml") diff --git a/solver/app/ephemeral.go b/solver/app/ephemeral.go new file mode 100644 index 000000000..dd18d89db --- /dev/null +++ b/solver/app/ephemeral.go @@ -0,0 +1,129 @@ +package app + +import ( + "context" + "math/big" + "time" + + "github.com/omni-network/omni/e2e/solve/devapp" + "github.com/omni-network/omni/lib/contracts" + "github.com/omni-network/omni/lib/errors" + "github.com/omni-network/omni/lib/ethclient/ethbackend" + "github.com/omni-network/omni/lib/log" + "github.com/omni-network/omni/lib/netconf" + + "github.com/ethereum/go-ethereum/common" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// maybeStartLoadGen starts the load generator on ephemeral networks. +func maybeStartLoadGen(ctx context.Context, cfg Config, network netconf.Network, backends ethbackend.Backends) error { + if !network.ID.IsEphemeral() || cfg.LoadGenPrivKey == "" { + return nil + } + + privKey, err := ethcrypto.LoadECDSA(cfg.LoadGenPrivKey) + if err != nil { + return errors.Wrap(err, "load loadgen private key") + } + + addr, err := backends.AddAccount(privKey) + if err != nil { + return errors.Wrap(err, "add loadgen account") + } + + log.Info(ctx, "Starting ephemeral network load generation", "depositor", addr) + + go ephemeralLoadGenForever(ctx, network, backends, addr) + + return nil +} + +func ephemeralLoadGenForever( + ctx context.Context, + network netconf.Network, + backends ethbackend.Backends, + depositor common.Address, +) { + period := time.Minute * 5 + timer := time.NewTimer(0) // Tick immediately, then tick every period + defer timer.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-timer.C: + if err := depositDevAppOnce(ctx, network, backends, depositor); err != nil { + log.Warn(ctx, "Depositing to devapp failed (will retry)", err) + } + timer.Reset(period) + } + } +} + +func depositDevAppOnce( + ctx context.Context, + network netconf.Network, + backends ethbackend.Backends, + depositor common.Address, +) error { + app, err := devapp.GetApp(network.ID) + if err != nil { + return err + } + + addrs, err := contracts.GetAddresses(ctx, network.ID) + if err != nil { + return err + } + + backend, err := backends.Backend(app.L2.ChainID) + if err != nil { + return err + } + + depositAmount := big.NewInt(params.GWei) // Deposit 1 gwei into devapp mock vault + + // Ensure depositor has enough balance to deposit + if bal, err := backend.BalanceAt(ctx, depositor, nil); err != nil { + return errors.Wrap(err, "get depositor balance") + } else if bal.Cmp(depositAmount) <= 0 { + return errors.New("depositor balance too low", "balance", bal, "required", depositAmount) + } + + reqs, err := devapp.RequestDeposits(ctx, backend, addrs.SolveInbox, devapp.DepositArgs{ + OnBehalfOf: depositor, + Amount: depositAmount, + }) + if err != nil { + return errors.Wrap(err, "request deposits") + } else if len(reqs) != 1 { + return errors.New("expected 1 deposit request", "got", len(reqs)) + } + req := reqs[0] + ctx = log.WithCtx(ctx, "req_id", reqIDOffset(req.ID)) + log.Debug(ctx, "Loadgen requested deposit to devapp") + + // Wait up to 1min for deposit to complete + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + + t0 := time.Now() + for { + if ok, err := devapp.IsDeposited(ctx, backends, req); err != nil { + return err + } else if ok { + log.Debug(ctx, "Loadgen deposit to devapp complete", "duration", time.Since(t0)) + break + } + + time.Sleep(time.Second) + if ctx.Err() != nil { + return errors.New("timeout waiting for deposit") + } + } + + return nil +} diff --git a/solver/app/procdeps.go b/solver/app/procdeps.go index 05e3cd28a..4e633ae5b 100644 --- a/solver/app/procdeps.go +++ b/solver/app/procdeps.go @@ -30,7 +30,9 @@ type procDeps struct { Fulfill func(ctx context.Context, chainID uint64, req bindings.SolveRequest) error Claim func(ctx context.Context, chainID uint64, req bindings.SolveRequest) error - ChainName func(chainID uint64) string + // Monitoring helpers + TargetName func(bindings.SolveRequest) string + ChainName func(chainID uint64) string } func newClaimer( @@ -166,7 +168,7 @@ func detectCustomError(custom error) string { } } - return "unknown" + return unknown } func checkAllowedCall(ctx context.Context, outbox *bindings.SolveOutbox, call bindings.SolveCall) error { diff --git a/solver/app/processor.go b/solver/app/processor.go index 8fd269a47..277446329 100644 --- a/solver/app/processor.go +++ b/solver/app/processor.go @@ -28,7 +28,6 @@ func newEventProcessor(deps procDeps, chainID uint64) xchain.EventLogsCallback { offset := reqIDOffset(reqID) statusOffset.WithLabelValues(deps.ChainName(chainID), statusString(event.Status)).Set(float64(offset)) ctx := log.WithCtx(ctx, "status", statusString(event.Status), "req_id", offset) - log.Debug(ctx, "Processing event") req, _, err := deps.GetRequest(ctx, chainID, reqID) if err != nil { @@ -39,24 +38,31 @@ func newEventProcessor(deps procDeps, chainID uint64) xchain.EventLogsCallback { continue } + ctx = log.WithCtx(ctx, "target", deps.TargetName(req)) + log.Debug(ctx, "Processing request event") + switch event.Status { case statusPending: if reason, reject, err := deps.ShouldReject(ctx, chainID, req); err != nil { return errors.Wrap(err, "should reject") } else if reject { + log.Info(ctx, "Rejecting request", "reason", reason) if err := deps.Reject(ctx, chainID, req, reason); err != nil { return errors.Wrap(err, "reject request") } } else { + log.Info(ctx, "Accepting request", "reason", reason) if err := deps.Accept(ctx, chainID, req); err != nil { return errors.Wrap(err, "accept request") } } case statusAccepted: + log.Info(ctx, "Accepting request") if err := deps.Fulfill(ctx, chainID, req); err != nil { return errors.Wrap(err, "fulfill request") } case statusFulfilled: + log.Info(ctx, "Claiming request") if err := deps.Claim(ctx, chainID, req); err != nil { return errors.Wrap(err, "claim request") } diff --git a/solver/app/processor_internal_test.go b/solver/app/processor_internal_test.go index 8aacce89c..9d75b5f7f 100644 --- a/solver/app/processor_internal_test.go +++ b/solver/app/processor_internal_test.go @@ -145,7 +145,8 @@ func TestEventProcessor(t *testing.T) { return nil }, - ChainName: func(uint64) string { return "" }, + ChainName: func(uint64) string { return "" }, + TargetName: func(bindings.SolveRequest) string { return "" }, } processor := newEventProcessor(deps, chainID) diff --git a/solver/app/testdata/default_solver.toml b/solver/app/testdata/default_solver.toml index 18632406c..aa1e0da08 100644 --- a/solver/app/testdata/default_solver.toml +++ b/solver/app/testdata/default_solver.toml @@ -12,9 +12,12 @@ network = "" ### Solver Options ### ####################################################################### -# Path to the ethereum private key used to sign avs omni sync transactions. +# Path to the ethereum private key used to for inbox and outbox request state transitions. private-key = "solver.key" +# Path to the ethereum private key used to generate deposit load on ephemeral networks only. +loadgen-key = "loadgen.key" + # The address that the solver listens for metric scrape requests. monitoring-addr = ":26660" diff --git a/solver/cmd/flags.go b/solver/cmd/flags.go index e710806bf..9149ce2f1 100644 --- a/solver/cmd/flags.go +++ b/solver/cmd/flags.go @@ -11,7 +11,8 @@ import ( func bindRunFlags(flags *pflag.FlagSet, cfg *solver.Config) { netconf.BindFlag(flags, &cfg.Network) xchain.BindFlags(flags, &cfg.RPCEndpoints) - flags.StringVar(&cfg.PrivateKey, "private-key", cfg.PrivateKey, "The path to the private key e.g path/private.key") + flags.StringVar(&cfg.SolverPrivKey, "private-key", cfg.SolverPrivKey, "The path to the solver private key e.g path/private.key") + flags.StringVar(&cfg.LoadGenPrivKey, "loadgen-key", cfg.LoadGenPrivKey, "The path to the loadgen private key e.g path/loadgen.key (not applicable to protected networks)") flags.StringVar(&cfg.MonitoringAddr, "monitoring-addr", cfg.MonitoringAddr, "The address to bind the monitoring server") flags.StringVar(&cfg.DBDir, "db-dir", cfg.DBDir, "The path to the database directory") } diff --git a/solver/types/target.go b/solver/types/target.go index 155bf7752..e1cea5156 100644 --- a/solver/types/target.go +++ b/solver/types/target.go @@ -10,6 +10,9 @@ import ( // Target is the interface for a target contract the solver can interact with. type Target interface { + // Name of the target + Name() string + // ChainID returns the chain ID of the target contract. ChainID() uint64