From 814053f64dab58f222eadc7ce06ff1ab2e37934d Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Wed, 12 Feb 2025 13:50:26 -0500 Subject: [PATCH 01/26] op-deployer: add 'verify' contracts command --- op-deployer/cmd/op-deployer/main.go | 7 + op-deployer/pkg/deployer/apply.go | 1 + op-deployer/pkg/deployer/flags.go | 38 ++- op-deployer/pkg/deployer/inspect/l1.go | 28 ++ op-deployer/pkg/deployer/verify/etherscan.go | 158 ++++++++++++ op-deployer/pkg/deployer/verify/verifier.go | 253 +++++++++++++++++++ 6 files changed, 476 insertions(+), 9 deletions(-) create mode 100644 op-deployer/pkg/deployer/verify/etherscan.go create mode 100644 op-deployer/pkg/deployer/verify/verifier.go diff --git a/op-deployer/cmd/op-deployer/main.go b/op-deployer/cmd/op-deployer/main.go index a3bd8363981fd..2560d6a0c025c 100644 --- a/op-deployer/cmd/op-deployer/main.go +++ b/op-deployer/cmd/op-deployer/main.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/clean" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/upgrade" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/verify" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/bootstrap" @@ -66,6 +67,12 @@ func main() { Usage: "cleans up various things", Subcommands: clean.Commands, }, + { + Name: "verify", + Usage: "verifies deployed contracts on Etherscan", + Flags: cliapp.ProtectFlags(deployer.VerifyFlags), + Action: verify.VerifyCLI, + }, } app.Writer = os.Stdout app.ErrWriter = os.Stderr diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go index ef4c5ca0ff146..84031fe36aa40 100644 --- a/op-deployer/pkg/deployer/apply.go +++ b/op-deployer/pkg/deployer/apply.go @@ -167,6 +167,7 @@ func ApplyPipeline( return fmt.Errorf("failed to download L1 artifacts: %w", err) } + fmt.Println("Downloading L2 artifacts") var l2ArtifactsFS foundry.StatDirFs if intent.L1ContractsLocator.Equal(intent.L2ContractsLocator) { l2ArtifactsFS = l1ArtifactsFS diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 09e300981a2d5..5e4fb054cd879 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -13,15 +13,17 @@ import ( ) const ( - EnvVarPrefix = "DEPLOYER" - L1RPCURLFlagName = "l1-rpc-url" - CacheDirFlagName = "cache-dir" - L1ChainIDFlagName = "l1-chain-id" - L2ChainIDsFlagName = "l2-chain-ids" - WorkdirFlagName = "workdir" - OutdirFlagName = "outdir" - PrivateKeyFlagName = "private-key" - IntentTypeFlagName = "intent-type" + EnvVarPrefix = "DEPLOYER" + L1RPCURLFlagName = "l1-rpc-url" + CacheDirFlagName = "cache-dir" + L1ChainIDFlagName = "l1-chain-id" + L2ChainIDsFlagName = "l2-chain-ids" + WorkdirFlagName = "workdir" + OutdirFlagName = "outdir" + PrivateKeyFlagName = "private-key" + IntentTypeFlagName = "intent-type" + EtherscanAPIKeyFlagName = "etherscan-api-key" + ContractBundleFlagName = "contract-bundle" ) type DeploymentTarget string @@ -117,6 +119,17 @@ var ( "intent-config-type", }, } + EtherscanAPIKeyFlag = &cli.StringFlag{ + Name: EtherscanAPIKeyFlagName, + Usage: "Etherscan API key for contract verification.", + EnvVars: PrefixEnvVar("ETHERSCAN_API_KEY"), + Required: true, + } + ContractBundleFlag = &cli.StringFlag{ + Name: ContractBundleFlagName, + Usage: "Which contract bundle/grouping to use (superchain, opchain, or implementations)", + EnvVars: PrefixEnvVar("CONTRACT_BUNDLE"), + } ) var GlobalFlags = append([]cli.Flag{CacheDirFlag}, oplog.CLIFlags(EnvVarPrefix)...) @@ -141,6 +154,13 @@ var UpgradeFlags = []cli.Flag{ DeploymentTargetFlag, } +var VerifyFlags = []cli.Flag{ + L1RPCURLFlag, + WorkdirFlag, + EtherscanAPIKeyFlag, + ContractBundleFlag, +} + func PrefixEnvVar(name string) []string { return op_service.PrefixEnvVar(EnvVarPrefix, name) } diff --git a/op-deployer/pkg/deployer/inspect/l1.go b/op-deployer/pkg/deployer/inspect/l1.go index cabcc997c4377..c9eab5dd7afe0 100644 --- a/op-deployer/pkg/deployer/inspect/l1.go +++ b/op-deployer/pkg/deployer/inspect/l1.go @@ -2,6 +2,7 @@ package inspect import ( "fmt" + "reflect" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" @@ -20,6 +21,33 @@ type L1Contracts struct { ImplementationsDeployment ImplementationsDeployment `json:"implementationsDeployment"` } +const ( + SuperchainBundle = "superchain" + ImplementationsBundle = "implementations" + OpChainBundle = "opchain" +) + +func (l L1Contracts) GetContractAddress(name string, bundleName string) (common.Address, error) { + var bundle interface{} + switch bundleName { + case SuperchainBundle: + bundle = l.SuperchainDeployment + case ImplementationsBundle: + bundle = l.ImplementationsDeployment + case OpChainBundle: + bundle = l.OpChainDeployment + default: + return common.Address{}, fmt.Errorf("invalid contract bundle type: %s", bundleName) + } + + field := reflect.ValueOf(bundle).FieldByName(name) + if !field.IsValid() { + return common.Address{}, fmt.Errorf("contract %s not found in %s bundle", name, bundleName) + } + + return field.Interface().(common.Address), nil +} + func (l L1Contracts) AsL1Deployments() *genesis.L1Deployments { return &genesis.L1Deployments{ AddressManager: l.OpChainDeployment.AddressManagerAddress, diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go new file mode 100644 index 0000000000000..449098d35aac9 --- /dev/null +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -0,0 +1,158 @@ +package verify + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/ethereum/go-ethereum/common" +) + +type EtherscanResponse struct { + Status string `json:"status"` + Message string `json:"message"` + Result string `json:"result"` +} + +func getAPIEndpoint(chainID uint64) string { + switch chainID { + case 1: + return "https://api.etherscan.io/api" // mainnet + case 11155111: + return "https://api-sepolia.etherscan.io/api" // sepolia + default: + return "" + } +} + +func (v *Verifier) verifyContract(address common.Address, contractName string) error { + v.log.Info("Preparing contract verification", "name", contractName, "address", address.Hex()) + + verified, err := v.isVerified(address) + if err != nil { + return fmt.Errorf("failed to check verification status: %w", err) + } + if verified { + v.log.Info("Contract is already verified", "name", contractName, "address", address.Hex()) + return nil + } + v.log.Info("Formatting etherscan verification request", "name", contractName, "address", address.Hex()) + + source, err := v.getContractArtifact(contractName) + if err != nil { + return fmt.Errorf("failed to get contract source: %w", err) + } + + optimized := "0" + if source.OptimizationUsed { + optimized = "1" + } + + data := url.Values{ + "apikey": {v.apiKey}, + "module": {"contract"}, + "action": {"verifysourcecode"}, + "contractaddress": {address.Hex()}, + "sourceCode": {source.SourceCode}, + "codeformat": {"solidity-single-file"}, + "contractname": {contractName}, + "compilerversion": {fmt.Sprintf("v%s", source.CompilerVersion)}, + "optimizationUsed": {optimized}, + "runs": {fmt.Sprintf("%d", source.OptimizationRuns)}, + "evmversion": {source.EVMVersion}, + } + + resp, err := http.PostForm(v.etherscanUrl, data) + if err != nil { + return fmt.Errorf("failed to submit verification request: %w", err) + } + defer resp.Body.Close() + + var result EtherscanResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return fmt.Errorf("failed to decode response: %w", err) + } + + if result.Status != "1" { + return fmt.Errorf("verification request failed: status=%s message=%s result=%s", + result.Status, result.Message, result.Result) + } + v.log.Info("Verification request submitted successfully", "name", contractName, "address", address.Hex()) + + return v.checkVerificationStatus(result.Result) +} + +// sendRateLimitedRequest is a helper function which waits for a rate limit token +// before sending a request +func (v *Verifier) sendRateLimitedRequest(req *http.Request) (*http.Response, error) { + if err := v.rateLimiter.Wait(context.Background()); err != nil { + return nil, fmt.Errorf("rate limiter error: %w", err) + } + + return http.DefaultClient.Do(req) +} + +func (v *Verifier) isVerified(address common.Address) (bool, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s?module=contract&action=getabi&address=%s&apikey=%s", + v.etherscanUrl, address.Hex(), v.apiKey), nil) + if err != nil { + return false, err + } + + resp, err := v.sendRateLimitedRequest(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + + var result EtherscanResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return false, err + } + + v.log.Debug("Contract verification status", "status", result.Status, "message", result.Message) + return result.Status == "1", nil +} + +func (v *Verifier) checkVerificationStatus(reqId string) error { + req, err := http.NewRequest("GET", fmt.Sprintf("%s?apikey=%s&module=contract&action=checkverifystatus&guid=%s", + v.etherscanUrl, v.apiKey, reqId), nil) + if err != nil { + return fmt.Errorf("failed to create checkverifystatus request: %w", err) + } + + for i := 0; i < 10; i++ { // Try 10 times with increasing delays + v.log.Info("Checking verification status", "guid", reqId) + time.Sleep(time.Duration(i+2) * time.Second) + + resp, err := v.sendRateLimitedRequest(req) + if err != nil { + return fmt.Errorf("failed to send checkverifystatus request: %w", err) + } + defer resp.Body.Close() + + var result struct { + Status string `json:"status"` + Message string `json:"message"` + Result string `json:"result"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return fmt.Errorf("failed to decode checkverifystatus response: %w", err) + } + + if result.Status == "1" { + return nil + } + if result.Result == "Already Verified" { + v.log.Info("Contract is already verified") + return nil + } + if result.Result != "Pending in queue" { + return fmt.Errorf("verification failed: %s, %s", result.Result, result.Message) + } + } + return fmt.Errorf("verification timed out") +} diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go new file mode 100644 index 0000000000000..88651a996174a --- /dev/null +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -0,0 +1,253 @@ +package verify + +import ( + "context" + "encoding/json" + "fmt" + "io" + "path" + "reflect" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" + "golang.org/x/time/rate" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/inspect" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + oplog "github.com/ethereum-optimism/optimism/op-service/log" +) + +type Verifier struct { + apiKey string + l1ChainID uint64 + st *state.State + artifactsFS foundry.StatDirFs + log log.Logger + etherscanUrl string + rateLimiter *rate.Limiter +} + +func NewVerifier(apiKey string, l1ChainID uint64, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger) (*Verifier, error) { + etherscanUrl := getAPIEndpoint(l1ChainID) + if etherscanUrl == "" { + return nil, fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) + } + + v := &Verifier{ + apiKey: apiKey, + l1ChainID: l1ChainID, + st: st, + artifactsFS: artifactsFS, + log: l, + etherscanUrl: etherscanUrl, + //rateLimiter: rate.NewLimiter(rate.Limit(3), 2), + rateLimiter: rate.NewLimiter(rate.Limit(5), 5), + } + + return v, nil +} + +func VerifyCLI(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) + workdir := cliCtx.String(deployer.WorkdirFlagName) + etherscanAPIKey := cliCtx.String(deployer.EtherscanAPIKeyFlagName) + + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + client, err := ethclient.Dial(l1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1: %w", err) + } + + chainID, err := client.ChainID(ctx) + if err != nil { + return err + } + + st, err := pipeline.ReadState(workdir) + if err != nil { + return fmt.Errorf("failed to read state: %w", err) + } + + progressor := func(curr, total int64) { + l.Info("artifacts download progress", "current", curr, "total", total) + } + + // Get artifacts filesystem (either local or downloaded) + artifactsFS, err := artifacts.Download(ctx, st.AppliedIntent.L1ContractsLocator, progressor) + if err != nil { + return fmt.Errorf("failed to get artifacts: %w", err) + } + l.Info("Downloaded artifacts", "artifacts", artifactsFS) + + v, err := NewVerifier(etherscanAPIKey, chainID.Uint64(), st, artifactsFS, l) + if err != nil { + return fmt.Errorf("failed to create verifier: %w", err) + } + + if cliCtx.Args().Len() > 0 { + // If a contract name is provided, verify just that contract + bundleName := cliCtx.String(deployer.ContractBundleFlagName) + contractName := cliCtx.Args().First() + if err := v.VerifySingleContract(ctx, contractName, bundleName); err != nil { + return err + } + } else { + if err := v.VerifyAll(ctx, client, workdir); err != nil { + return err + } + } + + v.log.Info("--- SUCCESS --- all requested contracts verified") + return nil +} + +func (v *Verifier) VerifySingleContract(ctx context.Context, contractName string, bundleName string) error { + l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[0].ID) + if err != nil { + return fmt.Errorf("failed to extract L1 contracts from state: %w", err) + } + + v.log.Info("Looking up contract address", "name", contractName, "bundle", bundleName) + addr, err := l1Contracts.GetContractAddress(contractName, bundleName) + if err != nil { + return fmt.Errorf("failed to find address for contract %s: %w", contractName, err) + } + + return v.verifyContract(addr, contractName) +} + +func (v *Verifier) VerifyAll(ctx context.Context, client *ethclient.Client, workdir string) error { + l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[0].ID) + if err != nil { + return fmt.Errorf("failed to extract L1 contracts from state: %w", err) + } + + if err := v.verifyContractBundle(l1Contracts.SuperchainDeployment); err != nil { + return err + } + if err := v.verifyContractBundle(l1Contracts.OpChainDeployment); err != nil { + return err + } + if err := v.verifyContractBundle(l1Contracts.ImplementationsDeployment); err != nil { + return err + } + + return nil +} + +func (v *Verifier) verifyContractBundle(s interface{}) error { + val := reflect.ValueOf(s) + typ := val.Type() + + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + if field.Type() == reflect.TypeOf(common.Address{}) { + addr := field.Interface().(common.Address) + if addr != (common.Address{}) { // Skip zero addresses + name := typ.Field(i).Name + if err := v.verifyContract(addr, name); err != nil { + return fmt.Errorf("failed to verify %s: %w", name, err) + } + } + } + } + return nil +} + +type ContractArtifact struct { + SourceCode string + ContractName string + CompilerVersion string + OptimizationUsed bool + OptimizationRuns int + EVMVersion string +} + +func (v *Verifier) getContractArtifact(name string) (*ContractArtifact, error) { + // Remove suffix if present + lookupName := strings.TrimSuffix(name, "ProxyAddress") + lookupName = strings.TrimSuffix(lookupName, "Address") + + artifactPath := path.Join(lookupName+".sol", lookupName+".json") + + v.log.Info("Opening artifact", "path", artifactPath, "lookupName", lookupName) + f, err := v.artifactsFS.Open(artifactPath) + if err != nil { + return nil, fmt.Errorf("failed to open artifact: %w", err) + } + defer f.Close() + + var art foundry.Artifact + if err := json.NewDecoder(f).Decode(&art); err != nil { + return nil, fmt.Errorf("failed to decode artifact: %w", err) + } + + var optimizer struct { + Enabled bool `json:"enabled"` + Runs int `json:"runs"` + } + if err := json.Unmarshal(art.Metadata.Settings.Optimizer, &optimizer); err != nil { + return nil, fmt.Errorf("failed to parse optimizer settings: %w", err) + } + + // Get compiler version from the main artifact and clean it + compilerVersion := art.Metadata.Compiler.Version + compilerVersion = strings.Split(compilerVersion, "+")[0] // Remove the "+commit..." part + v.log.Info("Using compiler version", "version", compilerVersion) + + // Combine all sources into one flat file + var combinedSource strings.Builder + for sourcePath := range art.Metadata.Sources { + // Extract just the filename from the path + baseName := strings.TrimSuffix(path.Base(sourcePath), ".sol") + contractDir := baseName + ".sol" + + baseName = strings.TrimPrefix(baseName, "draft-") + + // Try to find the JSON file with matching compiler version + sourceArtifactPath := path.Join(contractDir, fmt.Sprintf("%s.%s.json", + baseName, + compilerVersion)) + + f, err := v.artifactsFS.Open(sourceArtifactPath) + if err != nil { + // Fallback to non-versioned file if version-specific one doesn't exist + sourceArtifactPath = path.Join(contractDir, baseName+".json") + f, err = v.artifactsFS.Open(sourceArtifactPath) + if err != nil { + return nil, fmt.Errorf("failed to read source file %s: %w", baseName, err) + } + } + content, err := io.ReadAll(f) + f.Close() + if err != nil { + return nil, fmt.Errorf("failed to read source content %s: %w", sourceArtifactPath, err) + } + combinedSource.Write(content) + combinedSource.WriteString("\n\n") + + v.log.Info("added source", "contract", baseName) + } + + return &ContractArtifact{ + SourceCode: combinedSource.String(), + ContractName: lookupName, + CompilerVersion: art.Metadata.Compiler.Version, + OptimizationUsed: optimizer.Enabled, + OptimizationRuns: optimizer.Runs, + EVMVersion: art.Metadata.Settings.EVMVersion, + }, nil +} From 6de50151c3629b24cbe4833e3b1268739e538770 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Wed, 12 Feb 2025 13:53:42 -0500 Subject: [PATCH 02/26] fix rate limiter values --- op-deployer/pkg/deployer/verify/verifier.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index 88651a996174a..ea9f63d15133d 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -48,8 +48,7 @@ func NewVerifier(apiKey string, l1ChainID uint64, st *state.State, artifactsFS f artifactsFS: artifactsFS, log: l, etherscanUrl: etherscanUrl, - //rateLimiter: rate.NewLimiter(rate.Limit(3), 2), - rateLimiter: rate.NewLimiter(rate.Limit(5), 5), + rateLimiter: rate.NewLimiter(rate.Limit(3), 2), } return v, nil From 6cb5671815f1995ef768a2929374ceb7542191d6 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 13 Feb 2025 12:07:31 -0500 Subject: [PATCH 03/26] verified SuperchainConfigImplAddress --- op-chain-ops/foundry/artifact.go | 24 +++- op-deployer/pkg/deployer/verify/etherscan.go | 15 +- op-deployer/pkg/deployer/verify/verifier.go | 144 ++++++++++++------- 3 files changed, 117 insertions(+), 66 deletions(-) diff --git a/op-chain-ops/foundry/artifact.go b/op-chain-ops/foundry/artifact.go index 70e3085b09618..c25e86a0393a9 100644 --- a/op-chain-ops/foundry/artifact.go +++ b/op-chain-ops/foundry/artifact.go @@ -18,12 +18,20 @@ import ( // JSON marshaling logic is implemented to maintain the ability // to roundtrip serialize an artifact type Artifact struct { - ABI abi.ABI - abi json.RawMessage - StorageLayout solc.StorageLayout - DeployedBytecode DeployedBytecode - Bytecode Bytecode - Metadata Metadata + ABI abi.ABI + abi json.RawMessage + StorageLayout solc.StorageLayout + DeployedBytecode DeployedBytecode + Bytecode Bytecode + Metadata Metadata + ContractName string `json:"contractName"` + Version string `json:"version"` + Source string `json:"source"` + Language string `json:"language"` + CompilerVersion string `json:"compiler"` + License string `json:"license"` + MethodIdentifiers map[string]string `json:"methodIdentifiers"` + GasEstimates json.RawMessage `json:"gasEstimates"` } func (a *Artifact) UnmarshalJSON(data []byte) error { @@ -41,6 +49,7 @@ func (a *Artifact) UnmarshalJSON(data []byte) error { a.DeployedBytecode = artifact.DeployedBytecode a.Bytecode = artifact.Bytecode a.Metadata = artifact.Metadata + a.Source = artifact.Source return nil } @@ -51,6 +60,7 @@ func (a Artifact) MarshalJSON() ([]byte, error) { DeployedBytecode: a.DeployedBytecode, Bytecode: a.Bytecode, Metadata: a.Metadata, + Source: a.Source, } return json.Marshal(artifact) } @@ -59,6 +69,7 @@ func (a Artifact) MarshalJSON() ([]byte, error) { // foundry artifacts. type artifactMarshaling struct { ABI json.RawMessage `json:"abi"` + Source string `json:"source"` StorageLayout solc.StorageLayout `json:"storageLayout"` DeployedBytecode DeployedBytecode `json:"deployedBytecode"` Bytecode Bytecode `json:"bytecode"` @@ -102,6 +113,7 @@ type Metadata struct { type ContractSource struct { Keccak256 common.Hash `json:"keccak256"` URLs []string `json:"urls"` + Content string `json:"content"` License string `json:"license"` } diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go index 449098d35aac9..6d33ee06a1672 100644 --- a/op-deployer/pkg/deployer/verify/etherscan.go +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "github.com/ethereum/go-ethereum/common" @@ -56,16 +57,21 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e "module": {"contract"}, "action": {"verifysourcecode"}, "contractaddress": {address.Hex()}, - "sourceCode": {source.SourceCode}, - "codeformat": {"solidity-single-file"}, - "contractname": {contractName}, + "codeformat": {"solidity-standard-json-input"}, + "sourceCode": {source.StandardInput}, + "contractname": {source.ContractName}, "compilerversion": {fmt.Sprintf("v%s", source.CompilerVersion)}, "optimizationUsed": {optimized}, "runs": {fmt.Sprintf("%d", source.OptimizationRuns)}, "evmversion": {source.EVMVersion}, } - resp, err := http.PostForm(v.etherscanUrl, data) + req, err := http.NewRequest("POST", v.etherscanUrl, strings.NewReader(data.Encode())) + if err != nil { + return fmt.Errorf("failed to create verification request: %w", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := v.sendRateLimitedRequest(req) if err != nil { return fmt.Errorf("failed to submit verification request: %w", err) } @@ -81,7 +87,6 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e result.Status, result.Message, result.Result) } v.log.Info("Verification request submitted successfully", "name", contractName, "address", address.Hex()) - return v.checkVerificationStatus(result.Result) } diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index ea9f63d15133d..0bfd4aab4272d 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "path" "reflect" "strings" @@ -63,13 +62,12 @@ func VerifyCLI(cliCtx *cli.Context) error { workdir := cliCtx.String(deployer.WorkdirFlagName) etherscanAPIKey := cliCtx.String(deployer.EtherscanAPIKeyFlagName) - ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) - client, err := ethclient.Dial(l1RPCUrl) if err != nil { return fmt.Errorf("failed to connect to L1: %w", err) } + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) chainID, err := client.ChainID(ctx) if err != nil { return err @@ -80,12 +78,7 @@ func VerifyCLI(cliCtx *cli.Context) error { return fmt.Errorf("failed to read state: %w", err) } - progressor := func(curr, total int64) { - l.Info("artifacts download progress", "current", curr, "total", total) - } - - // Get artifacts filesystem (either local or downloaded) - artifactsFS, err := artifacts.Download(ctx, st.AppliedIntent.L1ContractsLocator, progressor) + artifactsFS, err := artifacts.Download(ctx, st.AppliedIntent.L1ContractsLocator, nil) if err != nil { return fmt.Errorf("failed to get artifacts: %w", err) } @@ -143,7 +136,6 @@ func (v *Verifier) VerifyAll(ctx context.Context, client *ethclient.Client, work if err := v.verifyContractBundle(l1Contracts.ImplementationsDeployment); err != nil { return err } - return nil } @@ -167,22 +159,46 @@ func (v *Verifier) verifyContractBundle(s interface{}) error { } type ContractArtifact struct { - SourceCode string ContractName string CompilerVersion string OptimizationUsed bool OptimizationRuns int EVMVersion string + StandardInput string } -func (v *Verifier) getContractArtifact(name string) (*ContractArtifact, error) { - // Remove suffix if present - lookupName := strings.TrimSuffix(name, "ProxyAddress") - lookupName = strings.TrimSuffix(lookupName, "Address") +var exceptions = map[string]string{ + "OptimismPortalImpl": "OptimismPortal2", + "L1StandardBridgeProxy": "L1ChugSplashProxy", + "L1CrossDomainMessengerProxy": "ResolvedDelegateProxy", + "Opcm": "OPContractsManager", +} + +func getArtifactName(name string) string { + lookupName := strings.TrimSuffix(name, "Address") + + if artifactName, exists := exceptions[lookupName]; exists { + return artifactName + } + + // Handle standard cases + lookupName = strings.TrimSuffix(lookupName, "Proxy") + lookupName = strings.TrimSuffix(lookupName, "Impl") + lookupName = strings.TrimSuffix(lookupName, "Singleton") + + // If it was a proxy and not a special case, return "Proxy" + if strings.HasSuffix(name, "ProxyAddress") { + return "Proxy" + } + + return lookupName +} - artifactPath := path.Join(lookupName+".sol", lookupName+".json") +func (v *Verifier) getContractArtifact(name string) (*ContractArtifact, error) { + artifactName := getArtifactName(name) + artifactPath := path.Join(artifactName+".sol", artifactName+".json") - v.log.Info("Opening artifact", "path", artifactPath, "lookupName", lookupName) + v.log.Info("Opening artifact", "path", artifactPath, "name", name) f, err := v.artifactsFS.Open(artifactPath) if err != nil { return nil, fmt.Errorf("failed to open artifact: %w", err) @@ -194,6 +210,21 @@ func (v *Verifier) getContractArtifact(name string) (*ContractArtifact, error) { return nil, fmt.Errorf("failed to decode artifact: %w", err) } + // Add all sources (main contract and dependencies) + sources := make(map[string]map[string]string) + for sourcePath, sourceInfo := range art.Metadata.Sources { + key := sourcePath + // If the source comes from OpenZeppelin, adjust the key to match the Solidity import. + if strings.HasPrefix(sourcePath, "lib/openzeppelin-contracts/contracts/") { + key = strings.Replace(sourcePath, "lib/openzeppelin-contracts/contracts/", "@openzeppelin/contracts/", 1) + } + + sources[key] = map[string]string{ + "content": sourceInfo.Content, + } + v.log.Info("added source", "originalPath", sourcePath, "key", key) + } + var optimizer struct { Enabled bool `json:"enabled"` Runs int `json:"runs"` @@ -202,51 +233,54 @@ func (v *Verifier) getContractArtifact(name string) (*ContractArtifact, error) { return nil, fmt.Errorf("failed to parse optimizer settings: %w", err) } - // Get compiler version from the main artifact and clean it - compilerVersion := art.Metadata.Compiler.Version - compilerVersion = strings.Split(compilerVersion, "+")[0] // Remove the "+commit..." part - v.log.Info("Using compiler version", "version", compilerVersion) - - // Combine all sources into one flat file - var combinedSource strings.Builder - for sourcePath := range art.Metadata.Sources { - // Extract just the filename from the path - baseName := strings.TrimSuffix(path.Base(sourcePath), ".sol") - contractDir := baseName + ".sol" - - baseName = strings.TrimPrefix(baseName, "draft-") - - // Try to find the JSON file with matching compiler version - sourceArtifactPath := path.Join(contractDir, fmt.Sprintf("%s.%s.json", - baseName, - compilerVersion)) - - f, err := v.artifactsFS.Open(sourceArtifactPath) - if err != nil { - // Fallback to non-versioned file if version-specific one doesn't exist - sourceArtifactPath = path.Join(contractDir, baseName+".json") - f, err = v.artifactsFS.Open(sourceArtifactPath) - if err != nil { - return nil, fmt.Errorf("failed to read source file %s: %w", baseName, err) - } - } - content, err := io.ReadAll(f) - f.Close() - if err != nil { - return nil, fmt.Errorf("failed to read source content %s: %w", sourceArtifactPath, err) - } - combinedSource.Write(content) - combinedSource.WriteString("\n\n") + standardInput := map[string]interface{}{ + "language": "Solidity", + "sources": sources, + "settings": map[string]interface{}{ + "optimizer": map[string]interface{}{ + "enabled": optimizer.Enabled, + "runs": optimizer.Runs, + }, + "evmVersion": art.Metadata.Settings.EVMVersion, + "metadata": map[string]interface{}{ + "useLiteralContent": true, + "bytecodeHash": "none", + }, + "outputSelection": map[string]interface{}{ + "*": map[string]interface{}{ + "*": []string{ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap", + "metadata", + }, + }, + }, + }, + } - v.log.Info("added source", "contract", baseName) + standardInputJSON, err := json.Marshal(standardInput) + if err != nil { + return nil, fmt.Errorf("failed to generate standard input: %w", err) } + // Get the contract name from the compilation target + var contractName string + for contractFile, name := range art.Metadata.Settings.CompilationTarget { + contractName = contractFile + ":" + name + break + } + + v.log.Info("contractName", "name", contractName) + return &ContractArtifact{ - SourceCode: combinedSource.String(), - ContractName: lookupName, + ContractName: contractName, CompilerVersion: art.Metadata.Compiler.Version, OptimizationUsed: optimizer.Enabled, OptimizationRuns: optimizer.Runs, EVMVersion: art.Metadata.Settings.EVMVersion, + StandardInput: string(standardInputJSON), }, nil } From 0cb5727396173b5017fd2549157fb1ff70df00b8 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 13 Feb 2025 14:54:28 -0500 Subject: [PATCH 04/26] verified superchain bundle (including constructor support) --- op-chain-ops/foundry/artifact.go | 25 ++- op-deployer/pkg/deployer/flags.go | 10 +- op-deployer/pkg/deployer/inspect/l1.go | 6 + op-deployer/pkg/deployer/verify/artifacts.go | 134 +++++++++++ .../pkg/deployer/verify/constructors.go | 22 ++ op-deployer/pkg/deployer/verify/etherscan.go | 23 +- op-deployer/pkg/deployer/verify/verifier.go | 208 +++++------------- 7 files changed, 256 insertions(+), 172 deletions(-) create mode 100644 op-deployer/pkg/deployer/verify/artifacts.go create mode 100644 op-deployer/pkg/deployer/verify/constructors.go diff --git a/op-chain-ops/foundry/artifact.go b/op-chain-ops/foundry/artifact.go index c25e86a0393a9..4898f377d43f0 100644 --- a/op-chain-ops/foundry/artifact.go +++ b/op-chain-ops/foundry/artifact.go @@ -88,7 +88,7 @@ type Metadata struct { Settings struct { // Remappings of the contract imports - Remappings json.RawMessage `json:"remappings"` + Remappings []string `json:"remappings"` // Optimizer settings affect the compiler output, but can be arbitrary. // We load them opaquely, to include it in the hash of what we run. Optimizer json.RawMessage `json:"optimizer"` @@ -165,3 +165,26 @@ func ReadArtifact(path string) (*Artifact, error) { } return &artifact, nil } + +// SearchRemappings applies the configured remappings to a given source path. +// It assumes that each remapping is of the form "alias/=actualPath". +func (a Artifact) SearchRemappings(sourcePath string) string { + for _, mapping := range a.Metadata.Settings.Remappings { + parts := strings.Split(mapping, "/=") + if len(parts) != 2 { + continue + } + alias := parts[0] + if !strings.HasSuffix(alias, "/") { + alias += "/" + } + actualPath := parts[1] + if !strings.HasSuffix(actualPath, "/") { + actualPath += "/" + } + if strings.HasPrefix(sourcePath, actualPath) { + return alias + sourcePath[len(actualPath):] + } + } + return sourcePath +} diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 5e4fb054cd879..0d5b2d3eab7b9 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -121,15 +121,20 @@ var ( } EtherscanAPIKeyFlag = &cli.StringFlag{ Name: EtherscanAPIKeyFlagName, - Usage: "Etherscan API key for contract verification.", + Usage: "etherscan API key for contract verification.", EnvVars: PrefixEnvVar("ETHERSCAN_API_KEY"), Required: true, } ContractBundleFlag = &cli.StringFlag{ Name: ContractBundleFlagName, - Usage: "Which contract bundle/grouping to use (superchain, opchain, or implementations)", + Usage: "contract bundle/grouping to verify (superchain, opchain, or implementations)", EnvVars: PrefixEnvVar("CONTRACT_BUNDLE"), } + ContractNameFlag = &cli.StringFlag{ + Name: ContractNameFlagName, + Usage: "contract name (mathcing a field within state.json) to verify", + EnvVars: PrefixEnvVar("CONTRACT_NAME"), + } ) var GlobalFlags = append([]cli.Flag{CacheDirFlag}, oplog.CLIFlags(EnvVarPrefix)...) @@ -159,6 +164,7 @@ var VerifyFlags = []cli.Flag{ WorkdirFlag, EtherscanAPIKeyFlag, ContractBundleFlag, + ContractNameFlag, } func PrefixEnvVar(name string) []string { diff --git a/op-deployer/pkg/deployer/inspect/l1.go b/op-deployer/pkg/deployer/inspect/l1.go index c9eab5dd7afe0..e8bccb0238ffc 100644 --- a/op-deployer/pkg/deployer/inspect/l1.go +++ b/op-deployer/pkg/deployer/inspect/l1.go @@ -27,6 +27,12 @@ const ( OpChainBundle = "opchain" ) +var ContractBundles = []string{ + SuperchainBundle, + ImplementationsBundle, + OpChainBundle, +} + func (l L1Contracts) GetContractAddress(name string, bundleName string) (common.Address, error) { var bundle interface{} switch bundleName { diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go new file mode 100644 index 0000000000000..dac1361dfaab1 --- /dev/null +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -0,0 +1,134 @@ +package verify + +import ( + "encoding/json" + "fmt" + "path" + "strings" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" +) + +type contractArtifact struct { + ContractName string + CompilerVersion string + OptimizationUsed bool + OptimizationRuns int + EVMVersion string + StandardInput string + ConstructorArgs string +} + +// Map state.json struct's contract field names to forge artifact names +var contractNameExceptions = map[string]string{ + "OptimismPortalImpl": "OptimismPortal2", + "L1StandardBridgeProxy": "L1ChugSplashProxy", + "L1CrossDomainMessengerProxy": "ResolvedDelegateProxy", + "Opcm": "OPContractsManager", +} + +func getArtifactName(name string) string { + lookupName := strings.TrimSuffix(name, "Address") + + if artifactName, exists := contractNameExceptions[lookupName]; exists { + return artifactName + } + + lookupName = strings.TrimSuffix(lookupName, "Proxy") + lookupName = strings.TrimSuffix(lookupName, "Impl") + lookupName = strings.TrimSuffix(lookupName, "Singleton") + + // If it was a proxy and not a special case, return "Proxy" + if strings.HasSuffix(name, "ProxyAddress") { + return "Proxy" + } + + return lookupName +} + +func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { + artifactName := getArtifactName(name) + artifactPath := path.Join(artifactName+".sol", artifactName+".json") + + v.log.Info("Opening artifact", "path", artifactPath, "name", name) + f, err := v.artifactsFS.Open(artifactPath) + if err != nil { + return nil, fmt.Errorf("failed to open artifact: %w", err) + } + defer f.Close() + + var art foundry.Artifact + if err := json.NewDecoder(f).Decode(&art); err != nil { + return nil, fmt.Errorf("failed to decode artifact: %w", err) + } + + // Add all sources (main contract and dependencies) + sources := make(map[string]map[string]string) + for sourcePath, sourceInfo := range art.Metadata.Sources { + remappedKey := art.SearchRemappings(sourcePath) + sources[remappedKey] = map[string]string{"content": sourceInfo.Content} + v.log.Info("added source contract", "originalPath", sourcePath, "remappedKey", remappedKey) + } + + var optimizer struct { + Enabled bool `json:"enabled"` + Runs int `json:"runs"` + } + if err := json.Unmarshal(art.Metadata.Settings.Optimizer, &optimizer); err != nil { + return nil, fmt.Errorf("failed to parse optimizer settings: %w", err) + } + + standardInput := map[string]interface{}{ + "language": "Solidity", + "sources": sources, + "settings": map[string]interface{}{ + "optimizer": map[string]interface{}{ + "enabled": optimizer.Enabled, + "runs": optimizer.Runs, + }, + "evmVersion": art.Metadata.Settings.EVMVersion, + "metadata": map[string]interface{}{ + "useLiteralContent": true, + "bytecodeHash": "none", + }, + "outputSelection": map[string]interface{}{ + "*": map[string]interface{}{ + "*": []string{ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap", + "metadata", + }, + }, + }, + }, + } + + standardInputJSON, err := json.Marshal(standardInput) + if err != nil { + return nil, fmt.Errorf("failed to generate standard input: %w", err) + } + + // Get the contract name from the compilation target + var contractName string + for contractFile, name := range art.Metadata.Settings.CompilationTarget { + contractName = contractFile + ":" + name + break + } + v.log.Info("contractName", "name", contractName) + + constructorArgs := GetConstructorArgs(name, v.st) + v.log.Info("constructorArgs", "args", constructorArgs) + + return &contractArtifact{ + ContractName: contractName, + CompilerVersion: art.Metadata.Compiler.Version, + OptimizationUsed: optimizer.Enabled, + OptimizationRuns: optimizer.Runs, + EVMVersion: art.Metadata.Settings.EVMVersion, + StandardInput: string(standardInputJSON), + ConstructorArgs: constructorArgs, + }, nil +} diff --git a/op-deployer/pkg/deployer/verify/constructors.go b/op-deployer/pkg/deployer/verify/constructors.go new file mode 100644 index 0000000000000..6894ef4546314 --- /dev/null +++ b/op-deployer/pkg/deployer/verify/constructors.go @@ -0,0 +1,22 @@ +package verify + +import ( + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// GetConstructorArgs returns the ABI‑encoded constructor arguments for the specified contract, so +// that they can be passed to etherscan for contract verification. +func GetConstructorArgs(contractName string, state *state.State) string { + // Normalize the contract name (for example, to lower-case) + switch contractName { + case "ProxyAdminAddress": + addr := state.AppliedIntent.SuperchainRoles.ProxyAdminOwner + padded := common.LeftPadBytes(addr.Bytes(), 32) + return hexutil.Encode(padded)[2:] + + default: + return "" + } +} diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go index 6d33ee06a1672..57dfd9c7f2ab0 100644 --- a/op-deployer/pkg/deployer/verify/etherscan.go +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -53,17 +53,18 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e } data := url.Values{ - "apikey": {v.apiKey}, - "module": {"contract"}, - "action": {"verifysourcecode"}, - "contractaddress": {address.Hex()}, - "codeformat": {"solidity-standard-json-input"}, - "sourceCode": {source.StandardInput}, - "contractname": {source.ContractName}, - "compilerversion": {fmt.Sprintf("v%s", source.CompilerVersion)}, - "optimizationUsed": {optimized}, - "runs": {fmt.Sprintf("%d", source.OptimizationRuns)}, - "evmversion": {source.EVMVersion}, + "apikey": {v.apiKey}, + "module": {"contract"}, + "action": {"verifysourcecode"}, + "contractaddress": {address.Hex()}, + "codeformat": {"solidity-standard-json-input"}, + "sourceCode": {source.StandardInput}, + "contractname": {source.ContractName}, + "compilerversion": {fmt.Sprintf("v%s", source.CompilerVersion)}, + "optimizationUsed": {optimized}, + "runs": {fmt.Sprintf("%d", source.OptimizationRuns)}, + "evmversion": {source.EVMVersion}, + "constructorArguements": {source.ConstructorArgs}, } req, err := http.NewRequest("POST", v.etherscanUrl, strings.NewReader(data.Encode())) diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index 0bfd4aab4272d..175b2f7b5e3c7 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -2,11 +2,8 @@ package verify import ( "context" - "encoding/json" "fmt" - "path" "reflect" - "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" @@ -68,7 +65,7 @@ func VerifyCLI(cliCtx *cli.Context) error { } ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) - chainID, err := client.ChainID(ctx) + l1ChainId, err := client.ChainID(ctx) if err != nil { return err } @@ -78,71 +75,78 @@ func VerifyCLI(cliCtx *cli.Context) error { return fmt.Errorf("failed to read state: %w", err) } + if l1ChainId.Uint64() != st.AppliedIntent.L1ChainID { + return fmt.Errorf("rpc l1 chain ID does not match state l1 chain ID: %d != %d", l1ChainId, st.AppliedIntent.L1ChainID) + } + artifactsFS, err := artifacts.Download(ctx, st.AppliedIntent.L1ContractsLocator, nil) if err != nil { return fmt.Errorf("failed to get artifacts: %w", err) } l.Info("Downloaded artifacts", "artifacts", artifactsFS) - v, err := NewVerifier(etherscanAPIKey, chainID.Uint64(), st, artifactsFS, l) + v, err := NewVerifier(etherscanAPIKey, l1ChainId.Uint64(), st, artifactsFS, l) if err != nil { return fmt.Errorf("failed to create verifier: %w", err) } - if cliCtx.Args().Len() > 0 { - // If a contract name is provided, verify just that contract - bundleName := cliCtx.String(deployer.ContractBundleFlagName) - contractName := cliCtx.Args().First() - if err := v.VerifySingleContract(ctx, contractName, bundleName); err != nil { + // Retrieve the CLI flags for the contract bundle and contract name. + bundleName := cliCtx.String(deployer.ContractBundleFlagName) + contractName := cliCtx.String(deployer.ContractNameFlagName) + + if bundleName == "" && contractName == "" { + if err := v.verifyAll(ctx); err != nil { return err } - } else { - if err := v.VerifyAll(ctx, client, workdir); err != nil { + } else if bundleName != "" && contractName == "" { + if err := v.verifyContractBundle(bundleName); err != nil { return err } + } else if bundleName != "" && contractName != "" { + if err := v.verifySingleContract(ctx, contractName, bundleName); err != nil { + return err + } + } else { + // If a contract name is provided without a contract bundle, report an error. + return fmt.Errorf("contract-name flag provided without contract-bundle flag") } - v.log.Info("--- SUCCESS --- all requested contracts verified") + v.log.Info("--- SUCCESS ---") return nil } -func (v *Verifier) VerifySingleContract(ctx context.Context, contractName string, bundleName string) error { - l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[0].ID) - if err != nil { - return fmt.Errorf("failed to extract L1 contracts from state: %w", err) - } - - v.log.Info("Looking up contract address", "name", contractName, "bundle", bundleName) - addr, err := l1Contracts.GetContractAddress(contractName, bundleName) - if err != nil { - return fmt.Errorf("failed to find address for contract %s: %w", contractName, err) +func (v *Verifier) verifyAll(ctx context.Context) error { + for _, bundleName := range inspect.ContractBundles { + if err := v.verifyContractBundle(bundleName); err != nil { + return fmt.Errorf("failed to verify bundle %s: %w", bundleName, err) + } } - - return v.verifyContract(addr, contractName) + return nil } -func (v *Verifier) VerifyAll(ctx context.Context, client *ethclient.Client, workdir string) error { +func (v *Verifier) verifyContractBundle(bundleName string) error { + // Retrieve the L1 contracts from state. l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[0].ID) if err != nil { return fmt.Errorf("failed to extract L1 contracts from state: %w", err) } - if err := v.verifyContractBundle(l1Contracts.SuperchainDeployment); err != nil { - return err - } - if err := v.verifyContractBundle(l1Contracts.OpChainDeployment); err != nil { - return err + // Select the appropriate bundle based on the input bundleName. + var bundle interface{} + switch bundleName { + case "superchain": + bundle = l1Contracts.SuperchainDeployment + case "opchain": + bundle = l1Contracts.OpChainDeployment + case "implementations": + bundle = l1Contracts.ImplementationsDeployment + default: + return fmt.Errorf("invalid contract bundle: %s", bundleName) } - if err := v.verifyContractBundle(l1Contracts.ImplementationsDeployment); err != nil { - return err - } - return nil -} -func (v *Verifier) verifyContractBundle(s interface{}) error { - val := reflect.ValueOf(s) + // Use reflection to iterate over fields of the bundle. + val := reflect.ValueOf(bundle) typ := val.Type() - for i := 0; i < val.NumField(); i++ { field := val.Field(i) if field.Type() == reflect.TypeOf(common.Address{}) { @@ -158,129 +162,17 @@ func (v *Verifier) verifyContractBundle(s interface{}) error { return nil } -type ContractArtifact struct { - ContractName string - CompilerVersion string - OptimizationUsed bool - OptimizationRuns int - EVMVersion string - StandardInput string -} - -var exceptions = map[string]string{ - "OptimismPortalImpl": "OptimismPortal2", - "L1StandardBridgeProxy": "L1ChugSplashProxy", - "L1CrossDomainMessengerProxy": "ResolvedDelegateProxy", - "Opcm": "OPContractsManager", -} - -func getArtifactName(name string) string { - lookupName := strings.TrimSuffix(name, "Address") - - if artifactName, exists := exceptions[lookupName]; exists { - return artifactName - } - - // Handle standard cases - lookupName = strings.TrimSuffix(lookupName, "Proxy") - lookupName = strings.TrimSuffix(lookupName, "Impl") - lookupName = strings.TrimSuffix(lookupName, "Singleton") - - // If it was a proxy and not a special case, return "Proxy" - if strings.HasSuffix(name, "ProxyAddress") { - return "Proxy" - } - - return lookupName -} - -func (v *Verifier) getContractArtifact(name string) (*ContractArtifact, error) { - artifactName := getArtifactName(name) - artifactPath := path.Join(artifactName+".sol", artifactName+".json") - - v.log.Info("Opening artifact", "path", artifactPath, "name", name) - f, err := v.artifactsFS.Open(artifactPath) +func (v *Verifier) verifySingleContract(ctx context.Context, contractName string, bundleName string) error { + l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[0].ID) if err != nil { - return nil, fmt.Errorf("failed to open artifact: %w", err) - } - defer f.Close() - - var art foundry.Artifact - if err := json.NewDecoder(f).Decode(&art); err != nil { - return nil, fmt.Errorf("failed to decode artifact: %w", err) - } - - // Add all sources (main contract and dependencies) - sources := make(map[string]map[string]string) - for sourcePath, sourceInfo := range art.Metadata.Sources { - key := sourcePath - // If the source comes from OpenZeppelin, adjust the key to match the Solidity import. - if strings.HasPrefix(sourcePath, "lib/openzeppelin-contracts/contracts/") { - key = strings.Replace(sourcePath, "lib/openzeppelin-contracts/contracts/", "@openzeppelin/contracts/", 1) - } - - sources[key] = map[string]string{ - "content": sourceInfo.Content, - } - v.log.Info("added source", "originalPath", sourcePath, "key", key) - } - - var optimizer struct { - Enabled bool `json:"enabled"` - Runs int `json:"runs"` - } - if err := json.Unmarshal(art.Metadata.Settings.Optimizer, &optimizer); err != nil { - return nil, fmt.Errorf("failed to parse optimizer settings: %w", err) - } - - standardInput := map[string]interface{}{ - "language": "Solidity", - "sources": sources, - "settings": map[string]interface{}{ - "optimizer": map[string]interface{}{ - "enabled": optimizer.Enabled, - "runs": optimizer.Runs, - }, - "evmVersion": art.Metadata.Settings.EVMVersion, - "metadata": map[string]interface{}{ - "useLiteralContent": true, - "bytecodeHash": "none", - }, - "outputSelection": map[string]interface{}{ - "*": map[string]interface{}{ - "*": []string{ - "abi", - "evm.bytecode.object", - "evm.bytecode.sourceMap", - "evm.deployedBytecode.object", - "evm.deployedBytecode.sourceMap", - "metadata", - }, - }, - }, - }, + return fmt.Errorf("failed to extract L1 contracts from state: %w", err) } - standardInputJSON, err := json.Marshal(standardInput) + v.log.Info("Looking up contract address", "name", contractName, "bundle", bundleName) + addr, err := l1Contracts.GetContractAddress(contractName, bundleName) if err != nil { - return nil, fmt.Errorf("failed to generate standard input: %w", err) - } - - // Get the contract name from the compilation target - var contractName string - for contractFile, name := range art.Metadata.Settings.CompilationTarget { - contractName = contractFile + ":" + name - break + return fmt.Errorf("failed to find address for contract %s: %w", contractName, err) } - v.log.Info("contractName", "name", contractName) - - return &ContractArtifact{ - ContractName: contractName, - CompilerVersion: art.Metadata.Compiler.Version, - OptimizationUsed: optimizer.Enabled, - OptimizationRuns: optimizer.Runs, - EVMVersion: art.Metadata.Settings.EVMVersion, - StandardInput: string(standardInputJSON), - }, nil + return v.verifyContract(addr, contractName) } From fe45a220a183d5d08d2b6cb590f5b24acdd99340 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 13 Feb 2025 15:07:49 -0500 Subject: [PATCH 05/26] cleanup logs and flags --- op-deployer/pkg/deployer/flags.go | 7 +++++++ op-deployer/pkg/deployer/verify/etherscan.go | 4 +--- op-deployer/pkg/deployer/verify/verifier.go | 21 ++++++++++---------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 0d5b2d3eab7b9..6b34843c5e793 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -135,6 +135,12 @@ var ( Usage: "contract name (mathcing a field within state.json) to verify", EnvVars: PrefixEnvVar("CONTRACT_NAME"), } + L2ChainIndexFlag = &cli.IntFlag{ + Name: "l2-chain-index", + Usage: "index of the L2 chain within the state.AppliedIntent.Chains array", + EnvVars: PrefixEnvVar("L2_CHAIN_INDEX"), + Value: 0, + } ) var GlobalFlags = append([]cli.Flag{CacheDirFlag}, oplog.CLIFlags(EnvVarPrefix)...) @@ -165,6 +171,7 @@ var VerifyFlags = []cli.Flag{ EtherscanAPIKeyFlag, ContractBundleFlag, ContractNameFlag, + L2ChainIndexFlag, } func PrefixEnvVar(name string) []string { diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go index 57dfd9c7f2ab0..b6fdad6113f71 100644 --- a/op-deployer/pkg/deployer/verify/etherscan.go +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -30,8 +30,6 @@ func getAPIEndpoint(chainID uint64) string { } func (v *Verifier) verifyContract(address common.Address, contractName string) error { - v.log.Info("Preparing contract verification", "name", contractName, "address", address.Hex()) - verified, err := v.isVerified(address) if err != nil { return fmt.Errorf("failed to check verification status: %w", err) @@ -40,8 +38,8 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e v.log.Info("Contract is already verified", "name", contractName, "address", address.Hex()) return nil } - v.log.Info("Formatting etherscan verification request", "name", contractName, "address", address.Hex()) + v.log.Info("Formatting etherscan verification request", "name", contractName, "address", address.Hex()) source, err := v.getContractArtifact(contractName) if err != nil { return fmt.Errorf("failed to get contract source: %w", err) diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index 175b2f7b5e3c7..bd200e9348bbf 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -58,6 +58,7 @@ func VerifyCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) workdir := cliCtx.String(deployer.WorkdirFlagName) etherscanAPIKey := cliCtx.String(deployer.EtherscanAPIKeyFlagName) + l2ChainIndex := cliCtx.Int(deployer.L2ChainIndexFlagName) client, err := ethclient.Dial(l1RPCUrl) if err != nil { @@ -83,7 +84,7 @@ func VerifyCLI(cliCtx *cli.Context) error { if err != nil { return fmt.Errorf("failed to get artifacts: %w", err) } - l.Info("Downloaded artifacts", "artifacts", artifactsFS) + l.Info("Downloaded artifacts", "path", artifactsFS) v, err := NewVerifier(etherscanAPIKey, l1ChainId.Uint64(), st, artifactsFS, l) if err != nil { @@ -95,15 +96,15 @@ func VerifyCLI(cliCtx *cli.Context) error { contractName := cliCtx.String(deployer.ContractNameFlagName) if bundleName == "" && contractName == "" { - if err := v.verifyAll(ctx); err != nil { + if err := v.verifyAll(ctx, l2ChainIndex); err != nil { return err } } else if bundleName != "" && contractName == "" { - if err := v.verifyContractBundle(bundleName); err != nil { + if err := v.verifyContractBundle(bundleName, l2ChainIndex); err != nil { return err } } else if bundleName != "" && contractName != "" { - if err := v.verifySingleContract(ctx, contractName, bundleName); err != nil { + if err := v.verifySingleContract(ctx, contractName, bundleName, l2ChainIndex); err != nil { return err } } else { @@ -115,18 +116,18 @@ func VerifyCLI(cliCtx *cli.Context) error { return nil } -func (v *Verifier) verifyAll(ctx context.Context) error { +func (v *Verifier) verifyAll(ctx context.Context, l2ChainIndex int) error { for _, bundleName := range inspect.ContractBundles { - if err := v.verifyContractBundle(bundleName); err != nil { + if err := v.verifyContractBundle(bundleName, l2ChainIndex); err != nil { return fmt.Errorf("failed to verify bundle %s: %w", bundleName, err) } } return nil } -func (v *Verifier) verifyContractBundle(bundleName string) error { +func (v *Verifier) verifyContractBundle(bundleName string, l2ChainIndex int) error { // Retrieve the L1 contracts from state. - l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[0].ID) + l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[l2ChainIndex].ID) if err != nil { return fmt.Errorf("failed to extract L1 contracts from state: %w", err) } @@ -162,8 +163,8 @@ func (v *Verifier) verifyContractBundle(bundleName string) error { return nil } -func (v *Verifier) verifySingleContract(ctx context.Context, contractName string, bundleName string) error { - l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[0].ID) +func (v *Verifier) verifySingleContract(ctx context.Context, contractName string, bundleName string, l2ChainIndex int) error { + l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[l2ChainIndex].ID) if err != nil { return fmt.Errorf("failed to extract L1 contracts from state: %w", err) } From 7a96df5cf14065d4bfd9de4b2dbcd2abe1a7e79c Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 13 Feb 2025 15:54:50 -0500 Subject: [PATCH 06/26] cleanup: remove unused code --- op-chain-ops/foundry/artifact.go | 22 ++++++--------------- op-deployer/pkg/deployer/apply.go | 1 - op-deployer/pkg/deployer/verify/verifier.go | 14 ++++++------- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/op-chain-ops/foundry/artifact.go b/op-chain-ops/foundry/artifact.go index 4898f377d43f0..d71165ee91de1 100644 --- a/op-chain-ops/foundry/artifact.go +++ b/op-chain-ops/foundry/artifact.go @@ -18,20 +18,12 @@ import ( // JSON marshaling logic is implemented to maintain the ability // to roundtrip serialize an artifact type Artifact struct { - ABI abi.ABI - abi json.RawMessage - StorageLayout solc.StorageLayout - DeployedBytecode DeployedBytecode - Bytecode Bytecode - Metadata Metadata - ContractName string `json:"contractName"` - Version string `json:"version"` - Source string `json:"source"` - Language string `json:"language"` - CompilerVersion string `json:"compiler"` - License string `json:"license"` - MethodIdentifiers map[string]string `json:"methodIdentifiers"` - GasEstimates json.RawMessage `json:"gasEstimates"` + ABI abi.ABI + abi json.RawMessage + StorageLayout solc.StorageLayout + DeployedBytecode DeployedBytecode + Bytecode Bytecode + Metadata Metadata } func (a *Artifact) UnmarshalJSON(data []byte) error { @@ -49,7 +41,6 @@ func (a *Artifact) UnmarshalJSON(data []byte) error { a.DeployedBytecode = artifact.DeployedBytecode a.Bytecode = artifact.Bytecode a.Metadata = artifact.Metadata - a.Source = artifact.Source return nil } @@ -60,7 +51,6 @@ func (a Artifact) MarshalJSON() ([]byte, error) { DeployedBytecode: a.DeployedBytecode, Bytecode: a.Bytecode, Metadata: a.Metadata, - Source: a.Source, } return json.Marshal(artifact) } diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go index 84031fe36aa40..ef4c5ca0ff146 100644 --- a/op-deployer/pkg/deployer/apply.go +++ b/op-deployer/pkg/deployer/apply.go @@ -167,7 +167,6 @@ func ApplyPipeline( return fmt.Errorf("failed to download L1 artifacts: %w", err) } - fmt.Println("Downloading L2 artifacts") var l2ArtifactsFS foundry.StatDirFs if intent.L1ContractsLocator.Equal(intent.L2ContractsLocator) { l2ArtifactsFS = l1ArtifactsFS diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index bd200e9348bbf..195a7b353ddbb 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -37,7 +37,7 @@ func NewVerifier(apiKey string, l1ChainID uint64, st *state.State, artifactsFS f return nil, fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) } - v := &Verifier{ + return &Verifier{ apiKey: apiKey, l1ChainID: l1ChainID, st: st, @@ -45,9 +45,7 @@ func NewVerifier(apiKey string, l1ChainID uint64, st *state.State, artifactsFS f log: l, etherscanUrl: etherscanUrl, rateLimiter: rate.NewLimiter(rate.Limit(3), 2), - } - - return v, nil + }, nil } func VerifyCLI(cliCtx *cli.Context) error { @@ -135,12 +133,12 @@ func (v *Verifier) verifyContractBundle(bundleName string, l2ChainIndex int) err // Select the appropriate bundle based on the input bundleName. var bundle interface{} switch bundleName { - case "superchain": + case inspect.SuperchainBundle: bundle = l1Contracts.SuperchainDeployment - case "opchain": - bundle = l1Contracts.OpChainDeployment - case "implementations": + case inspect.ImplementationsBundle: bundle = l1Contracts.ImplementationsDeployment + case inspect.OpChainBundle: + bundle = l1Contracts.OpChainDeployment default: return fmt.Errorf("invalid contract bundle: %s", bundleName) } From 510201f2093d4404f4525114bb6d744e6f08a5c3 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Fri, 14 Feb 2025 14:53:11 -0500 Subject: [PATCH 07/26] verified implementations bundle --- op-deployer/pkg/deployer/verify/artifacts.go | 9 +- .../pkg/deployer/verify/constructors.go | 235 +++++++++++++++++- op-deployer/pkg/deployer/verify/etherscan.go | 10 +- op-deployer/pkg/deployer/verify/verifier.go | 30 ++- 4 files changed, 257 insertions(+), 27 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go index dac1361dfaab1..3221cbad4239a 100644 --- a/op-deployer/pkg/deployer/verify/artifacts.go +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -67,7 +67,7 @@ func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { for sourcePath, sourceInfo := range art.Metadata.Sources { remappedKey := art.SearchRemappings(sourcePath) sources[remappedKey] = map[string]string{"content": sourceInfo.Content} - v.log.Info("added source contract", "originalPath", sourcePath, "remappedKey", remappedKey) + v.log.Debug("added source contract", "originalPath", sourcePath, "remappedKey", remappedKey) } var optimizer struct { @@ -119,8 +119,11 @@ func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { } v.log.Info("contractName", "name", contractName) - constructorArgs := GetConstructorArgs(name, v.st) - v.log.Info("constructorArgs", "args", constructorArgs) + constructorArgs, err := v.getEncodedConstructorArgs(name) + if err != nil { + return nil, fmt.Errorf("failed to get constructor args: %w", err) + } + v.log.Debug("constructorArgs", "args", constructorArgs) return &contractArtifact{ ContractName: contractName, diff --git a/op-deployer/pkg/deployer/verify/constructors.go b/op-deployer/pkg/deployer/verify/constructors.go index 6894ef4546314..c00990eb050d5 100644 --- a/op-deployer/pkg/deployer/verify/constructors.go +++ b/op-deployer/pkg/deployer/verify/constructors.go @@ -1,22 +1,233 @@ package verify import ( - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "math/big" + "strings" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/lmittmann/w3" + "github.com/lmittmann/w3/module/eth" ) -// GetConstructorArgs returns the ABI‑encoded constructor arguments for the specified contract, so -// that they can be passed to etherscan for contract verification. -func GetConstructorArgs(contractName string, state *state.State) string { - // Normalize the contract name (for example, to lower-case) - switch contractName { - case "ProxyAdminAddress": - addr := state.AppliedIntent.SuperchainRoles.ProxyAdminOwner - padded := common.LeftPadBytes(addr.Bytes(), 32) - return hexutil.Encode(padded)[2:] +type constructorArgEncoder func(*Verifier) (string, error) + +var constructorArgEncoders = map[string]constructorArgEncoder{ + "ProxyAdminAddress": encodeProxyAdminArgs, + "OpcmAddress": encodeOpcmArgs, + "DelayedWETHImplAddress": encodeDelayedWETHArgs, + "OptimismPortalImplAddress": encodeOptimismPortalArgs, + "PreimageOracleSingletonAddress": encodePreimageOracleArgs, + "MipsSingletonAddress": encodeMipsArgs, +} + +func (v *Verifier) getEncodedConstructorArgs(contractName string) (string, error) { + encoder, exists := constructorArgEncoders[contractName] + if !exists { + return "", nil + } + return encoder(v) +} + +func encodeProxyAdminArgs(v *Verifier) (string, error) { + addr := v.st.AppliedIntent.SuperchainRoles.ProxyAdminOwner + padded := common.LeftPadBytes(addr.Bytes(), 32) + return hexutil.Encode(padded)[2:], nil +} + +func encodeDelayedWETHArgs(v *Verifier) (string, error) { + var withdrawalDelay big.Int + withdrawalDelayFn := w3.MustNewFunc("delay()", "uint256") + if err := v.w3Client.Call( + eth.CallFunc(v.st.ImplementationsDeployment.DelayedWETHImplAddress, withdrawalDelayFn).Returns(&withdrawalDelay), + ); err != nil { + return "", err + } + paddedDelay := common.LeftPadBytes(withdrawalDelay.Bytes(), 32) + return strings.TrimPrefix(hexutil.Encode(paddedDelay), "0x"), nil +} + +func encodeOptimismPortalArgs(v *Verifier) (string, error) { + var maturityDelay big.Int + proofMaturityDelayFn := w3.MustNewFunc("proofMaturityDelaySeconds()", "uint256") + if err := v.w3Client.Call( + eth.CallFunc(v.st.ImplementationsDeployment.OptimismPortalImplAddress, proofMaturityDelayFn).Returns(&maturityDelay), + ); err != nil { + return "", err + } + + var finalityDelay big.Int + disputeGameFinalityDelayFn := w3.MustNewFunc("disputeGameFinalityDelaySeconds()", "uint256") + if err := v.w3Client.Call( + eth.CallFunc(v.st.ImplementationsDeployment.OptimismPortalImplAddress, disputeGameFinalityDelayFn).Returns(&finalityDelay), + ); err != nil { + return "", err + } + + paddedMaturity := common.LeftPadBytes(maturityDelay.Bytes(), 32) + paddedFinality := common.LeftPadBytes(finalityDelay.Bytes(), 32) + concatenated := append(paddedMaturity, paddedFinality...) + return strings.TrimPrefix(hexutil.Encode(concatenated), "0x"), nil +} - default: - return "" +func encodePreimageOracleArgs(v *Verifier) (string, error) { + var minProposalSize big.Int + minProposalSizeFn := w3.MustNewFunc("minProposalSize()", "uint256") + if err := v.w3Client.Call( + eth.CallFunc(v.st.ImplementationsDeployment.PreimageOracleSingletonAddress, minProposalSizeFn).Returns(&minProposalSize), + ); err != nil { + return "", err } + + var challengePeriod big.Int + challengePeriodFn := w3.MustNewFunc("challengePeriod()", "uint256") + if err := v.w3Client.Call( + eth.CallFunc(v.st.ImplementationsDeployment.PreimageOracleSingletonAddress, challengePeriodFn).Returns(&challengePeriod), + ); err != nil { + return "", err + } + + paddedMinProposalSize := common.LeftPadBytes(minProposalSize.Bytes(), 32) + paddedChallengePeriod := common.LeftPadBytes(challengePeriod.Bytes(), 32) + concatenated := append(paddedMinProposalSize, paddedChallengePeriod...) + return strings.TrimPrefix(hexutil.Encode(concatenated), "0x"), nil +} + +func encodeMipsArgs(v *Verifier) (string, error) { + addr := v.st.ImplementationsDeployment.PreimageOracleSingletonAddress + padded := common.LeftPadBytes(addr.Bytes(), 32) + return hexutil.Encode(padded)[2:], nil +} + +func encodeOpcmArgs(v *Verifier) (string, error) { + type Blueprints struct { + AddressManager common.Address `abi:"field0"` + Proxy common.Address `abi:"field1"` + ProxyAdmin common.Address `abi:"field2"` + L1ChugSplashProxy common.Address `abi:"field3"` + ResolvedDelegateProxy common.Address `abi:"field4"` + PermissionedDisputeGame1 common.Address `abi:"field5"` + PermissionedDisputeGame2 common.Address `abi:"field6"` + PermissionlessDisputeGame1 common.Address `abi:"field7"` + PermissionlessDisputeGame2 common.Address `abi:"field8"` + } + + type Implementations struct { + SuperchainConfigImpl common.Address `abi:"field0"` + ProtocolVersionsImpl common.Address `abi:"field1"` + L1ERC721BridgeImpl common.Address `abi:"field2"` + OptimismPortalImpl common.Address `abi:"field3"` + SystemConfigImpl common.Address `abi:"field4"` + OptimismMintableERC20FactoryImpl common.Address `abi:"field5"` + L1CrossDomainMessengerImpl common.Address `abi:"field6"` + L1StandardBridgeImpl common.Address `abi:"field7"` + DisputeGameFactoryImpl common.Address `abi:"field8"` + AnchorStateRegistryImpl common.Address `abi:"field9"` + DelayedWETHImpl common.Address `abi:"field10"` + MipsImpl common.Address `abi:"field11"` + } + + var blueprints Blueprints + blueprintsFn := w3.MustNewFunc("blueprints()", "(address addressManager,address proxy,address proxyAdmin,address l1ChugSplashProxy,address resolvedDelegateProxy,address permissionedDisputeGame1,address permissionedDisputeGame2,address permissionlessDisputeGame1,address permissionlessDisputeGame2)") + if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, blueprintsFn).Returns(&blueprints)); err != nil { + return "", err + } + + var impls Implementations + implementationsFn := w3.MustNewFunc("implementations()", "(address superchainConfigImpl,address protocolVersionsImpl,address l1ERC721BridgeImpl,address optimismPortalImpl,address systemConfigImpl,address optimismMintableERC20FactoryImpl,address l1CrossDomainMessengerImpl,address l1StandardBridgeImpl,address disputeGameFactoryImpl,address anchorStateRegistryImpl,address delayedWETHImpl,address mipsImpl)") + if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, implementationsFn).Returns(&impls)); err != nil { + return "", err + } + + var release string + releaseFn := w3.MustNewFunc("l1ContractsRelease()", "string") + if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, releaseFn).Returns(&release)); err != nil { + return "", err + } + + var isRc bool + isRcFn := w3.MustNewFunc("isRC()", "bool") + if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, isRcFn).Returns(&isRc)); err != nil { + return "", err + } + if isRc { + // Opcm code appends the "-rc" suffix, so we need to remove it to recreate the constructor arg + release = strings.TrimSuffix(release, "-rc") + } + + var upgradeController common.Address + upgradeControllerFn := w3.MustNewFunc("upgradeController()", "address") + if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, upgradeControllerFn).Returns(&upgradeController)); err != nil { + return "", err + } + + var superchainConfig common.Address + superchainConfigFn := w3.MustNewFunc("superchainConfig()", "address") + if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, superchainConfigFn).Returns(&superchainConfig)); err != nil { + return "", err + } + + var protocolVersions common.Address + protocolVersionsFn := w3.MustNewFunc("protocolVersions()", "address") + if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, protocolVersionsFn).Returns(&protocolVersions)); err != nil { + return "", err + } + + var superchainProxyAdmin common.Address + superchainProxyAdminFn := w3.MustNewFunc("superchainProxyAdmin()", "address") + if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, superchainProxyAdminFn).Returns(&superchainProxyAdmin)); err != nil { + return "", err + } + + result := []byte{} + result = append(result, common.LeftPadBytes(superchainConfig.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(protocolVersions.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(superchainProxyAdmin.Bytes(), 32)...) + + // Calculate dynamic offset for _l1ContractsRelease. + // 3 addresses + // 1 dynamic offset for _l1ContractsRelease, + // 9 addresses for blueprints + // 12 addresses for implementations + // 1 address for _upgradeController. + // -------------------------------- + // Total: 26 slots + // Offset = 26 * 32 = 832 bytes. + offset := big.NewInt(26 * 32) // 832 + result = append(result, common.LeftPadBytes(offset.Bytes(), 32)...) + + // blueprints + result = append(result, common.LeftPadBytes(blueprints.AddressManager.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(blueprints.Proxy.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(blueprints.ProxyAdmin.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(blueprints.L1ChugSplashProxy.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(blueprints.ResolvedDelegateProxy.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(blueprints.PermissionedDisputeGame1.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(blueprints.PermissionedDisputeGame2.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(blueprints.PermissionlessDisputeGame1.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(blueprints.PermissionlessDisputeGame2.Bytes(), 32)...) + + // implementations + result = append(result, common.LeftPadBytes(impls.SuperchainConfigImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.ProtocolVersionsImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.L1ERC721BridgeImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.OptimismPortalImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.SystemConfigImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.OptimismMintableERC20FactoryImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.L1CrossDomainMessengerImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.L1StandardBridgeImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.DisputeGameFactoryImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.AnchorStateRegistryImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.DelayedWETHImpl.Bytes(), 32)...) + result = append(result, common.LeftPadBytes(impls.MipsImpl.Bytes(), 32)...) + + // upgrade controller + result = append(result, common.LeftPadBytes(upgradeController.Bytes(), 32)...) + + // l1ContractsRelease (dynamic args appended to the end) + releaseBytes := []byte(release) + result = append(result, common.LeftPadBytes(big.NewInt(int64(len(releaseBytes))).Bytes(), 32)...) + result = append(result, common.RightPadBytes(releaseBytes, (len(releaseBytes)+31)/32*32)...) + + return strings.TrimPrefix(hexutil.Encode(result), "0x"), nil } diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go index b6fdad6113f71..36d3b4c3b4558 100644 --- a/op-deployer/pkg/deployer/verify/etherscan.go +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -36,6 +36,7 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e } if verified { v.log.Info("Contract is already verified", "name", contractName, "address", address.Hex()) + v.numSkipped++ return nil } @@ -85,8 +86,13 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e return fmt.Errorf("verification request failed: status=%s message=%s result=%s", result.Status, result.Message, result.Result) } - v.log.Info("Verification request submitted successfully", "name", contractName, "address", address.Hex()) - return v.checkVerificationStatus(result.Result) + v.log.Info("Verification request submitted", "name", contractName, "address", address.Hex()) + err = v.checkVerificationStatus(result.Result) + if err == nil { + v.log.Info("Verification complete", "name", contractName, "address", address.Hex()) + v.numVerified++ + } + return err } // sendRateLimitedRequest is a helper function which waits for a rate limit token diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index 195a7b353ddbb..5baf2fef8ac16 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -6,8 +6,9 @@ import ( "reflect" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/lmittmann/w3" + "github.com/lmittmann/w3/module/eth" "github.com/urfave/cli/v2" "golang.org/x/time/rate" @@ -29,9 +30,12 @@ type Verifier struct { log log.Logger etherscanUrl string rateLimiter *rate.Limiter + w3Client *w3.Client + numVerified int + numSkipped int } -func NewVerifier(apiKey string, l1ChainID uint64, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger) (*Verifier, error) { +func NewVerifier(apiKey string, l1ChainID uint64, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger, w3Client *w3.Client) (*Verifier, error) { etherscanUrl := getAPIEndpoint(l1ChainID) if etherscanUrl == "" { return nil, fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) @@ -45,6 +49,7 @@ func NewVerifier(apiKey string, l1ChainID uint64, st *state.State, artifactsFS f log: l, etherscanUrl: etherscanUrl, rateLimiter: rate.NewLimiter(rate.Limit(3), 2), + w3Client: w3Client, }, nil } @@ -58,15 +63,17 @@ func VerifyCLI(cliCtx *cli.Context) error { etherscanAPIKey := cliCtx.String(deployer.EtherscanAPIKeyFlagName) l2ChainIndex := cliCtx.Int(deployer.L2ChainIndexFlagName) - client, err := ethclient.Dial(l1RPCUrl) + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + w3Client, err := w3.Dial(l1RPCUrl) if err != nil { return fmt.Errorf("failed to connect to L1: %w", err) } + defer w3Client.Close() - ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) - l1ChainId, err := client.ChainID(ctx) - if err != nil { - return err + var l1ChainId uint64 + if err := w3Client.Call(eth.ChainID().Returns(&l1ChainId)); err != nil { + return fmt.Errorf("failed to get chain ID: %w", err) } st, err := pipeline.ReadState(workdir) @@ -74,7 +81,7 @@ func VerifyCLI(cliCtx *cli.Context) error { return fmt.Errorf("failed to read state: %w", err) } - if l1ChainId.Uint64() != st.AppliedIntent.L1ChainID { + if l1ChainId != st.AppliedIntent.L1ChainID { return fmt.Errorf("rpc l1 chain ID does not match state l1 chain ID: %d != %d", l1ChainId, st.AppliedIntent.L1ChainID) } @@ -84,7 +91,7 @@ func VerifyCLI(cliCtx *cli.Context) error { } l.Info("Downloaded artifacts", "path", artifactsFS) - v, err := NewVerifier(etherscanAPIKey, l1ChainId.Uint64(), st, artifactsFS, l) + v, err := NewVerifier(etherscanAPIKey, l1ChainId, st, artifactsFS, l, w3Client) if err != nil { return fmt.Errorf("failed to create verifier: %w", err) } @@ -93,6 +100,10 @@ func VerifyCLI(cliCtx *cli.Context) error { bundleName := cliCtx.String(deployer.ContractBundleFlagName) contractName := cliCtx.String(deployer.ContractNameFlagName) + defer func() { + v.log.Info("final results", "numVerified", v.numVerified, "numSkipped", v.numSkipped) + }() + if bundleName == "" && contractName == "" { if err := v.verifyAll(ctx, l2ChainIndex); err != nil { return err @@ -109,7 +120,6 @@ func VerifyCLI(cliCtx *cli.Context) error { // If a contract name is provided without a contract bundle, report an error. return fmt.Errorf("contract-name flag provided without contract-bundle flag") } - v.log.Info("--- SUCCESS ---") return nil } From f163c3e40cf31bdfb0c747986b32e94f88ddcf05 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Fri, 14 Feb 2025 17:31:21 -0500 Subject: [PATCH 08/26] verified opchain bundle --- .../pkg/deployer/verify/constructors.go | 90 +++++++++++++++++++ op-deployer/pkg/deployer/verify/verifier.go | 6 +- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/constructors.go b/op-deployer/pkg/deployer/verify/constructors.go index c00990eb050d5..e9f7703d7660e 100644 --- a/op-deployer/pkg/deployer/verify/constructors.go +++ b/op-deployer/pkg/deployer/verify/constructors.go @@ -19,6 +19,8 @@ var constructorArgEncoders = map[string]constructorArgEncoder{ "OptimismPortalImplAddress": encodeOptimismPortalArgs, "PreimageOracleSingletonAddress": encodePreimageOracleArgs, "MipsSingletonAddress": encodeMipsArgs, + "SuperchainConfigProxyAddress": encodeSuperchainConfigProxyArgs, + "PermissionedDisputeGameAddress": encodePermissionedDisputeGameArgs, } func (v *Verifier) getEncodedConstructorArgs(contractName string) (string, error) { @@ -231,3 +233,91 @@ func encodeOpcmArgs(v *Verifier) (string, error) { return strings.TrimPrefix(hexutil.Encode(result), "0x"), nil } + +func encodeSuperchainConfigProxyArgs(v *Verifier) (string, error) { + addr := v.st.SuperchainDeployment.ProxyAdminAddress + padded := common.LeftPadBytes(addr.Bytes(), 32) + return strings.TrimPrefix(hexutil.Encode(padded), "0x"), nil +} + +func encodePermissionedDisputeGameArgs(v *Verifier) (string, error) { + addr := v.st.Chains[v.l2ChainIndex].PermissionedDisputeGameAddress + result := []byte{} + + var gameType uint32 + gameTypeFn := w3.MustNewFunc("gameType()", "uint32") + if err := v.w3Client.Call(eth.CallFunc(addr, gameTypeFn).Returns(&gameType)); err != nil { + return "", err + } + result = append(result, common.LeftPadBytes(big.NewInt(int64(gameType)).Bytes(), 32)...) + + var absolutePrestate [32]byte + absolutePrestateFn := w3.MustNewFunc("absolutePrestate()", "bytes32") + if err := v.w3Client.Call(eth.CallFunc(addr, absolutePrestateFn).Returns(&absolutePrestate)); err != nil { + return "", err + } + result = append(result, absolutePrestate[:]...) + + var maxGameDepth big.Int + maxGameDepthFn := w3.MustNewFunc("maxGameDepth()", "uint256") + if err := v.w3Client.Call(eth.CallFunc(addr, maxGameDepthFn).Returns(&maxGameDepth)); err != nil { + return "", err + } + result = append(result, common.LeftPadBytes(maxGameDepth.Bytes(), 32)...) + + var splitDepth big.Int + splitDepthFn := w3.MustNewFunc("splitDepth()", "uint256") + if err := v.w3Client.Call(eth.CallFunc(addr, splitDepthFn).Returns(&splitDepth)); err != nil { + return "", err + } + result = append(result, common.LeftPadBytes(splitDepth.Bytes(), 32)...) + + var clockExtension uint64 + clockExtensionFn := w3.MustNewFunc("clockExtension()", "uint64") + if err := v.w3Client.Call(eth.CallFunc(addr, clockExtensionFn).Returns(&clockExtension)); err != nil { + return "", err + } + result = append(result, common.LeftPadBytes(big.NewInt(int64(clockExtension)).Bytes(), 32)...) + + var maxClockDuration uint64 + maxClockDurationFn := w3.MustNewFunc("maxClockDuration()", "uint64") + if err := v.w3Client.Call(eth.CallFunc(addr, maxClockDurationFn).Returns(&maxClockDuration)); err != nil { + return "", err + } + result = append(result, common.LeftPadBytes(big.NewInt(int64(maxClockDuration)).Bytes(), 32)...) + var vm common.Address + vmFn := w3.MustNewFunc("vm()", "address") + if err := v.w3Client.Call(eth.CallFunc(addr, vmFn).Returns(&vm)); err != nil { + return "", err + } + result = append(result, common.LeftPadBytes(vm.Bytes(), 32)...) + + var weth common.Address + wethFn := w3.MustNewFunc("weth()", "address") + if err := v.w3Client.Call(eth.CallFunc(addr, wethFn).Returns(&weth)); err != nil { + return "", err + } + result = append(result, common.LeftPadBytes(weth.Bytes(), 32)...) + + var anchorStateRegistry common.Address + anchorStateRegistryFn := w3.MustNewFunc("anchorStateRegistry()", "address") + if err := v.w3Client.Call(eth.CallFunc(addr, anchorStateRegistryFn).Returns(&anchorStateRegistry)); err != nil { + return "", err + } + result = append(result, common.LeftPadBytes(anchorStateRegistry.Bytes(), 32)...) + + var l2ChainId big.Int + l2ChainIdFn := w3.MustNewFunc("l2ChainId()", "uint256") + if err := v.w3Client.Call(eth.CallFunc(addr, l2ChainIdFn).Returns(&l2ChainId)); err != nil { + return "", err + } + result = append(result, common.LeftPadBytes(l2ChainId.Bytes(), 32)...) + + proposer := v.st.AppliedIntent.Chains[v.l2ChainIndex].Roles.Proposer + result = append(result, common.LeftPadBytes(proposer.Bytes(), 32)...) + + challenger := v.st.AppliedIntent.Chains[v.l2ChainIndex].Roles.Challenger + result = append(result, common.LeftPadBytes(challenger.Bytes(), 32)...) + + return strings.TrimPrefix(hexutil.Encode(result), "0x"), nil +} diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index 5baf2fef8ac16..96740ad640faf 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -25,6 +25,7 @@ import ( type Verifier struct { apiKey string l1ChainID uint64 + l2ChainIndex int st *state.State artifactsFS foundry.StatDirFs log log.Logger @@ -35,7 +36,7 @@ type Verifier struct { numSkipped int } -func NewVerifier(apiKey string, l1ChainID uint64, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger, w3Client *w3.Client) (*Verifier, error) { +func NewVerifier(apiKey string, l1ChainID uint64, l2ChainIndex int, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger, w3Client *w3.Client) (*Verifier, error) { etherscanUrl := getAPIEndpoint(l1ChainID) if etherscanUrl == "" { return nil, fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) @@ -44,6 +45,7 @@ func NewVerifier(apiKey string, l1ChainID uint64, st *state.State, artifactsFS f return &Verifier{ apiKey: apiKey, l1ChainID: l1ChainID, + l2ChainIndex: l2ChainIndex, st: st, artifactsFS: artifactsFS, log: l, @@ -91,7 +93,7 @@ func VerifyCLI(cliCtx *cli.Context) error { } l.Info("Downloaded artifacts", "path", artifactsFS) - v, err := NewVerifier(etherscanAPIKey, l1ChainId, st, artifactsFS, l, w3Client) + v, err := NewVerifier(etherscanAPIKey, l1ChainId, l2ChainIndex, st, artifactsFS, l, w3Client) if err != nil { return fmt.Errorf("failed to create verifier: %w", err) } From ebd7561cc944c506a763f7b6994149605594573c Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Mon, 17 Feb 2025 10:20:52 -0500 Subject: [PATCH 09/26] update searchRemappings comment --- op-chain-ops/foundry/artifact.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/op-chain-ops/foundry/artifact.go b/op-chain-ops/foundry/artifact.go index d71165ee91de1..a3e0a6e07678f 100644 --- a/op-chain-ops/foundry/artifact.go +++ b/op-chain-ops/foundry/artifact.go @@ -156,8 +156,9 @@ func ReadArtifact(path string) (*Artifact, error) { return &artifact, nil } -// SearchRemappings applies the configured remappings to a given source path. -// It assumes that each remapping is of the form "alias/=actualPath". +// SearchRemappings applies the configured remappings to a given source path, +// or returns the source path unchanged if no remapping is found. It assumes that +// each remapping is of the form "alias/=actualPath". func (a Artifact) SearchRemappings(sourcePath string) string { for _, mapping := range a.Metadata.Settings.Remappings { parts := strings.Split(mapping, "/=") From 301a75b9b32b1e56b1fa1537b3cb65015e210f09 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Mon, 17 Feb 2025 13:57:22 -0500 Subject: [PATCH 10/26] use more type-safe structs --- op-deployer/pkg/deployer/verify/artifacts.go | 64 ++++------------ op-deployer/pkg/deployer/verify/etherscan.go | 81 ++++++++++++++++++-- 2 files changed, 90 insertions(+), 55 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go index 3221cbad4239a..6c70b5915d34e 100644 --- a/op-deployer/pkg/deployer/verify/artifacts.go +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -10,13 +10,12 @@ import ( ) type contractArtifact struct { - ContractName string - CompilerVersion string - OptimizationUsed bool - OptimizationRuns int - EVMVersion string - StandardInput string - ConstructorArgs string + ContractName string + CompilerVersion string + Optimizer OptimizerSettings + EVMVersion string + StandardInput string + ConstructorArgs string } // Map state.json struct's contract field names to forge artifact names @@ -63,49 +62,19 @@ func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { } // Add all sources (main contract and dependencies) - sources := make(map[string]map[string]string) + sources := make(map[string]SourceContent) for sourcePath, sourceInfo := range art.Metadata.Sources { remappedKey := art.SearchRemappings(sourcePath) - sources[remappedKey] = map[string]string{"content": sourceInfo.Content} + sources[remappedKey] = SourceContent{Content: sourceInfo.Content} v.log.Debug("added source contract", "originalPath", sourcePath, "remappedKey", remappedKey) } - var optimizer struct { - Enabled bool `json:"enabled"` - Runs int `json:"runs"` - } + var optimizer OptimizerSettings if err := json.Unmarshal(art.Metadata.Settings.Optimizer, &optimizer); err != nil { return nil, fmt.Errorf("failed to parse optimizer settings: %w", err) } - standardInput := map[string]interface{}{ - "language": "Solidity", - "sources": sources, - "settings": map[string]interface{}{ - "optimizer": map[string]interface{}{ - "enabled": optimizer.Enabled, - "runs": optimizer.Runs, - }, - "evmVersion": art.Metadata.Settings.EVMVersion, - "metadata": map[string]interface{}{ - "useLiteralContent": true, - "bytecodeHash": "none", - }, - "outputSelection": map[string]interface{}{ - "*": map[string]interface{}{ - "*": []string{ - "abi", - "evm.bytecode.object", - "evm.bytecode.sourceMap", - "evm.deployedBytecode.object", - "evm.deployedBytecode.sourceMap", - "metadata", - }, - }, - }, - }, - } - + standardInput := newStandardInput(sources, optimizer, art.Metadata.Settings.EVMVersion) standardInputJSON, err := json.Marshal(standardInput) if err != nil { return nil, fmt.Errorf("failed to generate standard input: %w", err) @@ -126,12 +95,11 @@ func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { v.log.Debug("constructorArgs", "args", constructorArgs) return &contractArtifact{ - ContractName: contractName, - CompilerVersion: art.Metadata.Compiler.Version, - OptimizationUsed: optimizer.Enabled, - OptimizationRuns: optimizer.Runs, - EVMVersion: art.Metadata.Settings.EVMVersion, - StandardInput: string(standardInputJSON), - ConstructorArgs: constructorArgs, + ContractName: contractName, + CompilerVersion: art.Metadata.Compiler.Version, + Optimizer: optimizer, + EVMVersion: art.Metadata.Settings.EVMVersion, + StandardInput: string(standardInputJSON), + ConstructorArgs: constructorArgs, }, nil } diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go index 36d3b4c3b4558..9f1a5e5b31c20 100644 --- a/op-deployer/pkg/deployer/verify/etherscan.go +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -47,7 +47,7 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e } optimized := "0" - if source.OptimizationUsed { + if source.Optimizer.Enabled { optimized = "1" } @@ -61,7 +61,7 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e "contractname": {source.ContractName}, "compilerversion": {fmt.Sprintf("v%s", source.CompilerVersion)}, "optimizationUsed": {optimized}, - "runs": {fmt.Sprintf("%d", source.OptimizationRuns)}, + "runs": {fmt.Sprintf("%d", source.Optimizer.Runs)}, "evmversion": {source.EVMVersion}, "constructorArguements": {source.ConstructorArgs}, } @@ -144,11 +144,7 @@ func (v *Verifier) checkVerificationStatus(reqId string) error { } defer resp.Body.Close() - var result struct { - Status string `json:"status"` - Message string `json:"message"` - Result string `json:"result"` - } + var result EtherscanResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return fmt.Errorf("failed to decode checkverifystatus response: %w", err) } @@ -166,3 +162,74 @@ func (v *Verifier) checkVerificationStatus(reqId string) error { } return fmt.Errorf("verification timed out") } + +type StandardInput struct { + Language string `json:"language"` + Sources map[string]SourceContent `json:"sources"` + Settings Settings `json:"settings"` +} + +type SourceContent struct { + Content string `json:"content"` +} + +type Settings struct { + Optimizer OptimizerSettings `json:"optimizer"` + EVMVersion string `json:"evmVersion"` + Metadata MetadataSettings `json:"metadata"` + OutputSelection OutputSelection `json:"outputSelection"` +} + +type OptimizerSettings struct { + Enabled bool `json:"enabled"` + Runs int `json:"runs"` +} + +type MetadataSettings struct { + UseLiteralContent bool `json:"useLiteralContent"` + BytecodeHash string `json:"bytecodeHash"` +} + +type OutputSelection struct { + All map[string]OutputSelectionDetails `json:"*"` +} + +type OutputSelectionDetails struct { + All []string `json:"*"` +} + +func newStandardInput( + sources map[string]SourceContent, + optimizer OptimizerSettings, + evmVersion string, +) StandardInput { + return StandardInput{ + Language: "Solidity", + Sources: sources, + Settings: Settings{ + Optimizer: OptimizerSettings{ + Enabled: optimizer.Enabled, + Runs: optimizer.Runs, + }, + EVMVersion: evmVersion, + Metadata: MetadataSettings{ + UseLiteralContent: true, + BytecodeHash: "none", + }, + OutputSelection: OutputSelection{ + All: map[string]OutputSelectionDetails{ + "*": { + All: []string{ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap", + "metadata", + }, + }, + }, + }, + }, + } +} From fc284a70e04057f32fb6cb9d0f0b9d15aa5a24e7 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Mon, 17 Feb 2025 17:53:35 -0500 Subject: [PATCH 11/26] use l2-chain-id flag instead of l2-chain-index --- op-deployer/pkg/deployer/flags.go | 19 ++++---- .../pkg/deployer/verify/constructors.go | 14 ++++-- op-deployer/pkg/deployer/verify/verifier.go | 48 ++++++++++++------- 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 6b34843c5e793..47af4f254f838 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -24,6 +24,8 @@ const ( IntentTypeFlagName = "intent-type" EtherscanAPIKeyFlagName = "etherscan-api-key" ContractBundleFlagName = "contract-bundle" + ContractNameFlagName = "contract-name" + L2ChainIDFlagName = "l2-chain-id" ) type DeploymentTarget string @@ -87,6 +89,11 @@ var ( Usage: "Comma-separated list of L2 chain IDs to deploy.", EnvVars: PrefixEnvVar("L2_CHAIN_IDS"), } + L2ChainIDFlag = &cli.StringFlag{ + Name: L2ChainIDFlagName, + Usage: "Single L2 chain ID", + EnvVars: PrefixEnvVar("L2_CHAIN_ID"), + } WorkdirFlag = &cli.StringFlag{ Name: WorkdirFlagName, Usage: "Directory storing intent and stage. Defaults to the current directory.", @@ -127,20 +134,14 @@ var ( } ContractBundleFlag = &cli.StringFlag{ Name: ContractBundleFlagName, - Usage: "contract bundle/grouping to verify (superchain, opchain, or implementations)", + Usage: "contract bundle/grouping (superchain|implementations|opchain)", EnvVars: PrefixEnvVar("CONTRACT_BUNDLE"), } ContractNameFlag = &cli.StringFlag{ Name: ContractNameFlagName, - Usage: "contract name (mathcing a field within state.json) to verify", + Usage: "contract name (matching a field within state.json)", EnvVars: PrefixEnvVar("CONTRACT_NAME"), } - L2ChainIndexFlag = &cli.IntFlag{ - Name: "l2-chain-index", - Usage: "index of the L2 chain within the state.AppliedIntent.Chains array", - EnvVars: PrefixEnvVar("L2_CHAIN_INDEX"), - Value: 0, - } ) var GlobalFlags = append([]cli.Flag{CacheDirFlag}, oplog.CLIFlags(EnvVarPrefix)...) @@ -171,7 +172,7 @@ var VerifyFlags = []cli.Flag{ EtherscanAPIKeyFlag, ContractBundleFlag, ContractNameFlag, - L2ChainIndexFlag, + L2ChainIDFlag, } func PrefixEnvVar(name string) []string { diff --git a/op-deployer/pkg/deployer/verify/constructors.go b/op-deployer/pkg/deployer/verify/constructors.go index e9f7703d7660e..fa1cdb193d21a 100644 --- a/op-deployer/pkg/deployer/verify/constructors.go +++ b/op-deployer/pkg/deployer/verify/constructors.go @@ -241,7 +241,11 @@ func encodeSuperchainConfigProxyArgs(v *Verifier) (string, error) { } func encodePermissionedDisputeGameArgs(v *Verifier) (string, error) { - addr := v.st.Chains[v.l2ChainIndex].PermissionedDisputeGameAddress + chainState, err := v.st.Chain(v.l2ChainID) + if err != nil { + return "", err + } + addr := chainState.PermissionedDisputeGameAddress result := []byte{} var gameType uint32 @@ -313,10 +317,14 @@ func encodePermissionedDisputeGameArgs(v *Verifier) (string, error) { } result = append(result, common.LeftPadBytes(l2ChainId.Bytes(), 32)...) - proposer := v.st.AppliedIntent.Chains[v.l2ChainIndex].Roles.Proposer + chainIntent, err := v.st.AppliedIntent.Chain(v.l2ChainID) + if err != nil { + return "", err + } + proposer := chainIntent.Roles.Proposer result = append(result, common.LeftPadBytes(proposer.Bytes(), 32)...) - challenger := v.st.AppliedIntent.Chains[v.l2ChainIndex].Roles.Challenger + challenger := chainIntent.Roles.Challenger result = append(result, common.LeftPadBytes(challenger.Bytes(), 32)...) return strings.TrimPrefix(hexutil.Encode(result), "0x"), nil diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index 96740ad640faf..b05f34212d7f6 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -18,6 +18,7 @@ import ( "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/inspect" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + op_service "github.com/ethereum-optimism/optimism/op-service" "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" oplog "github.com/ethereum-optimism/optimism/op-service/log" ) @@ -25,7 +26,7 @@ import ( type Verifier struct { apiKey string l1ChainID uint64 - l2ChainIndex int + l2ChainID common.Hash st *state.State artifactsFS foundry.StatDirFs log log.Logger @@ -36,16 +37,20 @@ type Verifier struct { numSkipped int } -func NewVerifier(apiKey string, l1ChainID uint64, l2ChainIndex int, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger, w3Client *w3.Client) (*Verifier, error) { +func NewVerifier(apiKey string, l1ChainID uint64, l2ChainID common.Hash, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger, w3Client *w3.Client) (*Verifier, error) { etherscanUrl := getAPIEndpoint(l1ChainID) if etherscanUrl == "" { return nil, fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) } + if l2ChainID == (common.Hash{}) { + l2ChainID = st.AppliedIntent.Chains[0].ID + } + return &Verifier{ apiKey: apiKey, l1ChainID: l1ChainID, - l2ChainIndex: l2ChainIndex, + l2ChainID: l2ChainID, st: st, artifactsFS: artifactsFS, log: l, @@ -63,7 +68,18 @@ func VerifyCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) workdir := cliCtx.String(deployer.WorkdirFlagName) etherscanAPIKey := cliCtx.String(deployer.EtherscanAPIKeyFlagName) - l2ChainIndex := cliCtx.Int(deployer.L2ChainIndexFlagName) + bundleName := cliCtx.String(deployer.ContractBundleFlagName) + contractName := cliCtx.String(deployer.ContractNameFlagName) + l2ChainIDRaw := cliCtx.String(deployer.L2ChainIDFlagName) + + var l2ChainID common.Hash + var err error + if l2ChainIDRaw != "" { + l2ChainID, err = op_service.Parse256BitChainID(l2ChainIDRaw) + if err != nil { + return fmt.Errorf("invalid L2 chain ID '%s': %w", l2ChainIDRaw, err) + } + } ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) @@ -93,29 +109,25 @@ func VerifyCLI(cliCtx *cli.Context) error { } l.Info("Downloaded artifacts", "path", artifactsFS) - v, err := NewVerifier(etherscanAPIKey, l1ChainId, l2ChainIndex, st, artifactsFS, l, w3Client) + v, err := NewVerifier(etherscanAPIKey, l1ChainId, l2ChainID, st, artifactsFS, l, w3Client) if err != nil { return fmt.Errorf("failed to create verifier: %w", err) } - // Retrieve the CLI flags for the contract bundle and contract name. - bundleName := cliCtx.String(deployer.ContractBundleFlagName) - contractName := cliCtx.String(deployer.ContractNameFlagName) - defer func() { v.log.Info("final results", "numVerified", v.numVerified, "numSkipped", v.numSkipped) }() if bundleName == "" && contractName == "" { - if err := v.verifyAll(ctx, l2ChainIndex); err != nil { + if err := v.verifyAll(ctx); err != nil { return err } } else if bundleName != "" && contractName == "" { - if err := v.verifyContractBundle(bundleName, l2ChainIndex); err != nil { + if err := v.verifyContractBundle(bundleName); err != nil { return err } } else if bundleName != "" && contractName != "" { - if err := v.verifySingleContract(ctx, contractName, bundleName, l2ChainIndex); err != nil { + if err := v.verifySingleContract(ctx, contractName, bundleName); err != nil { return err } } else { @@ -126,18 +138,18 @@ func VerifyCLI(cliCtx *cli.Context) error { return nil } -func (v *Verifier) verifyAll(ctx context.Context, l2ChainIndex int) error { +func (v *Verifier) verifyAll(ctx context.Context) error { for _, bundleName := range inspect.ContractBundles { - if err := v.verifyContractBundle(bundleName, l2ChainIndex); err != nil { + if err := v.verifyContractBundle(bundleName); err != nil { return fmt.Errorf("failed to verify bundle %s: %w", bundleName, err) } } return nil } -func (v *Verifier) verifyContractBundle(bundleName string, l2ChainIndex int) error { +func (v *Verifier) verifyContractBundle(bundleName string) error { // Retrieve the L1 contracts from state. - l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[l2ChainIndex].ID) + l1Contracts, err := inspect.L1(v.st, v.l2ChainID) if err != nil { return fmt.Errorf("failed to extract L1 contracts from state: %w", err) } @@ -173,8 +185,8 @@ func (v *Verifier) verifyContractBundle(bundleName string, l2ChainIndex int) err return nil } -func (v *Verifier) verifySingleContract(ctx context.Context, contractName string, bundleName string, l2ChainIndex int) error { - l1Contracts, err := inspect.L1(v.st, v.st.AppliedIntent.Chains[l2ChainIndex].ID) +func (v *Verifier) verifySingleContract(ctx context.Context, contractName string, bundleName string) error { + l1Contracts, err := inspect.L1(v.st, v.l2ChainID) if err != nil { return fmt.Errorf("failed to extract L1 contracts from state: %w", err) } From 238f3315c892307da75de7f0be8d996e53359ff7 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Tue, 25 Feb 2025 15:50:43 -0500 Subject: [PATCH 12/26] update to use new artifacts.Download function definition --- op-deployer/pkg/deployer/flags.go | 7 ++++--- op-deployer/pkg/deployer/verify/verifier.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 47af4f254f838..48e19e16cc5e5 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -52,14 +52,15 @@ func NewDeploymentTarget(s string) (DeploymentTarget, error) { } } -var homeDir string +var DefaultCacheDir string func init() { var err error - homeDir, err = os.UserHomeDir() + homeDir, err := os.UserHomeDir() if err != nil { panic(fmt.Sprintf("failed to get home directory: %s", err)) } + DefaultCacheDir = path.Join(homeDir, ".op-deployer/cache") } var ( @@ -76,7 +77,7 @@ var ( Usage: "Cache directory. " + "If set, the deployer will attempt to cache downloaded artifacts in the specified directory.", EnvVars: PrefixEnvVar("CACHE_DIR"), - Value: path.Join(homeDir, ".op-deployer/cache"), + Value: DefaultCacheDir, } L1ChainIDFlag = &cli.Uint64Flag{ Name: L1ChainIDFlagName, diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index b05f34212d7f6..99d0aad648aae 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -103,7 +103,7 @@ func VerifyCLI(cliCtx *cli.Context) error { return fmt.Errorf("rpc l1 chain ID does not match state l1 chain ID: %d != %d", l1ChainId, st.AppliedIntent.L1ChainID) } - artifactsFS, err := artifacts.Download(ctx, st.AppliedIntent.L1ContractsLocator, nil) + artifactsFS, err := artifacts.Download(ctx, st.AppliedIntent.L1ContractsLocator, nil, deployer.DefaultCacheDir) if err != nil { return fmt.Errorf("failed to get artifacts: %w", err) } From ff014da7e23b89dc3da2378948ee24721494a10e Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Tue, 4 Mar 2025 17:19:26 -0500 Subject: [PATCH 13/26] read constructor args from deployment tx initcode --- op-deployer/pkg/deployer/verify/artifacts.go | 10 +- .../pkg/deployer/verify/constructors.go | 344 ++---------------- op-deployer/pkg/deployer/verify/etherscan.go | 131 ++++--- op-deployer/pkg/deployer/verify/verifier.go | 105 ++++-- 4 files changed, 184 insertions(+), 406 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go index 6c70b5915d34e..0a97a1fddeda4 100644 --- a/op-deployer/pkg/deployer/verify/artifacts.go +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -15,10 +15,9 @@ type contractArtifact struct { Optimizer OptimizerSettings EVMVersion string StandardInput string - ConstructorArgs string } -// Map state.json struct's contract field names to forge artifact names +// Map state.json struct fields to forge artifact names var contractNameExceptions = map[string]string{ "OptimismPortalImpl": "OptimismPortal2", "L1StandardBridgeProxy": "L1ChugSplashProxy", @@ -88,18 +87,11 @@ func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { } v.log.Info("contractName", "name", contractName) - constructorArgs, err := v.getEncodedConstructorArgs(name) - if err != nil { - return nil, fmt.Errorf("failed to get constructor args: %w", err) - } - v.log.Debug("constructorArgs", "args", constructorArgs) - return &contractArtifact{ ContractName: contractName, CompilerVersion: art.Metadata.Compiler.Version, Optimizer: optimizer, EVMVersion: art.Metadata.Settings.EVMVersion, StandardInput: string(standardInputJSON), - ConstructorArgs: constructorArgs, }, nil } diff --git a/op-deployer/pkg/deployer/verify/constructors.go b/op-deployer/pkg/deployer/verify/constructors.go index fa1cdb193d21a..805abc997bb9e 100644 --- a/op-deployer/pkg/deployer/verify/constructors.go +++ b/op-deployer/pkg/deployer/verify/constructors.go @@ -1,331 +1,53 @@ package verify import ( - "math/big" - "strings" + "context" + "encoding/hex" + "fmt" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/lmittmann/w3" - "github.com/lmittmann/w3/module/eth" ) -type constructorArgEncoder func(*Verifier) (string, error) - -var constructorArgEncoders = map[string]constructorArgEncoder{ - "ProxyAdminAddress": encodeProxyAdminArgs, - "OpcmAddress": encodeOpcmArgs, - "DelayedWETHImplAddress": encodeDelayedWETHArgs, - "OptimismPortalImplAddress": encodeOptimismPortalArgs, - "PreimageOracleSingletonAddress": encodePreimageOracleArgs, - "MipsSingletonAddress": encodeMipsArgs, - "SuperchainConfigProxyAddress": encodeSuperchainConfigProxyArgs, - "PermissionedDisputeGameAddress": encodePermissionedDisputeGameArgs, -} - -func (v *Verifier) getEncodedConstructorArgs(contractName string) (string, error) { - encoder, exists := constructorArgEncoders[contractName] - if !exists { +var constructorArgSlots = map[string]int{ + "ProxyAdminAddress": 1, + "OpcmAddress": 28, + "DelayedWETHImplAddress": 1, + "OptimismPortalImplAddress": 2, + "PreimageOracleSingletonAddress": 2, + "MipsSingletonAddress": 1, + "SuperchainConfigProxyAddress": 1, + "PermissionedDisputeGameAddress": 12, + "FaultDisputeGameAddress": 10, +} + +func (v *Verifier) getConstructorArgs(ctx context.Context, address common.Address, contractName string) (string, error) { + argSlots, ok := constructorArgSlots[contractName] + if !ok { return "", nil } - return encoder(v) -} - -func encodeProxyAdminArgs(v *Verifier) (string, error) { - addr := v.st.AppliedIntent.SuperchainRoles.ProxyAdminOwner - padded := common.LeftPadBytes(addr.Bytes(), 32) - return hexutil.Encode(padded)[2:], nil -} - -func encodeDelayedWETHArgs(v *Verifier) (string, error) { - var withdrawalDelay big.Int - withdrawalDelayFn := w3.MustNewFunc("delay()", "uint256") - if err := v.w3Client.Call( - eth.CallFunc(v.st.ImplementationsDeployment.DelayedWETHImplAddress, withdrawalDelayFn).Returns(&withdrawalDelay), - ); err != nil { - return "", err - } - paddedDelay := common.LeftPadBytes(withdrawalDelay.Bytes(), 32) - return strings.TrimPrefix(hexutil.Encode(paddedDelay), "0x"), nil -} - -func encodeOptimismPortalArgs(v *Verifier) (string, error) { - var maturityDelay big.Int - proofMaturityDelayFn := w3.MustNewFunc("proofMaturityDelaySeconds()", "uint256") - if err := v.w3Client.Call( - eth.CallFunc(v.st.ImplementationsDeployment.OptimismPortalImplAddress, proofMaturityDelayFn).Returns(&maturityDelay), - ); err != nil { - return "", err - } - - var finalityDelay big.Int - disputeGameFinalityDelayFn := w3.MustNewFunc("disputeGameFinalityDelaySeconds()", "uint256") - if err := v.w3Client.Call( - eth.CallFunc(v.st.ImplementationsDeployment.OptimismPortalImplAddress, disputeGameFinalityDelayFn).Returns(&finalityDelay), - ); err != nil { - return "", err - } - - paddedMaturity := common.LeftPadBytes(maturityDelay.Bytes(), 32) - paddedFinality := common.LeftPadBytes(finalityDelay.Bytes(), 32) - concatenated := append(paddedMaturity, paddedFinality...) - return strings.TrimPrefix(hexutil.Encode(concatenated), "0x"), nil -} - -func encodePreimageOracleArgs(v *Verifier) (string, error) { - var minProposalSize big.Int - minProposalSizeFn := w3.MustNewFunc("minProposalSize()", "uint256") - if err := v.w3Client.Call( - eth.CallFunc(v.st.ImplementationsDeployment.PreimageOracleSingletonAddress, minProposalSizeFn).Returns(&minProposalSize), - ); err != nil { - return "", err - } - - var challengePeriod big.Int - challengePeriodFn := w3.MustNewFunc("challengePeriod()", "uint256") - if err := v.w3Client.Call( - eth.CallFunc(v.st.ImplementationsDeployment.PreimageOracleSingletonAddress, challengePeriodFn).Returns(&challengePeriod), - ); err != nil { - return "", err - } - - paddedMinProposalSize := common.LeftPadBytes(minProposalSize.Bytes(), 32) - paddedChallengePeriod := common.LeftPadBytes(challengePeriod.Bytes(), 32) - concatenated := append(paddedMinProposalSize, paddedChallengePeriod...) - return strings.TrimPrefix(hexutil.Encode(concatenated), "0x"), nil -} - -func encodeMipsArgs(v *Verifier) (string, error) { - addr := v.st.ImplementationsDeployment.PreimageOracleSingletonAddress - padded := common.LeftPadBytes(addr.Bytes(), 32) - return hexutil.Encode(padded)[2:], nil -} - -func encodeOpcmArgs(v *Verifier) (string, error) { - type Blueprints struct { - AddressManager common.Address `abi:"field0"` - Proxy common.Address `abi:"field1"` - ProxyAdmin common.Address `abi:"field2"` - L1ChugSplashProxy common.Address `abi:"field3"` - ResolvedDelegateProxy common.Address `abi:"field4"` - PermissionedDisputeGame1 common.Address `abi:"field5"` - PermissionedDisputeGame2 common.Address `abi:"field6"` - PermissionlessDisputeGame1 common.Address `abi:"field7"` - PermissionlessDisputeGame2 common.Address `abi:"field8"` - } - - type Implementations struct { - SuperchainConfigImpl common.Address `abi:"field0"` - ProtocolVersionsImpl common.Address `abi:"field1"` - L1ERC721BridgeImpl common.Address `abi:"field2"` - OptimismPortalImpl common.Address `abi:"field3"` - SystemConfigImpl common.Address `abi:"field4"` - OptimismMintableERC20FactoryImpl common.Address `abi:"field5"` - L1CrossDomainMessengerImpl common.Address `abi:"field6"` - L1StandardBridgeImpl common.Address `abi:"field7"` - DisputeGameFactoryImpl common.Address `abi:"field8"` - AnchorStateRegistryImpl common.Address `abi:"field9"` - DelayedWETHImpl common.Address `abi:"field10"` - MipsImpl common.Address `abi:"field11"` - } - - var blueprints Blueprints - blueprintsFn := w3.MustNewFunc("blueprints()", "(address addressManager,address proxy,address proxyAdmin,address l1ChugSplashProxy,address resolvedDelegateProxy,address permissionedDisputeGame1,address permissionedDisputeGame2,address permissionlessDisputeGame1,address permissionlessDisputeGame2)") - if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, blueprintsFn).Returns(&blueprints)); err != nil { - return "", err - } - - var impls Implementations - implementationsFn := w3.MustNewFunc("implementations()", "(address superchainConfigImpl,address protocolVersionsImpl,address l1ERC721BridgeImpl,address optimismPortalImpl,address systemConfigImpl,address optimismMintableERC20FactoryImpl,address l1CrossDomainMessengerImpl,address l1StandardBridgeImpl,address disputeGameFactoryImpl,address anchorStateRegistryImpl,address delayedWETHImpl,address mipsImpl)") - if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, implementationsFn).Returns(&impls)); err != nil { - return "", err - } - - var release string - releaseFn := w3.MustNewFunc("l1ContractsRelease()", "string") - if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, releaseFn).Returns(&release)); err != nil { - return "", err - } - - var isRc bool - isRcFn := w3.MustNewFunc("isRC()", "bool") - if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, isRcFn).Returns(&isRc)); err != nil { - return "", err - } - if isRc { - // Opcm code appends the "-rc" suffix, so we need to remove it to recreate the constructor arg - release = strings.TrimSuffix(release, "-rc") - } - var upgradeController common.Address - upgradeControllerFn := w3.MustNewFunc("upgradeController()", "address") - if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, upgradeControllerFn).Returns(&upgradeController)); err != nil { - return "", err - } - - var superchainConfig common.Address - superchainConfigFn := w3.MustNewFunc("superchainConfig()", "address") - if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, superchainConfigFn).Returns(&superchainConfig)); err != nil { - return "", err - } - - var protocolVersions common.Address - protocolVersionsFn := w3.MustNewFunc("protocolVersions()", "address") - if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, protocolVersionsFn).Returns(&protocolVersions)); err != nil { - return "", err - } - - var superchainProxyAdmin common.Address - superchainProxyAdminFn := w3.MustNewFunc("superchainProxyAdmin()", "address") - if err := v.w3Client.Call(eth.CallFunc(v.st.ImplementationsDeployment.OpcmAddress, superchainProxyAdminFn).Returns(&superchainProxyAdmin)); err != nil { - return "", err - } - - result := []byte{} - result = append(result, common.LeftPadBytes(superchainConfig.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(protocolVersions.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(superchainProxyAdmin.Bytes(), 32)...) - - // Calculate dynamic offset for _l1ContractsRelease. - // 3 addresses - // 1 dynamic offset for _l1ContractsRelease, - // 9 addresses for blueprints - // 12 addresses for implementations - // 1 address for _upgradeController. - // -------------------------------- - // Total: 26 slots - // Offset = 26 * 32 = 832 bytes. - offset := big.NewInt(26 * 32) // 832 - result = append(result, common.LeftPadBytes(offset.Bytes(), 32)...) - - // blueprints - result = append(result, common.LeftPadBytes(blueprints.AddressManager.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(blueprints.Proxy.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(blueprints.ProxyAdmin.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(blueprints.L1ChugSplashProxy.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(blueprints.ResolvedDelegateProxy.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(blueprints.PermissionedDisputeGame1.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(blueprints.PermissionedDisputeGame2.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(blueprints.PermissionlessDisputeGame1.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(blueprints.PermissionlessDisputeGame2.Bytes(), 32)...) - - // implementations - result = append(result, common.LeftPadBytes(impls.SuperchainConfigImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.ProtocolVersionsImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.L1ERC721BridgeImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.OptimismPortalImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.SystemConfigImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.OptimismMintableERC20FactoryImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.L1CrossDomainMessengerImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.L1StandardBridgeImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.DisputeGameFactoryImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.AnchorStateRegistryImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.DelayedWETHImpl.Bytes(), 32)...) - result = append(result, common.LeftPadBytes(impls.MipsImpl.Bytes(), 32)...) - - // upgrade controller - result = append(result, common.LeftPadBytes(upgradeController.Bytes(), 32)...) - - // l1ContractsRelease (dynamic args appended to the end) - releaseBytes := []byte(release) - result = append(result, common.LeftPadBytes(big.NewInt(int64(len(releaseBytes))).Bytes(), 32)...) - result = append(result, common.RightPadBytes(releaseBytes, (len(releaseBytes)+31)/32*32)...) - - return strings.TrimPrefix(hexutil.Encode(result), "0x"), nil -} - -func encodeSuperchainConfigProxyArgs(v *Verifier) (string, error) { - addr := v.st.SuperchainDeployment.ProxyAdminAddress - padded := common.LeftPadBytes(addr.Bytes(), 32) - return strings.TrimPrefix(hexutil.Encode(padded), "0x"), nil -} - -func encodePermissionedDisputeGameArgs(v *Verifier) (string, error) { - chainState, err := v.st.Chain(v.l2ChainID) + v.log.Info("Extracting constructor arguments", "address", address.Hex()) + txHash, err := v.etherscan.getContractCreation(address) if err != nil { - return "", err + return "", fmt.Errorf("failed to get contract creation tx: %w", err) } - addr := chainState.PermissionedDisputeGameAddress - result := []byte{} - var gameType uint32 - gameTypeFn := w3.MustNewFunc("gameType()", "uint32") - if err := v.w3Client.Call(eth.CallFunc(addr, gameTypeFn).Returns(&gameType)); err != nil { - return "", err - } - result = append(result, common.LeftPadBytes(big.NewInt(int64(gameType)).Bytes(), 32)...) - - var absolutePrestate [32]byte - absolutePrestateFn := w3.MustNewFunc("absolutePrestate()", "bytes32") - if err := v.w3Client.Call(eth.CallFunc(addr, absolutePrestateFn).Returns(&absolutePrestate)); err != nil { - return "", err - } - result = append(result, absolutePrestate[:]...) - - var maxGameDepth big.Int - maxGameDepthFn := w3.MustNewFunc("maxGameDepth()", "uint256") - if err := v.w3Client.Call(eth.CallFunc(addr, maxGameDepthFn).Returns(&maxGameDepth)); err != nil { - return "", err - } - result = append(result, common.LeftPadBytes(maxGameDepth.Bytes(), 32)...) - - var splitDepth big.Int - splitDepthFn := w3.MustNewFunc("splitDepth()", "uint256") - if err := v.w3Client.Call(eth.CallFunc(addr, splitDepthFn).Returns(&splitDepth)); err != nil { - return "", err - } - result = append(result, common.LeftPadBytes(splitDepth.Bytes(), 32)...) - - var clockExtension uint64 - clockExtensionFn := w3.MustNewFunc("clockExtension()", "uint64") - if err := v.w3Client.Call(eth.CallFunc(addr, clockExtensionFn).Returns(&clockExtension)); err != nil { - return "", err - } - result = append(result, common.LeftPadBytes(big.NewInt(int64(clockExtension)).Bytes(), 32)...) + v.log.Info("Contract creation tx hash", "txHash", txHash.Hex()) - var maxClockDuration uint64 - maxClockDurationFn := w3.MustNewFunc("maxClockDuration()", "uint64") - if err := v.w3Client.Call(eth.CallFunc(addr, maxClockDurationFn).Returns(&maxClockDuration)); err != nil { - return "", err - } - result = append(result, common.LeftPadBytes(big.NewInt(int64(maxClockDuration)).Bytes(), 32)...) - var vm common.Address - vmFn := w3.MustNewFunc("vm()", "address") - if err := v.w3Client.Call(eth.CallFunc(addr, vmFn).Returns(&vm)); err != nil { - return "", err - } - result = append(result, common.LeftPadBytes(vm.Bytes(), 32)...) - - var weth common.Address - wethFn := w3.MustNewFunc("weth()", "address") - if err := v.w3Client.Call(eth.CallFunc(addr, wethFn).Returns(&weth)); err != nil { - return "", err - } - result = append(result, common.LeftPadBytes(weth.Bytes(), 32)...) - - var anchorStateRegistry common.Address - anchorStateRegistryFn := w3.MustNewFunc("anchorStateRegistry()", "address") - if err := v.w3Client.Call(eth.CallFunc(addr, anchorStateRegistryFn).Returns(&anchorStateRegistry)); err != nil { - return "", err - } - result = append(result, common.LeftPadBytes(anchorStateRegistry.Bytes(), 32)...) - - var l2ChainId big.Int - l2ChainIdFn := w3.MustNewFunc("l2ChainId()", "uint256") - if err := v.w3Client.Call(eth.CallFunc(addr, l2ChainIdFn).Returns(&l2ChainId)); err != nil { - return "", err + tx, isPending, err := v.l1Client.TransactionByHash(ctx, txHash) + if err != nil { + return "", fmt.Errorf("failed to get transaction: %w", err) } - result = append(result, common.LeftPadBytes(l2ChainId.Bytes(), 32)...) - chainIntent, err := v.st.AppliedIntent.Chain(v.l2ChainID) - if err != nil { - return "", err + if isPending { + return "", fmt.Errorf("transaction is still pending") } - proposer := chainIntent.Roles.Proposer - result = append(result, common.LeftPadBytes(proposer.Bytes(), 32)...) - challenger := chainIntent.Roles.Challenger - result = append(result, common.LeftPadBytes(challenger.Bytes(), 32)...) + // tx.Data contains bytecode + constructor args, so we strip the + // constructor args off of the end + txInput := hex.EncodeToString(tx.Data()) + constructorArgs := txInput[len(txInput)-(argSlots*64):] + v.log.Info("Successfully extracted constructor arguments", "address", address.Hex()) - return strings.TrimPrefix(hexutil.Encode(result), "0x"), nil + return constructorArgs, nil } diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go index 9f1a5e5b31c20..054ca7db74781 100644 --- a/op-deployer/pkg/deployer/verify/etherscan.go +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -10,14 +10,30 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "golang.org/x/time/rate" ) -type EtherscanResponse struct { +type EtherscanGenericResp struct { Status string `json:"status"` Message string `json:"message"` Result string `json:"result"` } +type EtherscanContractCreationResp struct { + Status string `json:"status"` + Message string `json:"message"` + Result []struct { + ContractCreator string `json:"contractCreator"` + TxHash string `json:"txHash"` + } `json:"result"` +} + +type EtherscanClient struct { + apiKey string + url string + rateLimiter *rate.Limiter +} + func getAPIEndpoint(chainID uint64) string { switch chainID { case 1: @@ -29,122 +45,130 @@ func getAPIEndpoint(chainID uint64) string { } } -func (v *Verifier) verifyContract(address common.Address, contractName string) error { - verified, err := v.isVerified(address) - if err != nil { - return fmt.Errorf("failed to check verification status: %w", err) +func NewEtherscanClient(apiKey string, url string, rateLimiter *rate.Limiter) *EtherscanClient { + return &EtherscanClient{ + apiKey: apiKey, + url: url, + rateLimiter: rateLimiter, } - if verified { - v.log.Info("Contract is already verified", "name", contractName, "address", address.Hex()) - v.numSkipped++ - return nil +} + +// sendRateLimitedRequest is a helper function which waits for a rate limit token +// before sending a request +func (c *EtherscanClient) sendRateLimitedRequest(req *http.Request) (*http.Response, error) { + if err := c.rateLimiter.Wait(context.Background()); err != nil { + return nil, fmt.Errorf("rate limiter error: %w", err) + } + return http.DefaultClient.Do(req) +} + +// getContractCreation returns the txHash of the contract creation tx +// (useful for extracting constructor args) +func (c *EtherscanClient) getContractCreation(address common.Address) (common.Hash, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s?module=contract&action=getcontractcreation&contractaddresses=%s&apikey=%s", + c.url, address.Hex(), c.apiKey), nil) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to create contract creation request: %w", err) } - v.log.Info("Formatting etherscan verification request", "name", contractName, "address", address.Hex()) - source, err := v.getContractArtifact(contractName) + resp, err := c.sendRateLimitedRequest(req) if err != nil { - return fmt.Errorf("failed to get contract source: %w", err) + return common.Hash{}, fmt.Errorf("failed to send contract creation request: %w", err) + } + defer resp.Body.Close() + + var creationResp EtherscanContractCreationResp + if err := json.NewDecoder(resp.Body).Decode(&creationResp); err != nil { + return common.Hash{}, fmt.Errorf("failed to decode contract creation response: %w", err) } + txHash := common.HexToHash(creationResp.Result[0].TxHash) + return txHash, nil +} + +func (c *EtherscanClient) verifySourceCode(address common.Address, artifact *contractArtifact, constructorArgs string) (string, error) { optimized := "0" - if source.Optimizer.Enabled { + if artifact.Optimizer.Enabled { optimized = "1" } data := url.Values{ - "apikey": {v.apiKey}, + "apikey": {c.apiKey}, "module": {"contract"}, "action": {"verifysourcecode"}, "contractaddress": {address.Hex()}, "codeformat": {"solidity-standard-json-input"}, - "sourceCode": {source.StandardInput}, - "contractname": {source.ContractName}, - "compilerversion": {fmt.Sprintf("v%s", source.CompilerVersion)}, + "sourceCode": {artifact.StandardInput}, + "contractname": {artifact.ContractName}, + "compilerversion": {fmt.Sprintf("v%s", artifact.CompilerVersion)}, "optimizationUsed": {optimized}, - "runs": {fmt.Sprintf("%d", source.Optimizer.Runs)}, - "evmversion": {source.EVMVersion}, - "constructorArguements": {source.ConstructorArgs}, + "runs": {fmt.Sprintf("%d", artifact.Optimizer.Runs)}, + "evmversion": {artifact.EVMVersion}, + "constructorArguements": {constructorArgs}, } - req, err := http.NewRequest("POST", v.etherscanUrl, strings.NewReader(data.Encode())) + req, err := http.NewRequest("POST", c.url, strings.NewReader(data.Encode())) if err != nil { - return fmt.Errorf("failed to create verification request: %w", err) + return "", fmt.Errorf("failed to create verification request: %w", err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, err := v.sendRateLimitedRequest(req) + resp, err := c.sendRateLimitedRequest(req) if err != nil { - return fmt.Errorf("failed to submit verification request: %w", err) + return "", fmt.Errorf("failed to submit verification request: %w", err) } defer resp.Body.Close() - var result EtherscanResponse + var result EtherscanGenericResp if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return fmt.Errorf("failed to decode response: %w", err) + return "", fmt.Errorf("failed to decode response: %w", err) } if result.Status != "1" { - return fmt.Errorf("verification request failed: status=%s message=%s result=%s", + return "", fmt.Errorf("verification request failed: status=%s message=%s result=%s", result.Status, result.Message, result.Result) } - v.log.Info("Verification request submitted", "name", contractName, "address", address.Hex()) - err = v.checkVerificationStatus(result.Result) - if err == nil { - v.log.Info("Verification complete", "name", contractName, "address", address.Hex()) - v.numVerified++ - } - return err -} -// sendRateLimitedRequest is a helper function which waits for a rate limit token -// before sending a request -func (v *Verifier) sendRateLimitedRequest(req *http.Request) (*http.Response, error) { - if err := v.rateLimiter.Wait(context.Background()); err != nil { - return nil, fmt.Errorf("rate limiter error: %w", err) - } - - return http.DefaultClient.Do(req) + return result.Result, nil } -func (v *Verifier) isVerified(address common.Address) (bool, error) { +func (c *EtherscanClient) isVerified(address common.Address) (bool, error) { req, err := http.NewRequest("GET", fmt.Sprintf("%s?module=contract&action=getabi&address=%s&apikey=%s", - v.etherscanUrl, address.Hex(), v.apiKey), nil) + c.url, address.Hex(), c.apiKey), nil) if err != nil { return false, err } - resp, err := v.sendRateLimitedRequest(req) + resp, err := c.sendRateLimitedRequest(req) if err != nil { return false, err } defer resp.Body.Close() - var result EtherscanResponse + var result EtherscanGenericResp if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return false, err } - v.log.Debug("Contract verification status", "status", result.Status, "message", result.Message) return result.Status == "1", nil } -func (v *Verifier) checkVerificationStatus(reqId string) error { +func (c *EtherscanClient) pollVerificationStatus(reqId string) error { req, err := http.NewRequest("GET", fmt.Sprintf("%s?apikey=%s&module=contract&action=checkverifystatus&guid=%s", - v.etherscanUrl, v.apiKey, reqId), nil) + c.url, c.apiKey, reqId), nil) if err != nil { return fmt.Errorf("failed to create checkverifystatus request: %w", err) } for i := 0; i < 10; i++ { // Try 10 times with increasing delays - v.log.Info("Checking verification status", "guid", reqId) time.Sleep(time.Duration(i+2) * time.Second) - resp, err := v.sendRateLimitedRequest(req) + resp, err := c.sendRateLimitedRequest(req) if err != nil { return fmt.Errorf("failed to send checkverifystatus request: %w", err) } defer resp.Body.Close() - var result EtherscanResponse + var result EtherscanGenericResp if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return fmt.Errorf("failed to decode checkverifystatus response: %w", err) } @@ -153,7 +177,6 @@ func (v *Verifier) checkVerificationStatus(reqId string) error { return nil } if result.Result == "Already Verified" { - v.log.Info("Contract is already verified") return nil } if result.Result != "Pending in queue" { diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index 99d0aad648aae..adc62b3e486f7 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -6,9 +6,8 @@ import ( "reflect" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" - "github.com/lmittmann/w3" - "github.com/lmittmann/w3/module/eth" "github.com/urfave/cli/v2" "golang.org/x/time/rate" @@ -24,20 +23,19 @@ import ( ) type Verifier struct { - apiKey string - l1ChainID uint64 - l2ChainID common.Hash - st *state.State - artifactsFS foundry.StatDirFs - log log.Logger - etherscanUrl string - rateLimiter *rate.Limiter - w3Client *w3.Client - numVerified int - numSkipped int + l1ChainID uint64 + l2ChainID common.Hash + st *state.State + artifactsFS foundry.StatDirFs + log log.Logger + etherscan *EtherscanClient + l1Client *ethclient.Client + numVerified int + numSkipped int + numFailed int } -func NewVerifier(apiKey string, l1ChainID uint64, l2ChainID common.Hash, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger, w3Client *w3.Client) (*Verifier, error) { +func NewVerifier(apiKey string, l1ChainID uint64, l2ChainID common.Hash, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger, l1Client *ethclient.Client) (*Verifier, error) { etherscanUrl := getAPIEndpoint(l1ChainID) if etherscanUrl == "" { return nil, fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) @@ -47,16 +45,16 @@ func NewVerifier(apiKey string, l1ChainID uint64, l2ChainID common.Hash, st *sta l2ChainID = st.AppliedIntent.Chains[0].ID } + etherscan := NewEtherscanClient(apiKey, etherscanUrl, rate.NewLimiter(rate.Limit(3), 2)) + return &Verifier{ - apiKey: apiKey, - l1ChainID: l1ChainID, - l2ChainID: l2ChainID, - st: st, - artifactsFS: artifactsFS, - log: l, - etherscanUrl: etherscanUrl, - rateLimiter: rate.NewLimiter(rate.Limit(3), 2), - w3Client: w3Client, + l1ChainID: l1ChainID, + l2ChainID: l2ChainID, + st: st, + artifactsFS: artifactsFS, + log: l, + l1Client: l1Client, + etherscan: etherscan, }, nil } @@ -83,16 +81,17 @@ func VerifyCLI(cliCtx *cli.Context) error { ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) - w3Client, err := w3.Dial(l1RPCUrl) + l1Client, err := ethclient.Dial(l1RPCUrl) if err != nil { return fmt.Errorf("failed to connect to L1: %w", err) } - defer w3Client.Close() + defer l1Client.Close() - var l1ChainId uint64 - if err := w3Client.Call(eth.ChainID().Returns(&l1ChainId)); err != nil { + chainId, err := l1Client.ChainID(ctx) + if err != nil { return fmt.Errorf("failed to get chain ID: %w", err) } + l1ChainId := chainId.Uint64() st, err := pipeline.ReadState(workdir) if err != nil { @@ -109,13 +108,13 @@ func VerifyCLI(cliCtx *cli.Context) error { } l.Info("Downloaded artifacts", "path", artifactsFS) - v, err := NewVerifier(etherscanAPIKey, l1ChainId, l2ChainID, st, artifactsFS, l, w3Client) + v, err := NewVerifier(etherscanAPIKey, l1ChainId, l2ChainID, st, artifactsFS, l, l1Client) if err != nil { return fmt.Errorf("failed to create verifier: %w", err) } defer func() { - v.log.Info("final results", "numVerified", v.numVerified, "numSkipped", v.numSkipped) + v.log.Info("final results", "numVerified", v.numVerified, "numSkipped", v.numSkipped, "numFailed", v.numFailed) }() if bundleName == "" && contractName == "" { @@ -176,8 +175,8 @@ func (v *Verifier) verifyContractBundle(bundleName string) error { addr := field.Interface().(common.Address) if addr != (common.Address{}) { // Skip zero addresses name := typ.Field(i).Name - if err := v.verifyContract(addr, name); err != nil { - return fmt.Errorf("failed to verify %s: %w", name, err) + if err := v.verifySingleContract(context.Background(), name, bundleName); err != nil { + v.log.Error("failed to verify contract", "name", name, "bundle", bundleName, "error", err) } } } @@ -197,5 +196,47 @@ func (v *Verifier) verifySingleContract(ctx context.Context, contractName string return fmt.Errorf("failed to find address for contract %s: %w", contractName, err) } - return v.verifyContract(addr, contractName) + if err := v.verifyContract(addr, contractName); err != nil { + v.numFailed++ + return err + } + + return nil +} + +func (v *Verifier) verifyContract(address common.Address, contractName string) error { + verified, err := v.etherscan.isVerified(address) + if err != nil { + return fmt.Errorf("failed to check verification status: %w", err) + } + if verified { + v.log.Info("Contract is already verified", "name", contractName, "address", address.Hex()) + v.numSkipped++ + return nil + } + + v.log.Info("Formatting etherscan verification request", "name", contractName, "address", address.Hex()) + artifact, err := v.getContractArtifact(contractName) + if err != nil { + return fmt.Errorf("failed to get contract source: %w", err) + } + + constructorArgs, err := v.getConstructorArgs(context.Background(), address, contractName) + if err != nil { + return fmt.Errorf("failed to get constructor args: %w", err) + } + + reqId, err := v.etherscan.verifySourceCode(address, artifact, constructorArgs) + if err != nil { + return fmt.Errorf("failed to verify contract: %w", err) + } + + v.log.Info("Verification request submitted", "name", contractName, "address", address.Hex()) + if err = v.etherscan.pollVerificationStatus(reqId); err != nil { + return fmt.Errorf("failed when checking verification status: %w", err) + } + + v.log.Info("Verification complete", "name", contractName, "address", address.Hex()) + v.numVerified++ + return nil } From 554ba008d7c074b24888188dccc00a4d91a4b7c8 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Wed, 5 Mar 2025 12:04:47 -0500 Subject: [PATCH 14/26] return error if contract creation query fails --- op-deployer/pkg/deployer/verify/etherscan.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go index 054ca7db74781..24deda910f5c7 100644 --- a/op-deployer/pkg/deployer/verify/etherscan.go +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -34,8 +34,8 @@ type EtherscanClient struct { rateLimiter *rate.Limiter } -func getAPIEndpoint(chainID uint64) string { - switch chainID { +func getAPIEndpoint(l1ChainID uint64) string { + switch l1ChainID { case 1: return "https://api.etherscan.io/api" // mainnet case 11155111: @@ -81,6 +81,9 @@ func (c *EtherscanClient) getContractCreation(address common.Address) (common.Ha if err := json.NewDecoder(resp.Body).Decode(&creationResp); err != nil { return common.Hash{}, fmt.Errorf("failed to decode contract creation response: %w", err) } + if creationResp.Status != "1" { + return common.Hash{}, fmt.Errorf("contract creation query failed: %s", creationResp.Message) + } txHash := common.HexToHash(creationResp.Result[0].TxHash) return txHash, nil From 63263bd00c3614be473f51f944c13679754feeb1 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Wed, 5 Mar 2025 12:06:16 -0500 Subject: [PATCH 15/26] use more informative contract-name flag description --- op-deployer/pkg/deployer/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 48e19e16cc5e5..1008db61af944 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -140,7 +140,7 @@ var ( } ContractNameFlag = &cli.StringFlag{ Name: ContractNameFlagName, - Usage: "contract name (matching a field within state.json)", + Usage: "contract name (matching a field within a contract bundle struct)", EnvVars: PrefixEnvVar("CONTRACT_NAME"), } ) From 0dfc23cc896f9c8b9a3505251a91c6c26d475898 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Wed, 5 Mar 2025 12:46:33 -0500 Subject: [PATCH 16/26] calc num constructorArgSlots by parsing abi --- op-deployer/pkg/deployer/verify/artifacts.go | 13 +- .../pkg/deployer/verify/constructors.go | 44 +++-- .../pkg/deployer/verify/constructors_test.go | 171 ++++++++++++++++++ op-deployer/pkg/deployer/verify/etherscan.go | 22 ++- op-deployer/pkg/deployer/verify/verifier.go | 148 +++++++++------ 5 files changed, 302 insertions(+), 96 deletions(-) create mode 100644 op-deployer/pkg/deployer/verify/constructors_test.go diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go index 0a97a1fddeda4..a873924958cbd 100644 --- a/op-deployer/pkg/deployer/verify/artifacts.go +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum/go-ethereum/accounts/abi" ) type contractArtifact struct { @@ -14,7 +15,8 @@ type contractArtifact struct { CompilerVersion string Optimizer OptimizerSettings EVMVersion string - StandardInput string + Sources map[string]SourceContent + ConstructorArgs abi.Arguments } // Map state.json struct fields to forge artifact names @@ -73,12 +75,6 @@ func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { return nil, fmt.Errorf("failed to parse optimizer settings: %w", err) } - standardInput := newStandardInput(sources, optimizer, art.Metadata.Settings.EVMVersion) - standardInputJSON, err := json.Marshal(standardInput) - if err != nil { - return nil, fmt.Errorf("failed to generate standard input: %w", err) - } - // Get the contract name from the compilation target var contractName string for contractFile, name := range art.Metadata.Settings.CompilationTarget { @@ -92,6 +88,7 @@ func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { CompilerVersion: art.Metadata.Compiler.Version, Optimizer: optimizer, EVMVersion: art.Metadata.Settings.EVMVersion, - StandardInput: string(standardInputJSON), + Sources: sources, + ConstructorArgs: art.ABI.Constructor.Inputs, }, nil } diff --git a/op-deployer/pkg/deployer/verify/constructors.go b/op-deployer/pkg/deployer/verify/constructors.go index 805abc997bb9e..e355adea4df69 100644 --- a/op-deployer/pkg/deployer/verify/constructors.go +++ b/op-deployer/pkg/deployer/verify/constructors.go @@ -4,34 +4,26 @@ import ( "context" "encoding/hex" "fmt" + "strings" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" ) -var constructorArgSlots = map[string]int{ - "ProxyAdminAddress": 1, - "OpcmAddress": 28, - "DelayedWETHImplAddress": 1, - "OptimismPortalImplAddress": 2, - "PreimageOracleSingletonAddress": 2, - "MipsSingletonAddress": 1, - "SuperchainConfigProxyAddress": 1, - "PermissionedDisputeGameAddress": 12, - "FaultDisputeGameAddress": 10, -} - -func (v *Verifier) getConstructorArgs(ctx context.Context, address common.Address, contractName string) (string, error) { - argSlots, ok := constructorArgSlots[contractName] - if !ok { +func (v *Verifier) getConstructorArgs(ctx context.Context, address common.Address, artifact *contractArtifact) (string, error) { + argSlots := 0 + for _, arg := range artifact.ConstructorArgs { + argSlots += calculateTypeSlots(arg.Type) + } + if argSlots == 0 { return "", nil } - v.log.Info("Extracting constructor arguments", "address", address.Hex()) + v.log.Info("Extracting constructor args from initcode", "address", address.Hex(), "argSlots", argSlots) txHash, err := v.etherscan.getContractCreation(address) if err != nil { return "", fmt.Errorf("failed to get contract creation tx: %w", err) } - v.log.Info("Contract creation tx hash", "txHash", txHash.Hex()) tx, isPending, err := v.l1Client.TransactionByHash(ctx, txHash) @@ -47,7 +39,23 @@ func (v *Verifier) getConstructorArgs(ctx context.Context, address common.Addres // constructor args off of the end txInput := hex.EncodeToString(tx.Data()) constructorArgs := txInput[len(txInput)-(argSlots*64):] - v.log.Info("Successfully extracted constructor arguments", "address", address.Hex()) + v.log.Info("Successfully extracted constructor args", "address", address.Hex()) return constructorArgs, nil } + +// Helper function to calculate slots needed for a abi.Type, handling nested tuples +func calculateTypeSlots(t abi.Type) int { + if t.String() == "string" { + return 3 // 1 slot each for: offset, length, value (assuming value only takes 1 slot) + } else if strings.HasPrefix(t.String(), "(") { + // loop through nested tuple elements + totalSlots := 0 + for _, elem := range t.TupleElems { + totalSlots += calculateTypeSlots(*elem) + } + return totalSlots + } else { + return 1 + } +} diff --git a/op-deployer/pkg/deployer/verify/constructors_test.go b/op-deployer/pkg/deployer/verify/constructors_test.go new file mode 100644 index 0000000000000..20a3dc0c2e2fd --- /dev/null +++ b/op-deployer/pkg/deployer/verify/constructors_test.go @@ -0,0 +1,171 @@ +package verify + +import ( + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/stretchr/testify/require" +) + +func TestCalculateTypeSlots(t *testing.T) { + t.Run("nested tuple", func(t *testing.T) { + constructorArgsJSON := `[ + { + "name": "_superchainConfig", + "type": "address", + "internalType": "contract ISuperchainConfig" + }, + { + "name": "_protocolVersions", + "type": "address", + "internalType": "contract IProtocolVersions" + }, + { + "name": "_superchainProxyAdmin", + "type": "address", + "internalType": "contract IProxyAdmin" + }, + { + "name": "_l1ContractsRelease", + "type": "string", + "internalType": "string" + }, + { + "name": "_blueprints", + "type": "tuple", + "internalType": "struct OPContractsManager.Blueprints", + "components": [ + { + "name": "addressManager", + "type": "address", + "internalType": "address" + }, + { + "name": "proxy", + "type": "address", + "internalType": "address" + }, + { + "name": "proxyAdmin", + "type": "address", + "internalType": "address" + }, + { + "name": "l1ChugSplashProxy", + "type": "address", + "internalType": "address" + }, + { + "name": "resolvedDelegateProxy", + "type": "address", + "internalType": "address" + }, + { + "name": "permissionedDisputeGame1", + "type": "address", + "internalType": "address" + }, + { + "name": "permissionedDisputeGame2", + "type": "address", + "internalType": "address" + }, + { + "name": "permissionlessDisputeGame1", + "type": "address", + "internalType": "address" + }, + { + "name": "permissionlessDisputeGame2", + "type": "address", + "internalType": "address" + } + ] + }, + { + "name": "_implementations", + "type": "tuple", + "internalType": "struct OPContractsManager.Implementations", + "components": [ + { + "name": "superchainConfigImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "protocolVersionsImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "l1ERC721BridgeImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "optimismPortalImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "systemConfigImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "optimismMintableERC20FactoryImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "l1CrossDomainMessengerImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "l1StandardBridgeImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "disputeGameFactoryImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "anchorStateRegistryImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "delayedWETHImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "mipsImpl", + "type": "address", + "internalType": "address" + } + ] + }, + { + "name": "_upgradeController", + "type": "address", + "internalType": "address" + } + ]` + + var args abi.Arguments + err := json.Unmarshal([]byte(constructorArgsJSON), &args) + require.NoError(t, err) + + totalSlots := 0 + for _, arg := range args { + totalSlots += calculateTypeSlots(arg.Type) + } + + require.Equal(t, 28, totalSlots) + }) +} diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go index 24deda910f5c7..2fce3f778f7d7 100644 --- a/op-deployer/pkg/deployer/verify/etherscan.go +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -95,13 +95,19 @@ func (c *EtherscanClient) verifySourceCode(address common.Address, artifact *con optimized = "1" } + standardInput := newStandardInput(artifact) + standardInputJSON, err := json.Marshal(standardInput) + if err != nil { + return "", fmt.Errorf("failed to generate standard input: %w", err) + } + data := url.Values{ "apikey": {c.apiKey}, "module": {"contract"}, "action": {"verifysourcecode"}, "contractaddress": {address.Hex()}, "codeformat": {"solidity-standard-json-input"}, - "sourceCode": {artifact.StandardInput}, + "sourceCode": {string(standardInputJSON)}, "contractname": {artifact.ContractName}, "compilerversion": {fmt.Sprintf("v%s", artifact.CompilerVersion)}, "optimizationUsed": {optimized}, @@ -224,20 +230,16 @@ type OutputSelectionDetails struct { All []string `json:"*"` } -func newStandardInput( - sources map[string]SourceContent, - optimizer OptimizerSettings, - evmVersion string, -) StandardInput { +func newStandardInput(artifact *contractArtifact) StandardInput { return StandardInput{ Language: "Solidity", - Sources: sources, + Sources: artifact.Sources, Settings: Settings{ Optimizer: OptimizerSettings{ - Enabled: optimizer.Enabled, - Runs: optimizer.Runs, + Enabled: artifact.Optimizer.Enabled, + Runs: artifact.Optimizer.Runs, }, - EVMVersion: evmVersion, + EVMVersion: artifact.EVMVersion, Metadata: MetadataSettings{ UseLiteralContent: true, BytecodeHash: "none", diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index adc62b3e486f7..476bfb08765e0 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -2,7 +2,10 @@ package verify import ( "context" + "encoding/json" "fmt" + "os" + "path/filepath" "reflect" "github.com/ethereum/go-ethereum/common" @@ -66,8 +69,11 @@ func VerifyCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) workdir := cliCtx.String(deployer.WorkdirFlagName) etherscanAPIKey := cliCtx.String(deployer.EtherscanAPIKeyFlagName) + if etherscanAPIKey == "" { + return fmt.Errorf("etherscan API key is required") + } + bundleName := cliCtx.String(deployer.ContractBundleFlagName) - contractName := cliCtx.String(deployer.ContractNameFlagName) l2ChainIDRaw := cliCtx.String(deployer.L2ChainIDFlagName) var l2ChainID common.Hash @@ -117,53 +123,94 @@ func VerifyCLI(cliCtx *cli.Context) error { v.log.Info("final results", "numVerified", v.numVerified, "numSkipped", v.numSkipped, "numFailed", v.numFailed) }() - if bundleName == "" && contractName == "" { - if err := v.verifyAll(ctx); err != nil { - return err - } - } else if bundleName != "" && contractName == "" { - if err := v.verifyContractBundle(bundleName); err != nil { - return err - } - } else if bundleName != "" && contractName != "" { - if err := v.verifySingleContract(ctx, contractName, bundleName); err != nil { + if bundleName == "" { + if err := v.verifyAll(ctx, workdir); err != nil { return err } - } else { - // If a contract name is provided without a contract bundle, report an error. - return fmt.Errorf("contract-name flag provided without contract-bundle flag") + } else if err := v.verifyContractBundle(ctx, workdir, bundleName); err != nil { + return err } - v.log.Info("--- SUCCESS ---") + v.log.Info("--- COMPLETE ---") return nil } -func (v *Verifier) verifyAll(ctx context.Context) error { +func (v *Verifier) verifyAll(ctx context.Context, workdir string) error { for _, bundleName := range inspect.ContractBundles { - if err := v.verifyContractBundle(bundleName); err != nil { + if err := v.verifyContractBundle(ctx, workdir, bundleName); err != nil { return fmt.Errorf("failed to verify bundle %s: %w", bundleName, err) } } return nil } -func (v *Verifier) verifyContractBundle(bundleName string) error { - // Retrieve the L1 contracts from state. - l1Contracts, err := inspect.L1(v.st, v.l2ChainID) - if err != nil { - return fmt.Errorf("failed to extract L1 contracts from state: %w", err) - } +func (v *Verifier) getContractBundle(workdir string, bundleName string) (interface{}, error) { + bundleFilePath := filepath.Join(workdir, fmt.Sprintf("bootstrap_%s.json", bundleName)) - // Select the appropriate bundle based on the input bundleName. var bundle interface{} - switch bundleName { - case inspect.SuperchainBundle: - bundle = l1Contracts.SuperchainDeployment - case inspect.ImplementationsBundle: - bundle = l1Contracts.ImplementationsDeployment - case inspect.OpChainBundle: - bundle = l1Contracts.OpChainDeployment - default: - return fmt.Errorf("invalid contract bundle: %s", bundleName) + + // Check if the bundle file exists + if _, err := os.Stat(bundleFilePath); err == nil { + // File exists, read and parse it + v.log.Info("Found bundle file", "path", bundleFilePath) + bundleData, err := os.ReadFile(bundleFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read bundle file %s: %w", bundleFilePath, err) + } + + // Parse the file based on bundle type + switch bundleName { + case inspect.SuperchainBundle: + var superchainBundle inspect.SuperchainDeployment + if err := json.Unmarshal(bundleData, &superchainBundle); err != nil { + return nil, fmt.Errorf("failed to parse superchain bundle: %w", err) + } + bundle = superchainBundle + + case inspect.ImplementationsBundle: + var implBundle inspect.ImplementationsDeployment + if err := json.Unmarshal(bundleData, &implBundle); err != nil { + return nil, fmt.Errorf("failed to parse implementations bundle: %w", err) + } + bundle = implBundle + + case inspect.OpChainBundle: + var opChainBundle inspect.OpChainDeployment + if err := json.Unmarshal(bundleData, &opChainBundle); err != nil { + return nil, fmt.Errorf("failed to parse opchain bundle: %w", err) + } + bundle = opChainBundle + + default: + return nil, fmt.Errorf("invalid contract bundle: %s", bundleName) + } + v.log.Info("Using bundle file", "path", bundleFilePath) + } else { + v.log.Info("Bundle file not found, using state file", "bundle", bundleName) + l1Contracts, err := inspect.L1(v.st, v.l2ChainID) + if err != nil { + return nil, fmt.Errorf("failed to extract L1 contracts from state: %w", err) + } + + // Select the appropriate bundle based on the input bundleName. + switch bundleName { + case inspect.SuperchainBundle: + bundle = l1Contracts.SuperchainDeployment + case inspect.ImplementationsBundle: + bundle = l1Contracts.ImplementationsDeployment + case inspect.OpChainBundle: + bundle = l1Contracts.OpChainDeployment + default: + return nil, fmt.Errorf("invalid contract bundle: %s", bundleName) + } + } + + return bundle, nil +} + +func (v *Verifier) verifyContractBundle(ctx context.Context, workdir string, bundleName string) error { + bundle, err := v.getContractBundle(workdir, bundleName) + if err != nil { + return fmt.Errorf("failed to retrieve bundle: %w", err) } // Use reflection to iterate over fields of the bundle. @@ -174,9 +221,10 @@ func (v *Verifier) verifyContractBundle(bundleName string) error { if field.Type() == reflect.TypeOf(common.Address{}) { addr := field.Interface().(common.Address) if addr != (common.Address{}) { // Skip zero addresses - name := typ.Field(i).Name - if err := v.verifySingleContract(context.Background(), name, bundleName); err != nil { - v.log.Error("failed to verify contract", "name", name, "bundle", bundleName, "error", err) + contractName := typ.Field(i).Name + if err := v.verifySingleContract(ctx, addr, contractName); err != nil { + v.numFailed++ + v.log.Error("failed to verify contract", "name", contractName, "bundle", bundleName, "error", err) } } } @@ -184,27 +232,7 @@ func (v *Verifier) verifyContractBundle(bundleName string) error { return nil } -func (v *Verifier) verifySingleContract(ctx context.Context, contractName string, bundleName string) error { - l1Contracts, err := inspect.L1(v.st, v.l2ChainID) - if err != nil { - return fmt.Errorf("failed to extract L1 contracts from state: %w", err) - } - - v.log.Info("Looking up contract address", "name", contractName, "bundle", bundleName) - addr, err := l1Contracts.GetContractAddress(contractName, bundleName) - if err != nil { - return fmt.Errorf("failed to find address for contract %s: %w", contractName, err) - } - - if err := v.verifyContract(addr, contractName); err != nil { - v.numFailed++ - return err - } - - return nil -} - -func (v *Verifier) verifyContract(address common.Address, contractName string) error { +func (v *Verifier) verifySingleContract(ctx context.Context, address common.Address, contractName string) error { verified, err := v.etherscan.isVerified(address) if err != nil { return fmt.Errorf("failed to check verification status: %w", err) @@ -215,13 +243,13 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e return nil } - v.log.Info("Formatting etherscan verification request", "name", contractName, "address", address.Hex()) + v.log.Info("Formatting etherscan verify request", "name", contractName, "address", address.Hex()) artifact, err := v.getContractArtifact(contractName) if err != nil { return fmt.Errorf("failed to get contract source: %w", err) } - constructorArgs, err := v.getConstructorArgs(context.Background(), address, contractName) + constructorArgs, err := v.getConstructorArgs(ctx, address, artifact) if err != nil { return fmt.Errorf("failed to get constructor args: %w", err) } @@ -230,8 +258,8 @@ func (v *Verifier) verifyContract(address common.Address, contractName string) e if err != nil { return fmt.Errorf("failed to verify contract: %w", err) } - v.log.Info("Verification request submitted", "name", contractName, "address", address.Hex()) + if err = v.etherscan.pollVerificationStatus(reqId); err != nil { return fmt.Errorf("failed when checking verification status: %w", err) } From 824322fc19293e9c49a6832a0c692211829ddabd Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Wed, 5 Mar 2025 13:21:06 -0500 Subject: [PATCH 17/26] add json tags (matching contract bundle) to deploy script outputs --- .../pkg/deployer/opcm/implementations.go | 36 +++++++++---------- op-deployer/pkg/deployer/opcm/superchain.go | 10 +++--- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index 163e899fc9d22..36efedde9a065 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -30,24 +30,24 @@ func (input *DeployImplementationsInput) InputSet() bool { } type DeployImplementationsOutput struct { - Opcm common.Address - OpcmContractsContainer common.Address - OpcmGameTypeAdder common.Address - OpcmDeployer common.Address - OpcmUpgrader common.Address - DelayedWETHImpl common.Address - OptimismPortalImpl common.Address - PreimageOracleSingleton common.Address - MipsSingleton common.Address - SystemConfigImpl common.Address - L1CrossDomainMessengerImpl common.Address - L1ERC721BridgeImpl common.Address - L1StandardBridgeImpl common.Address - OptimismMintableERC20FactoryImpl common.Address - DisputeGameFactoryImpl common.Address - AnchorStateRegistryImpl common.Address - SuperchainConfigImpl common.Address - ProtocolVersionsImpl common.Address + Opcm common.Address `json:"opcmAddress"` + OpcmContractsContainer common.Address `json:"opcmContractsContainerAddress"` + OpcmGameTypeAdder common.Address `json:"opcmGameTypeAdderAddress"` + OpcmDeployer common.Address `json:"opcmDeployerAddress"` + OpcmUpgrader common.Address `json:"opcmUpgraderAddress"` + DelayedWETHImpl common.Address `json:"delayedWETHImplAddress"` + OptimismPortalImpl common.Address `json:"optimismPortalImplAddress"` + PreimageOracleSingleton common.Address `json:"preimageOracleSingletonAddress"` + MipsSingleton common.Address `json:"mipsSingletonAddress"` + SystemConfigImpl common.Address `json:"systemConfigImplAddress"` + L1CrossDomainMessengerImpl common.Address `json:"l1CrossDomainMessengerImplAddress"` + L1ERC721BridgeImpl common.Address `json:"l1ERC721BridgeImplAddress"` + L1StandardBridgeImpl common.Address `json:"l1StandardBridgeImplAddress"` + OptimismMintableERC20FactoryImpl common.Address `json:"optimismMintableERC20FactoryImplAddress"` + DisputeGameFactoryImpl common.Address `json:"disputeGameFactoryImplAddress"` + AnchorStateRegistryImpl common.Address `json:"anchorStateRegistryImplAddress"` + SuperchainConfigImpl common.Address `json:"superchainConfigImplAddress"` + ProtocolVersionsImpl common.Address `json:"protocolVersionsImplAddress"` } func (output *DeployImplementationsOutput) CheckOutput(input common.Address) error { diff --git a/op-deployer/pkg/deployer/opcm/superchain.go b/op-deployer/pkg/deployer/opcm/superchain.go index 35811bd693404..abc9a1bd12d9a 100644 --- a/op-deployer/pkg/deployer/opcm/superchain.go +++ b/op-deployer/pkg/deployer/opcm/superchain.go @@ -26,11 +26,11 @@ func (dsi *DeploySuperchainInput) InputSet() bool { } type DeploySuperchainOutput struct { - SuperchainProxyAdmin common.Address - SuperchainConfigImpl common.Address - SuperchainConfigProxy common.Address - ProtocolVersionsImpl common.Address - ProtocolVersionsProxy common.Address + SuperchainProxyAdmin common.Address `json:"proxyAdminAddress"` + SuperchainConfigImpl common.Address `json:"superchainConfigImplAddress"` + SuperchainConfigProxy common.Address `json:"superchainConfigProxyAddress"` + ProtocolVersionsImpl common.Address `json:"protocolVersionsImplAddress"` + ProtocolVersionsProxy common.Address `json:"protocolVersionsProxyAddress"` } func (output *DeploySuperchainOutput) CheckOutput(input common.Address) error { From b3292001890b450744bd3d43ba0fd4c38366f540 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Wed, 5 Mar 2025 13:22:24 -0500 Subject: [PATCH 18/26] bootstrap default to writing to bootstrap_.json file --- op-deployer/pkg/deployer/bootstrap/flags.go | 20 +++++++++++++++++++ .../pkg/deployer/bootstrap/implementations.go | 3 ++- op-deployer/pkg/deployer/bootstrap/proxy.go | 2 +- .../pkg/deployer/bootstrap/superchain.go | 9 +++++++-- .../pkg/deployer/bootstrap/validator.go | 2 +- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/op-deployer/pkg/deployer/bootstrap/flags.go b/op-deployer/pkg/deployer/bootstrap/flags.go index ba29f6141163e..5ecee47bc4dc5 100644 --- a/op-deployer/pkg/deployer/bootstrap/flags.go +++ b/op-deployer/pkg/deployer/bootstrap/flags.go @@ -1,6 +1,9 @@ package bootstrap import ( + "fmt" + "path/filepath" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" "github.com/ethereum-optimism/optimism/op-service/cliapp" @@ -33,6 +36,12 @@ var ( EnvVars: deployer.PrefixEnvVar("OUTFILE"), Value: "-", } + WorkdirFlag = &cli.StringFlag{ + Name: deployer.WorkdirFlagName, + Usage: "Working directory.", + EnvVars: deployer.PrefixEnvVar("WORKDIR"), + Value: "", + } ArtifactsLocatorFlag = &cli.StringFlag{ Name: ArtifactsLocatorFlagName, Usage: "Locator for artifacts.", @@ -149,6 +158,7 @@ var ImplementationsFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, + WorkdirFlag, ArtifactsLocatorFlag, L1ContractsReleaseFlag, MIPSVersionFlag, @@ -167,6 +177,7 @@ var ProxyFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, + WorkdirFlag, ArtifactsLocatorFlag, ProxyOwnerFlag, } @@ -175,6 +186,7 @@ var SuperchainFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, + WorkdirFlag, ArtifactsLocatorFlag, SuperchainProxyAdminOwnerFlag, ProtocolVersionsOwnerFlag, @@ -188,6 +200,7 @@ var ValidatorFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, + WorkdirFlag, ArtifactsLocatorFlag, ConfigFileFlag, } @@ -218,3 +231,10 @@ var Commands = []*cli.Command{ Action: ValidatorCLI, }, } + +func getDefaultOutfile(commandName, outfile, workdir string) string { + if workdir != "" { + return filepath.Join(workdir, fmt.Sprintf("bootstrap_%s.json", commandName)) + } + return outfile +} diff --git a/op-deployer/pkg/deployer/bootstrap/implementations.go b/op-deployer/pkg/deployer/bootstrap/implementations.go index 9875ee6c0f3ad..026d060f942e1 100644 --- a/op-deployer/pkg/deployer/bootstrap/implementations.go +++ b/op-deployer/pkg/deployer/bootstrap/implementations.go @@ -8,6 +8,7 @@ import ( "math/big" "strings" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" @@ -115,7 +116,7 @@ func ImplementationsCLI(cliCtx *cli.Context) error { cfg.Logger = l ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) - outfile := cliCtx.String(OutfileFlagName) + outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) dio, err := Implementations(ctx, cfg) if err != nil { return fmt.Errorf("failed to deploy implementations: %w", err) diff --git a/op-deployer/pkg/deployer/bootstrap/proxy.go b/op-deployer/pkg/deployer/bootstrap/proxy.go index 06f4f762b5e17..c6227d2a06095 100644 --- a/op-deployer/pkg/deployer/bootstrap/proxy.go +++ b/op-deployer/pkg/deployer/bootstrap/proxy.go @@ -76,7 +76,7 @@ func ProxyCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) - outfile := cliCtx.String(OutfileFlagName) + outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) diff --git a/op-deployer/pkg/deployer/bootstrap/superchain.go b/op-deployer/pkg/deployer/bootstrap/superchain.go index 95055f2a2ae16..d99360bb2aa2c 100644 --- a/op-deployer/pkg/deployer/bootstrap/superchain.go +++ b/op-deployer/pkg/deployer/bootstrap/superchain.go @@ -99,7 +99,7 @@ func SuperchainCLI(cliCtx *cli.Context) error { paused := cliCtx.Bool(PausedFlagName) requiredVersionStr := cliCtx.String(RequiredProtocolVersionFlagName) recommendedVersionStr := cliCtx.String(RecommendedProtocolVersionFlagName) - outfile := cliCtx.String(OutfileFlagName) + outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) cfg := SuperchainConfig{ L1RPCUrl: l1RPCUrl, @@ -127,9 +127,14 @@ func SuperchainCLI(cliCtx *cli.Context) error { return fmt.Errorf("failed to deploy superchain: %w", err) } - if err := jsonutil.WriteJSON(dso, ioutil.ToStdOutOrFileOrNoop(outfile, 0o755)); err != nil { + outTarget := ioutil.ToStdOutOrFileOrNoop(outfile, 0o755) + if err := jsonutil.WriteJSON(dso, outTarget); err != nil { return fmt.Errorf("failed to write output: %w", err) } + if outfile != "-" { + l.Info("results written to file", "filepath", outfile) + } + return nil } diff --git a/op-deployer/pkg/deployer/bootstrap/validator.go b/op-deployer/pkg/deployer/bootstrap/validator.go index f32b00b13d4c4..a1e71bb7a860b 100644 --- a/op-deployer/pkg/deployer/bootstrap/validator.go +++ b/op-deployer/pkg/deployer/bootstrap/validator.go @@ -94,7 +94,7 @@ func ValidatorCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) - outfile := cliCtx.String(OutfileFlagName) + outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) configFile := cliCtx.String(ConfigFileFlag.Name) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) From 01e53028fc267f98f98af5038679656404061839 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Wed, 5 Mar 2025 16:03:22 -0500 Subject: [PATCH 19/26] pass contracts-locator flag to verify command --- op-deployer/pkg/deployer/bootstrap/flags.go | 23 +-- op-deployer/pkg/deployer/bootstrap/proxy.go | 2 +- .../pkg/deployer/bootstrap/superchain.go | 2 +- .../pkg/deployer/bootstrap/validator.go | 2 +- op-deployer/pkg/deployer/flags.go | 48 ++--- op-deployer/pkg/deployer/verify/artifacts.go | 3 +- op-deployer/pkg/deployer/verify/verifier.go | 164 +++++------------- 7 files changed, 82 insertions(+), 162 deletions(-) diff --git a/op-deployer/pkg/deployer/bootstrap/flags.go b/op-deployer/pkg/deployer/bootstrap/flags.go index 5ecee47bc4dc5..a116097d5dbc1 100644 --- a/op-deployer/pkg/deployer/bootstrap/flags.go +++ b/op-deployer/pkg/deployer/bootstrap/flags.go @@ -13,7 +13,6 @@ import ( const ( OutfileFlagName = "outfile" - ArtifactsLocatorFlagName = "artifacts-locator" WithdrawalDelaySecondsFlagName = "withdrawal-delay-seconds" MinProposalSizeBytesFlagName = "min-proposal-size-bytes" ChallengePeriodSecondsFlagName = "challenge-period-seconds" @@ -22,6 +21,7 @@ const ( MIPSVersionFlagName = "mips-version" ProxyOwnerFlagName = "proxy-owner" SuperchainProxyAdminOwnerFlagName = "superchain-proxy-admin-owner" + L1ContractsReleaseFlagName = "l1-contracts-release" ProtocolVersionsOwnerFlagName = "protocol-versions-owner" GuardianFlagName = "guardian" PausedFlagName = "paused" @@ -42,10 +42,10 @@ var ( EnvVars: deployer.PrefixEnvVar("WORKDIR"), Value: "", } - ArtifactsLocatorFlag = &cli.StringFlag{ - Name: ArtifactsLocatorFlagName, - Usage: "Locator for artifacts.", - EnvVars: deployer.PrefixEnvVar("ARTIFACTS_LOCATOR"), + L1ContractsReleaseFlag = &cli.StringFlag{ + Name: L1ContractsReleaseFlagName, + Usage: "Release version to set OPCM implementations for, of the format `op-contracts/vX.Y.Z`.", + EnvVars: deployer.PrefixEnvVar("L1_CONTRACTS_RELEASE"), } WithdrawalDelaySecondsFlag = &cli.Uint64Flag{ Name: WithdrawalDelaySecondsFlagName, @@ -122,11 +122,6 @@ var ( Usage: "Recommended protocol version (semver)", EnvVars: deployer.PrefixEnvVar("RECOMMENDED_PROTOCOL_VERSION"), } - L1ContractsReleaseFlag = &cli.StringFlag{ - Name: "l1-contracts-release", - Usage: "Release version to set OPCM implementations for, of the format `op-contracts/vX.Y.Z`.", - EnvVars: deployer.PrefixEnvVar("L1_CONTRACTS_RELEASE"), - } SuperchainConfigProxyFlag = &cli.StringFlag{ Name: "superchain-config-proxy", Usage: "Superchain config proxy.", @@ -159,7 +154,7 @@ var ImplementationsFlags = []cli.Flag{ deployer.PrivateKeyFlag, OutfileFlag, WorkdirFlag, - ArtifactsLocatorFlag, + deployer.ArtifactsLocatorFlag, L1ContractsReleaseFlag, MIPSVersionFlag, WithdrawalDelaySecondsFlag, @@ -178,7 +173,7 @@ var ProxyFlags = []cli.Flag{ deployer.PrivateKeyFlag, OutfileFlag, WorkdirFlag, - ArtifactsLocatorFlag, + deployer.ArtifactsLocatorFlag, ProxyOwnerFlag, } @@ -187,7 +182,7 @@ var SuperchainFlags = []cli.Flag{ deployer.PrivateKeyFlag, OutfileFlag, WorkdirFlag, - ArtifactsLocatorFlag, + deployer.ArtifactsLocatorFlag, SuperchainProxyAdminOwnerFlag, ProtocolVersionsOwnerFlag, GuardianFlag, @@ -201,7 +196,7 @@ var ValidatorFlags = []cli.Flag{ deployer.PrivateKeyFlag, OutfileFlag, WorkdirFlag, - ArtifactsLocatorFlag, + deployer.ArtifactsLocatorFlag, ConfigFileFlag, } diff --git a/op-deployer/pkg/deployer/bootstrap/proxy.go b/op-deployer/pkg/deployer/bootstrap/proxy.go index c6227d2a06095..554aae6ced608 100644 --- a/op-deployer/pkg/deployer/bootstrap/proxy.go +++ b/op-deployer/pkg/deployer/bootstrap/proxy.go @@ -77,7 +77,7 @@ func ProxyCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) - artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsURLStr := cliCtx.String(deployer.ArtifactsLocatorFlagName) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) artifactsLocator := new(artifacts.Locator) diff --git a/op-deployer/pkg/deployer/bootstrap/superchain.go b/op-deployer/pkg/deployer/bootstrap/superchain.go index d99360bb2aa2c..addc9f61d658f 100644 --- a/op-deployer/pkg/deployer/bootstrap/superchain.go +++ b/op-deployer/pkg/deployer/bootstrap/superchain.go @@ -87,7 +87,7 @@ func SuperchainCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) - artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsURLStr := cliCtx.String(deployer.ArtifactsLocatorFlagName) artifactsLocator := new(artifacts.Locator) if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { return fmt.Errorf("failed to parse artifacts URL: %w", err) diff --git a/op-deployer/pkg/deployer/bootstrap/validator.go b/op-deployer/pkg/deployer/bootstrap/validator.go index a1e71bb7a860b..9498db050a1cc 100644 --- a/op-deployer/pkg/deployer/bootstrap/validator.go +++ b/op-deployer/pkg/deployer/bootstrap/validator.go @@ -95,7 +95,7 @@ func ValidatorCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) - artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsURLStr := cliCtx.String(deployer.ArtifactsLocatorFlagName) configFile := cliCtx.String(ConfigFileFlag.Name) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 1008db61af944..8949a3e35e343 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -13,19 +13,19 @@ import ( ) const ( - EnvVarPrefix = "DEPLOYER" - L1RPCURLFlagName = "l1-rpc-url" - CacheDirFlagName = "cache-dir" - L1ChainIDFlagName = "l1-chain-id" - L2ChainIDsFlagName = "l2-chain-ids" - WorkdirFlagName = "workdir" - OutdirFlagName = "outdir" - PrivateKeyFlagName = "private-key" - IntentTypeFlagName = "intent-type" - EtherscanAPIKeyFlagName = "etherscan-api-key" - ContractBundleFlagName = "contract-bundle" - ContractNameFlagName = "contract-name" - L2ChainIDFlagName = "l2-chain-id" + EnvVarPrefix = "DEPLOYER" + L1RPCURLFlagName = "l1-rpc-url" + CacheDirFlagName = "cache-dir" + L1ChainIDFlagName = "l1-chain-id" + ArtifactsLocatorFlagName = "artifacts-locator" + L2ChainIDsFlagName = "l2-chain-ids" + WorkdirFlagName = "workdir" + OutdirFlagName = "outdir" + PrivateKeyFlagName = "private-key" + IntentTypeFlagName = "intent-type" + EtherscanAPIKeyFlagName = "etherscan-api-key" + InputFileFlagName = "input-file" + ContractNameFlagName = "contract-name" ) type DeploymentTarget string @@ -72,6 +72,11 @@ var ( "L1_RPC_URL", }, } + ArtifactsLocatorFlag = &cli.StringFlag{ + Name: ArtifactsLocatorFlagName, + Usage: "Locator for artifacts.", + EnvVars: PrefixEnvVar("ARTIFACTS_LOCATOR"), + } CacheDirFlag = &cli.StringFlag{ Name: CacheDirFlagName, Usage: "Cache directory. " + @@ -90,11 +95,6 @@ var ( Usage: "Comma-separated list of L2 chain IDs to deploy.", EnvVars: PrefixEnvVar("L2_CHAIN_IDS"), } - L2ChainIDFlag = &cli.StringFlag{ - Name: L2ChainIDFlagName, - Usage: "Single L2 chain ID", - EnvVars: PrefixEnvVar("L2_CHAIN_ID"), - } WorkdirFlag = &cli.StringFlag{ Name: WorkdirFlagName, Usage: "Directory storing intent and stage. Defaults to the current directory.", @@ -133,10 +133,10 @@ var ( EnvVars: PrefixEnvVar("ETHERSCAN_API_KEY"), Required: true, } - ContractBundleFlag = &cli.StringFlag{ - Name: ContractBundleFlagName, - Usage: "contract bundle/grouping (superchain|implementations|opchain)", - EnvVars: PrefixEnvVar("CONTRACT_BUNDLE"), + InputFileFlag = &cli.StringFlag{ + Name: InputFileFlagName, + Usage: "filepath of input file for command", + EnvVars: PrefixEnvVar("INPUT_FILE"), } ContractNameFlag = &cli.StringFlag{ Name: ContractNameFlagName, @@ -169,11 +169,11 @@ var UpgradeFlags = []cli.Flag{ var VerifyFlags = []cli.Flag{ L1RPCURLFlag, + ArtifactsLocatorFlag, WorkdirFlag, EtherscanAPIKeyFlag, - ContractBundleFlag, + InputFileFlag, ContractNameFlag, - L2ChainIDFlag, } func PrefixEnvVar(name string) []string { diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go index a873924958cbd..200dcac323476 100644 --- a/op-deployer/pkg/deployer/verify/artifacts.go +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -29,6 +29,7 @@ var contractNameExceptions = map[string]string{ func getArtifactName(name string) string { lookupName := strings.TrimSuffix(name, "Address") + lookupName = strings.ToUpper(string(lookupName[0])) + lookupName[1:] if artifactName, exists := contractNameExceptions[lookupName]; exists { return artifactName @@ -81,7 +82,7 @@ func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { contractName = contractFile + ":" + name break } - v.log.Info("contractName", "name", contractName) + v.log.Info("Compilation target", "target", contractName) return &contractArtifact{ ContractName: contractName, diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index 476bfb08765e0..68fab735928ae 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -5,8 +5,6 @@ import ( "encoding/json" "fmt" "os" - "path/filepath" - "reflect" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" @@ -17,18 +15,12 @@ import ( "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/inspect" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" - op_service "github.com/ethereum-optimism/optimism/op-service" "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" oplog "github.com/ethereum-optimism/optimism/op-service/log" ) type Verifier struct { l1ChainID uint64 - l2ChainID common.Hash - st *state.State artifactsFS foundry.StatDirFs log log.Logger etherscan *EtherscanClient @@ -38,22 +30,16 @@ type Verifier struct { numFailed int } -func NewVerifier(apiKey string, l1ChainID uint64, l2ChainID common.Hash, st *state.State, artifactsFS foundry.StatDirFs, l log.Logger, l1Client *ethclient.Client) (*Verifier, error) { +func NewVerifier(apiKey string, l1ChainID uint64, artifactsFS foundry.StatDirFs, l log.Logger, l1Client *ethclient.Client) (*Verifier, error) { etherscanUrl := getAPIEndpoint(l1ChainID) if etherscanUrl == "" { return nil, fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) } - if l2ChainID == (common.Hash{}) { - l2ChainID = st.AppliedIntent.Chains[0].ID - } - etherscan := NewEtherscanClient(apiKey, etherscanUrl, rate.NewLimiter(rate.Limit(3), 2)) return &Verifier{ l1ChainID: l1ChainID, - l2ChainID: l2ChainID, - st: st, artifactsFS: artifactsFS, log: l, l1Client: l1Client, @@ -67,22 +53,20 @@ func VerifyCLI(cliCtx *cli.Context) error { oplog.SetGlobalLogHandler(l.Handler()) l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) - workdir := cliCtx.String(deployer.WorkdirFlagName) etherscanAPIKey := cliCtx.String(deployer.EtherscanAPIKeyFlagName) if etherscanAPIKey == "" { - return fmt.Errorf("etherscan API key is required") + return fmt.Errorf("etherscan-api-key is required") } - bundleName := cliCtx.String(deployer.ContractBundleFlagName) - l2ChainIDRaw := cliCtx.String(deployer.L2ChainIDFlagName) + inputFile := cliCtx.String(deployer.InputFileFlagName) + if inputFile == "" { + return fmt.Errorf("input-file is required") + } + contractName := cliCtx.String(deployer.ContractNameFlagName) - var l2ChainID common.Hash - var err error - if l2ChainIDRaw != "" { - l2ChainID, err = op_service.Parse256BitChainID(l2ChainIDRaw) - if err != nil { - return fmt.Errorf("invalid L2 chain ID '%s': %w", l2ChainIDRaw, err) - } + l1ContractsLocator := cliCtx.String(deployer.ArtifactsLocatorFlagName) + if l1ContractsLocator == "" { + return fmt.Errorf("artifacts-locator is required") } ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) @@ -99,22 +83,17 @@ func VerifyCLI(cliCtx *cli.Context) error { } l1ChainId := chainId.Uint64() - st, err := pipeline.ReadState(workdir) + locator, err := artifacts.NewLocatorFromURL(l1ContractsLocator) if err != nil { - return fmt.Errorf("failed to read state: %w", err) + return fmt.Errorf("failed to parse l1 contracts release locator: %w", err) } - - if l1ChainId != st.AppliedIntent.L1ChainID { - return fmt.Errorf("rpc l1 chain ID does not match state l1 chain ID: %d != %d", l1ChainId, st.AppliedIntent.L1ChainID) - } - - artifactsFS, err := artifacts.Download(ctx, st.AppliedIntent.L1ContractsLocator, nil, deployer.DefaultCacheDir) + artifactsFS, err := artifacts.Download(ctx, locator, nil, deployer.DefaultCacheDir) if err != nil { return fmt.Errorf("failed to get artifacts: %w", err) } l.Info("Downloaded artifacts", "path", artifactsFS) - v, err := NewVerifier(etherscanAPIKey, l1ChainId, l2ChainID, st, artifactsFS, l, l1Client) + v, err := NewVerifier(etherscanAPIKey, l1ChainId, artifactsFS, l, l1Client) if err != nil { return fmt.Errorf("failed to create verifier: %w", err) } @@ -123,109 +102,54 @@ func VerifyCLI(cliCtx *cli.Context) error { v.log.Info("final results", "numVerified", v.numVerified, "numSkipped", v.numSkipped, "numFailed", v.numFailed) }() - if bundleName == "" { - if err := v.verifyAll(ctx, workdir); err != nil { - return err - } - } else if err := v.verifyContractBundle(ctx, workdir, bundleName); err != nil { + if err := v.verifyContractBundle(ctx, inputFile, contractName); err != nil { return err } v.log.Info("--- COMPLETE ---") return nil } -func (v *Verifier) verifyAll(ctx context.Context, workdir string) error { - for _, bundleName := range inspect.ContractBundles { - if err := v.verifyContractBundle(ctx, workdir, bundleName); err != nil { - return fmt.Errorf("failed to verify bundle %s: %w", bundleName, err) - } +func (v *Verifier) getContractBundle(filepath string) (map[string]common.Address, error) { + _, err := os.Stat(filepath) + if err != nil { + return nil, fmt.Errorf("input file not found: %s", filepath) } - return nil -} - -func (v *Verifier) getContractBundle(workdir string, bundleName string) (interface{}, error) { - bundleFilePath := filepath.Join(workdir, fmt.Sprintf("bootstrap_%s.json", bundleName)) - - var bundle interface{} - - // Check if the bundle file exists - if _, err := os.Stat(bundleFilePath); err == nil { - // File exists, read and parse it - v.log.Info("Found bundle file", "path", bundleFilePath) - bundleData, err := os.ReadFile(bundleFilePath) - if err != nil { - return nil, fmt.Errorf("failed to read bundle file %s: %w", bundleFilePath, err) - } - // Parse the file based on bundle type - switch bundleName { - case inspect.SuperchainBundle: - var superchainBundle inspect.SuperchainDeployment - if err := json.Unmarshal(bundleData, &superchainBundle); err != nil { - return nil, fmt.Errorf("failed to parse superchain bundle: %w", err) - } - bundle = superchainBundle - - case inspect.ImplementationsBundle: - var implBundle inspect.ImplementationsDeployment - if err := json.Unmarshal(bundleData, &implBundle); err != nil { - return nil, fmt.Errorf("failed to parse implementations bundle: %w", err) - } - bundle = implBundle - - case inspect.OpChainBundle: - var opChainBundle inspect.OpChainDeployment - if err := json.Unmarshal(bundleData, &opChainBundle); err != nil { - return nil, fmt.Errorf("failed to parse opchain bundle: %w", err) - } - bundle = opChainBundle - - default: - return nil, fmt.Errorf("invalid contract bundle: %s", bundleName) - } - v.log.Info("Using bundle file", "path", bundleFilePath) - } else { - v.log.Info("Bundle file not found, using state file", "bundle", bundleName) - l1Contracts, err := inspect.L1(v.st, v.l2ChainID) - if err != nil { - return nil, fmt.Errorf("failed to extract L1 contracts from state: %w", err) - } + bundleData, err := os.ReadFile(filepath) + if err != nil { + return nil, fmt.Errorf("failed to read bundle file %s: %w", filepath, err) + } - // Select the appropriate bundle based on the input bundleName. - switch bundleName { - case inspect.SuperchainBundle: - bundle = l1Contracts.SuperchainDeployment - case inspect.ImplementationsBundle: - bundle = l1Contracts.ImplementationsDeployment - case inspect.OpChainBundle: - bundle = l1Contracts.OpChainDeployment - default: - return nil, fmt.Errorf("invalid contract bundle: %s", bundleName) - } + var bundle map[string]common.Address + if err := json.Unmarshal(bundleData, &bundle); err != nil { + return nil, fmt.Errorf("failed to parse superchain bundle: %w", err) } return bundle, nil } -func (v *Verifier) verifyContractBundle(ctx context.Context, workdir string, bundleName string) error { - bundle, err := v.getContractBundle(workdir, bundleName) +func (v *Verifier) verifyContractBundle(ctx context.Context, filepath string, contractName string) error { + bundle, err := v.getContractBundle(filepath) if err != nil { return fmt.Errorf("failed to retrieve bundle: %w", err) } - // Use reflection to iterate over fields of the bundle. - val := reflect.ValueOf(bundle) - typ := val.Type() - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - if field.Type() == reflect.TypeOf(common.Address{}) { - addr := field.Interface().(common.Address) - if addr != (common.Address{}) { // Skip zero addresses - contractName := typ.Field(i).Name - if err := v.verifySingleContract(ctx, addr, contractName); err != nil { - v.numFailed++ - v.log.Error("failed to verify contract", "name", contractName, "bundle", bundleName, "error", err) - } + if contractName != "" { + addr, ok := bundle[contractName] + if !ok { + return fmt.Errorf("contract %s not found in bundle", contractName) + } + if err := v.verifySingleContract(ctx, addr, contractName); err != nil { + return fmt.Errorf("failed to verify contract %s: %w", contractName, err) + } + return nil + } + + for contractName, addr := range bundle { + if addr != (common.Address{}) { // skip zero addresses + if err := v.verifySingleContract(ctx, addr, contractName); err != nil { + v.numFailed++ + v.log.Error("failed to verify contract", "name", contractName, "error", err) } } } From a498fdd470b8c48b129026f67ef0de3cdef518ab Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 6 Mar 2025 09:15:33 -0500 Subject: [PATCH 20/26] minimize flags required for bootstrap, verify --- op-deployer/pkg/deployer/bootstrap/flags.go | 30 ++++--------------- .../pkg/deployer/bootstrap/implementations.go | 3 +- op-deployer/pkg/deployer/bootstrap/proxy.go | 2 +- .../pkg/deployer/bootstrap/superchain.go | 9 ++---- .../pkg/deployer/bootstrap/validator.go | 2 +- op-deployer/pkg/deployer/flags.go | 1 - 6 files changed, 10 insertions(+), 37 deletions(-) diff --git a/op-deployer/pkg/deployer/bootstrap/flags.go b/op-deployer/pkg/deployer/bootstrap/flags.go index a116097d5dbc1..e55bc204ed5ed 100644 --- a/op-deployer/pkg/deployer/bootstrap/flags.go +++ b/op-deployer/pkg/deployer/bootstrap/flags.go @@ -1,9 +1,6 @@ package bootstrap import ( - "fmt" - "path/filepath" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" "github.com/ethereum-optimism/optimism/op-service/cliapp" @@ -36,17 +33,6 @@ var ( EnvVars: deployer.PrefixEnvVar("OUTFILE"), Value: "-", } - WorkdirFlag = &cli.StringFlag{ - Name: deployer.WorkdirFlagName, - Usage: "Working directory.", - EnvVars: deployer.PrefixEnvVar("WORKDIR"), - Value: "", - } - L1ContractsReleaseFlag = &cli.StringFlag{ - Name: L1ContractsReleaseFlagName, - Usage: "Release version to set OPCM implementations for, of the format `op-contracts/vX.Y.Z`.", - EnvVars: deployer.PrefixEnvVar("L1_CONTRACTS_RELEASE"), - } WithdrawalDelaySecondsFlag = &cli.Uint64Flag{ Name: WithdrawalDelaySecondsFlagName, Usage: "Withdrawal delay in seconds.", @@ -122,6 +108,11 @@ var ( Usage: "Recommended protocol version (semver)", EnvVars: deployer.PrefixEnvVar("RECOMMENDED_PROTOCOL_VERSION"), } + L1ContractsReleaseFlag = &cli.StringFlag{ + Name: L1ContractsReleaseFlagName, + Usage: "Release version to set OPCM implementations for, of the format `op-contracts/vX.Y.Z`.", + EnvVars: deployer.PrefixEnvVar("L1_CONTRACTS_RELEASE"), + } SuperchainConfigProxyFlag = &cli.StringFlag{ Name: "superchain-config-proxy", Usage: "Superchain config proxy.", @@ -153,7 +144,6 @@ var ImplementationsFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, - WorkdirFlag, deployer.ArtifactsLocatorFlag, L1ContractsReleaseFlag, MIPSVersionFlag, @@ -172,7 +162,6 @@ var ProxyFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, - WorkdirFlag, deployer.ArtifactsLocatorFlag, ProxyOwnerFlag, } @@ -181,7 +170,6 @@ var SuperchainFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, - WorkdirFlag, deployer.ArtifactsLocatorFlag, SuperchainProxyAdminOwnerFlag, ProtocolVersionsOwnerFlag, @@ -195,7 +183,6 @@ var ValidatorFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, - WorkdirFlag, deployer.ArtifactsLocatorFlag, ConfigFileFlag, } @@ -226,10 +213,3 @@ var Commands = []*cli.Command{ Action: ValidatorCLI, }, } - -func getDefaultOutfile(commandName, outfile, workdir string) string { - if workdir != "" { - return filepath.Join(workdir, fmt.Sprintf("bootstrap_%s.json", commandName)) - } - return outfile -} diff --git a/op-deployer/pkg/deployer/bootstrap/implementations.go b/op-deployer/pkg/deployer/bootstrap/implementations.go index 026d060f942e1..9875ee6c0f3ad 100644 --- a/op-deployer/pkg/deployer/bootstrap/implementations.go +++ b/op-deployer/pkg/deployer/bootstrap/implementations.go @@ -8,7 +8,6 @@ import ( "math/big" "strings" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" @@ -116,7 +115,7 @@ func ImplementationsCLI(cliCtx *cli.Context) error { cfg.Logger = l ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) - outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) + outfile := cliCtx.String(OutfileFlagName) dio, err := Implementations(ctx, cfg) if err != nil { return fmt.Errorf("failed to deploy implementations: %w", err) diff --git a/op-deployer/pkg/deployer/bootstrap/proxy.go b/op-deployer/pkg/deployer/bootstrap/proxy.go index 554aae6ced608..865dfe5b8a7ee 100644 --- a/op-deployer/pkg/deployer/bootstrap/proxy.go +++ b/op-deployer/pkg/deployer/bootstrap/proxy.go @@ -76,7 +76,7 @@ func ProxyCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) - outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) + outfile := cliCtx.String(OutfileFlagName) artifactsURLStr := cliCtx.String(deployer.ArtifactsLocatorFlagName) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) diff --git a/op-deployer/pkg/deployer/bootstrap/superchain.go b/op-deployer/pkg/deployer/bootstrap/superchain.go index addc9f61d658f..7628f257b34ee 100644 --- a/op-deployer/pkg/deployer/bootstrap/superchain.go +++ b/op-deployer/pkg/deployer/bootstrap/superchain.go @@ -99,7 +99,7 @@ func SuperchainCLI(cliCtx *cli.Context) error { paused := cliCtx.Bool(PausedFlagName) requiredVersionStr := cliCtx.String(RequiredProtocolVersionFlagName) recommendedVersionStr := cliCtx.String(RecommendedProtocolVersionFlagName) - outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) + outfile := cliCtx.String(OutfileFlagName) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) cfg := SuperchainConfig{ L1RPCUrl: l1RPCUrl, @@ -127,14 +127,9 @@ func SuperchainCLI(cliCtx *cli.Context) error { return fmt.Errorf("failed to deploy superchain: %w", err) } - outTarget := ioutil.ToStdOutOrFileOrNoop(outfile, 0o755) - if err := jsonutil.WriteJSON(dso, outTarget); err != nil { + if err := jsonutil.WriteJSON(dso, ioutil.ToStdOutOrFileOrNoop(outfile, 0o755)); err != nil { return fmt.Errorf("failed to write output: %w", err) } - if outfile != "-" { - l.Info("results written to file", "filepath", outfile) - } - return nil } diff --git a/op-deployer/pkg/deployer/bootstrap/validator.go b/op-deployer/pkg/deployer/bootstrap/validator.go index 9498db050a1cc..5daaf890d56b5 100644 --- a/op-deployer/pkg/deployer/bootstrap/validator.go +++ b/op-deployer/pkg/deployer/bootstrap/validator.go @@ -94,7 +94,7 @@ func ValidatorCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) - outfile := getDefaultOutfile(cliCtx.Command.Name, cliCtx.String(OutfileFlagName), cliCtx.String(deployer.WorkdirFlagName)) + outfile := cliCtx.String(OutfileFlagName) artifactsURLStr := cliCtx.String(deployer.ArtifactsLocatorFlagName) configFile := cliCtx.String(ConfigFileFlag.Name) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 8949a3e35e343..5ec0b2c4c83ac 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -170,7 +170,6 @@ var UpgradeFlags = []cli.Flag{ var VerifyFlags = []cli.Flag{ L1RPCURLFlag, ArtifactsLocatorFlag, - WorkdirFlag, EtherscanAPIKeyFlag, InputFileFlag, ContractNameFlag, From b2d8534c75d76a52faa71ba1564e3defd5d672df Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 6 Mar 2025 09:45:04 -0500 Subject: [PATCH 21/26] fix TestArtifactJSON --- .../foundry/testdata/forge-artifacts/Owned.sol/Owned.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-chain-ops/foundry/testdata/forge-artifacts/Owned.sol/Owned.json b/op-chain-ops/foundry/testdata/forge-artifacts/Owned.sol/Owned.json index 2646394b6347f..f8f68822ae93e 100644 --- a/op-chain-ops/foundry/testdata/forge-artifacts/Owned.sol/Owned.json +++ b/op-chain-ops/foundry/testdata/forge-artifacts/Owned.sol/Owned.json @@ -1 +1 @@ -{"abi":[{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"setOwner","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnerUpdated","inputs":[{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"owner()":"8da5cb5b","setOwner(address)":"13af4035"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerUpdated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Simple single owner authorization mixin.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/solmate/src/auth/Owned.sol\":\"Owned\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[\":@lib-keccak/=lib/lib-keccak/contracts/lib/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":@rari-capital/solmate/=lib/solmate/\",\":@solady-test/=lib/lib-keccak/lib/solady/test/\",\":@solady/=lib/solady/src/\",\":automate/=lib/automate/contracts/\",\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/automate/lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":gelato/=lib/automate/contracts/\",\":hardhat/=lib/automate/node_modules/hardhat/\",\":kontrol-cheatcodes/=lib/kontrol-cheatcodes/src/\",\":lib-keccak/=lib/lib-keccak/contracts/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":prb-test/=lib/automate/lib/prb-test/src/\",\":prb/-est/=lib/automate/lib/prb-test/src/\",\":safe-contracts/=lib/safe-contracts/contracts/\",\":solady/=lib/solady/\",\":solmate/=lib/solmate/src/\"]},\"sources\":{\"lib/solmate/src/auth/Owned.sol\":{\"keccak256\":\"0x7e91c80b0dd1a14a19cb9e661b99924043adab6d9d893bbfcf3a6a3dc23a6743\",\"license\":\"AGPL-3.0-only\",\"urls\":[\"bzz-raw://515890d9fc87d6762dae2354a3a0714a26c652f0ea5bb631122be1968ef8c0e9\",\"dweb:/ipfs/QmTRpQ7uoAR1vCACKJm14Ba3oKVLqcA9reTwbHAPxawVpM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"user","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnerUpdated","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"setOwner"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@lib-keccak/=lib/lib-keccak/contracts/lib/","@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","@rari-capital/solmate/=lib/solmate/","@solady-test/=lib/lib-keccak/lib/solady/test/","@solady/=lib/solady/src/","automate/=lib/automate/contracts/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/automate/lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","gelato/=lib/automate/contracts/","hardhat/=lib/automate/node_modules/hardhat/","kontrol-cheatcodes/=lib/kontrol-cheatcodes/src/","lib-keccak/=lib/lib-keccak/contracts/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/","prb-test/=lib/automate/lib/prb-test/src/","prb/-est/=lib/automate/lib/prb-test/src/","safe-contracts/=lib/safe-contracts/contracts/","solady/=lib/solady/","solmate/=lib/solmate/src/"],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"lib/solmate/src/auth/Owned.sol":"Owned"},"evmVersion":"london","libraries":{}},"sources":{"lib/solmate/src/auth/Owned.sol":{"keccak256":"0x7e91c80b0dd1a14a19cb9e661b99924043adab6d9d893bbfcf3a6a3dc23a6743","urls":["bzz-raw://515890d9fc87d6762dae2354a3a0714a26c652f0ea5bb631122be1968ef8c0e9","dweb:/ipfs/QmTRpQ7uoAR1vCACKJm14Ba3oKVLqcA9reTwbHAPxawVpM"],"license":"AGPL-3.0-only"}},"version":1},"storageLayout":{"storage":[{"astId":53517,"contract":"lib/solmate/src/auth/Owned.sol:Owned","label":"owner","offset":0,"slot":"0","type":"t_address"}],"types":{"t_address":{"encoding":"inplace","label":"address","numberOfBytes":"20"}}},"userdoc":{"version":1,"kind":"user","notice":"Simple single owner authorization mixin."},"devdoc":{"version":1,"kind":"dev","author":"Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)"},"ast":{"absolutePath":"lib/solmate/src/auth/Owned.sol","id":53567,"exportedSymbols":{"Owned":[53566]},"nodeType":"SourceUnit","src":"42:1398:68","nodes":[{"id":53508,"nodeType":"PragmaDirective","src":"42:24:68","nodes":[],"literals":["solidity",">=","0.8",".0"]},{"id":53566,"nodeType":"ContractDefinition","src":"212:1227:68","nodes":[{"id":53515,"nodeType":"EventDefinition","src":"421:67:68","nodes":[],"anonymous":false,"eventSelector":"8292fce18fa69edf4db7b94ea2e58241df0ae57f97e0a6c9b29067028bf92d76","name":"OwnerUpdated","nameLocation":"427:12:68","parameters":{"id":53514,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53511,"indexed":true,"mutability":"mutable","name":"user","nameLocation":"456:4:68","nodeType":"VariableDeclaration","scope":53515,"src":"440:20:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53510,"name":"address","nodeType":"ElementaryTypeName","src":"440:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":53513,"indexed":true,"mutability":"mutable","name":"newOwner","nameLocation":"478:8:68","nodeType":"VariableDeclaration","scope":53515,"src":"462:24:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53512,"name":"address","nodeType":"ElementaryTypeName","src":"462:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"439:48:68"}},{"id":53517,"nodeType":"VariableDeclaration","src":"679:20:68","nodes":[],"constant":false,"functionSelector":"8da5cb5b","mutability":"mutable","name":"owner","nameLocation":"694:5:68","scope":53566,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53516,"name":"address","nodeType":"ElementaryTypeName","src":"679:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"public"},{"id":53529,"nodeType":"ModifierDefinition","src":"706:102:68","nodes":[],"body":{"id":53528,"nodeType":"Block","src":"735:73:68","nodes":[],"statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_address","typeString":"address"},"id":53523,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":53520,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"753:3:68","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":53521,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"753:10:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"id":53522,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"767:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"753:19:68","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"554e415554484f52495a4544","id":53524,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"774:14:68","typeDescriptions":{"typeIdentifier":"t_stringliteral_269df367cd41cace5897a935d0e0858fe4543b5619d45e09af6b124c1bb3d528","typeString":"literal_string \"UNAUTHORIZED\""},"value":"UNAUTHORIZED"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_269df367cd41cace5897a935d0e0858fe4543b5619d45e09af6b124c1bb3d528","typeString":"literal_string \"UNAUTHORIZED\""}],"id":53519,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"745:7:68","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":53525,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"745:44:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53526,"nodeType":"ExpressionStatement","src":"745:44:68"},{"id":53527,"nodeType":"PlaceholderStatement","src":"800:1:68"}]},"name":"onlyOwner","nameLocation":"715:9:68","parameters":{"id":53518,"nodeType":"ParameterList","parameters":[],"src":"724:2:68"},"virtual":true,"visibility":"internal"},{"id":53547,"nodeType":"FunctionDefinition","src":"996:107:68","nodes":[],"body":{"id":53546,"nodeType":"Block","src":"1024:79:68","nodes":[],"statements":[{"expression":{"id":53536,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":53534,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"1034:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":53535,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53531,"src":"1042:6:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1034:14:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":53537,"nodeType":"ExpressionStatement","src":"1034:14:68"},{"eventCall":{"arguments":[{"arguments":[{"hexValue":"30","id":53541,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1085:1:68","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":53540,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1077:7:68","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":53539,"name":"address","nodeType":"ElementaryTypeName","src":"1077:7:68","typeDescriptions":{}}},"id":53542,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1077:10:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":53543,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53531,"src":"1089:6:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"}],"id":53538,"name":"OwnerUpdated","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53515,"src":"1064:12:68","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_address_$returns$__$","typeString":"function (address,address)"}},"id":53544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1064:32:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53545,"nodeType":"EmitStatement","src":"1059:37:68"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":53532,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53531,"mutability":"mutable","name":"_owner","nameLocation":"1016:6:68","nodeType":"VariableDeclaration","scope":53547,"src":"1008:14:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53530,"name":"address","nodeType":"ElementaryTypeName","src":"1008:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1007:16:68"},"returnParameters":{"id":53533,"nodeType":"ParameterList","parameters":[],"src":"1024:0:68"},"scope":53566,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"},{"id":53565,"nodeType":"FunctionDefinition","src":"1293:144:68","nodes":[],"body":{"id":53564,"nodeType":"Block","src":"1354:83:68","nodes":[],"statements":[{"expression":{"id":53556,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":53554,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"1364:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":53555,"name":"newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53549,"src":"1372:8:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1364:16:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":53557,"nodeType":"ExpressionStatement","src":"1364:16:68"},{"eventCall":{"arguments":[{"expression":{"id":53559,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"1409:3:68","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":53560,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"1409:10:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":53561,"name":"newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53549,"src":"1421:8:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"}],"id":53558,"name":"OwnerUpdated","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53515,"src":"1396:12:68","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_address_$returns$__$","typeString":"function (address,address)"}},"id":53562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1396:34:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53563,"nodeType":"EmitStatement","src":"1391:39:68"}]},"functionSelector":"13af4035","implemented":true,"kind":"function","modifiers":[{"id":53552,"kind":"modifierInvocation","modifierName":{"id":53551,"name":"onlyOwner","nodeType":"IdentifierPath","referencedDeclaration":53529,"src":"1344:9:68"},"nodeType":"ModifierInvocation","src":"1344:9:68"}],"name":"setOwner","nameLocation":"1302:8:68","parameters":{"id":53550,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53549,"mutability":"mutable","name":"newOwner","nameLocation":"1319:8:68","nodeType":"VariableDeclaration","scope":53565,"src":"1311:16:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53548,"name":"address","nodeType":"ElementaryTypeName","src":"1311:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1310:18:68"},"returnParameters":{"id":53553,"nodeType":"ParameterList","parameters":[],"src":"1354:0:68"},"scope":53566,"stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"abstract":true,"baseContracts":[],"canonicalName":"Owned","contractDependencies":[],"contractKind":"contract","documentation":{"id":53509,"nodeType":"StructuredDocumentation","src":"68:144:68","text":"@notice Simple single owner authorization mixin.\n @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)"},"fullyImplemented":true,"linearizedBaseContracts":[53566],"name":"Owned","nameLocation":"230:5:68","scope":53567,"usedErrors":[]}],"license":"AGPL-3.0-only"},"id":68} +{"abi":[{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"setOwner","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnerUpdated","inputs":[{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"owner()":"8da5cb5b","setOwner(address)":"13af4035"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerUpdated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Simple single owner authorization mixin.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/solmate/src/auth/Owned.sol\":\"Owned\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[\":@lib-keccak/=lib/lib-keccak/contracts/lib/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":@rari-capital/solmate/=lib/solmate/\",\":@solady-test/=lib/lib-keccak/lib/solady/test/\",\":@solady/=lib/solady/src/\",\":automate/=lib/automate/contracts/\",\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/automate/lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":gelato/=lib/automate/contracts/\",\":hardhat/=lib/automate/node_modules/hardhat/\",\":kontrol-cheatcodes/=lib/kontrol-cheatcodes/src/\",\":lib-keccak/=lib/lib-keccak/contracts/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":prb-test/=lib/automate/lib/prb-test/src/\",\":prb/-est/=lib/automate/lib/prb-test/src/\",\":safe-contracts/=lib/safe-contracts/contracts/\",\":solady/=lib/solady/\",\":solmate/=lib/solmate/src/\"]},\"sources\":{\"lib/solmate/src/auth/Owned.sol\":{\"content\":\"\",\"keccak256\":\"0x7e91c80b0dd1a14a19cb9e661b99924043adab6d9d893bbfcf3a6a3dc23a6743\",\"license\":\"AGPL-3.0-only\",\"urls\":[\"bzz-raw://515890d9fc87d6762dae2354a3a0714a26c652f0ea5bb631122be1968ef8c0e9\",\"dweb:/ipfs/QmTRpQ7uoAR1vCACKJm14Ba3oKVLqcA9reTwbHAPxawVpM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"user","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnerUpdated","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"setOwner"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@lib-keccak/=lib/lib-keccak/contracts/lib/","@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","@rari-capital/solmate/=lib/solmate/","@solady-test/=lib/lib-keccak/lib/solady/test/","@solady/=lib/solady/src/","automate/=lib/automate/contracts/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/automate/lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","gelato/=lib/automate/contracts/","hardhat/=lib/automate/node_modules/hardhat/","kontrol-cheatcodes/=lib/kontrol-cheatcodes/src/","lib-keccak/=lib/lib-keccak/contracts/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/","prb-test/=lib/automate/lib/prb-test/src/","prb/-est/=lib/automate/lib/prb-test/src/","safe-contracts/=lib/safe-contracts/contracts/","solady/=lib/solady/","solmate/=lib/solmate/src/"],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"lib/solmate/src/auth/Owned.sol":"Owned"},"evmVersion":"london","libraries":{}},"sources":{"lib/solmate/src/auth/Owned.sol":{"content":"","keccak256":"0x7e91c80b0dd1a14a19cb9e661b99924043adab6d9d893bbfcf3a6a3dc23a6743","urls":["bzz-raw://515890d9fc87d6762dae2354a3a0714a26c652f0ea5bb631122be1968ef8c0e9","dweb:/ipfs/QmTRpQ7uoAR1vCACKJm14Ba3oKVLqcA9reTwbHAPxawVpM"],"license":"AGPL-3.0-only"}},"version":1},"storageLayout":{"storage":[{"astId":53517,"contract":"lib/solmate/src/auth/Owned.sol:Owned","label":"owner","offset":0,"slot":"0","type":"t_address"}],"types":{"t_address":{"encoding":"inplace","label":"address","numberOfBytes":"20"}}},"userdoc":{"version":1,"kind":"user","notice":"Simple single owner authorization mixin."},"devdoc":{"version":1,"kind":"dev","author":"Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)"},"ast":{"absolutePath":"lib/solmate/src/auth/Owned.sol","id":53567,"exportedSymbols":{"Owned":[53566]},"nodeType":"SourceUnit","src":"42:1398:68","nodes":[{"id":53508,"nodeType":"PragmaDirective","src":"42:24:68","nodes":[],"literals":["solidity",">=","0.8",".0"]},{"id":53566,"nodeType":"ContractDefinition","src":"212:1227:68","nodes":[{"id":53515,"nodeType":"EventDefinition","src":"421:67:68","nodes":[],"anonymous":false,"eventSelector":"8292fce18fa69edf4db7b94ea2e58241df0ae57f97e0a6c9b29067028bf92d76","name":"OwnerUpdated","nameLocation":"427:12:68","parameters":{"id":53514,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53511,"indexed":true,"mutability":"mutable","name":"user","nameLocation":"456:4:68","nodeType":"VariableDeclaration","scope":53515,"src":"440:20:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53510,"name":"address","nodeType":"ElementaryTypeName","src":"440:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":53513,"indexed":true,"mutability":"mutable","name":"newOwner","nameLocation":"478:8:68","nodeType":"VariableDeclaration","scope":53515,"src":"462:24:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53512,"name":"address","nodeType":"ElementaryTypeName","src":"462:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"439:48:68"}},{"id":53517,"nodeType":"VariableDeclaration","src":"679:20:68","nodes":[],"constant":false,"functionSelector":"8da5cb5b","mutability":"mutable","name":"owner","nameLocation":"694:5:68","scope":53566,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53516,"name":"address","nodeType":"ElementaryTypeName","src":"679:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"public"},{"id":53529,"nodeType":"ModifierDefinition","src":"706:102:68","nodes":[],"body":{"id":53528,"nodeType":"Block","src":"735:73:68","nodes":[],"statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_address","typeString":"address"},"id":53523,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":53520,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"753:3:68","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":53521,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"753:10:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"id":53522,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"767:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"753:19:68","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"554e415554484f52495a4544","id":53524,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"774:14:68","typeDescriptions":{"typeIdentifier":"t_stringliteral_269df367cd41cace5897a935d0e0858fe4543b5619d45e09af6b124c1bb3d528","typeString":"literal_string \"UNAUTHORIZED\""},"value":"UNAUTHORIZED"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_269df367cd41cace5897a935d0e0858fe4543b5619d45e09af6b124c1bb3d528","typeString":"literal_string \"UNAUTHORIZED\""}],"id":53519,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"745:7:68","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":53525,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"745:44:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53526,"nodeType":"ExpressionStatement","src":"745:44:68"},{"id":53527,"nodeType":"PlaceholderStatement","src":"800:1:68"}]},"name":"onlyOwner","nameLocation":"715:9:68","parameters":{"id":53518,"nodeType":"ParameterList","parameters":[],"src":"724:2:68"},"virtual":true,"visibility":"internal"},{"id":53547,"nodeType":"FunctionDefinition","src":"996:107:68","nodes":[],"body":{"id":53546,"nodeType":"Block","src":"1024:79:68","nodes":[],"statements":[{"expression":{"id":53536,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":53534,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"1034:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":53535,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53531,"src":"1042:6:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1034:14:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":53537,"nodeType":"ExpressionStatement","src":"1034:14:68"},{"eventCall":{"arguments":[{"arguments":[{"hexValue":"30","id":53541,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1085:1:68","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":53540,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1077:7:68","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":53539,"name":"address","nodeType":"ElementaryTypeName","src":"1077:7:68","typeDescriptions":{}}},"id":53542,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1077:10:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":53543,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53531,"src":"1089:6:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"}],"id":53538,"name":"OwnerUpdated","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53515,"src":"1064:12:68","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_address_$returns$__$","typeString":"function (address,address)"}},"id":53544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1064:32:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53545,"nodeType":"EmitStatement","src":"1059:37:68"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":53532,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53531,"mutability":"mutable","name":"_owner","nameLocation":"1016:6:68","nodeType":"VariableDeclaration","scope":53547,"src":"1008:14:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53530,"name":"address","nodeType":"ElementaryTypeName","src":"1008:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1007:16:68"},"returnParameters":{"id":53533,"nodeType":"ParameterList","parameters":[],"src":"1024:0:68"},"scope":53566,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"},{"id":53565,"nodeType":"FunctionDefinition","src":"1293:144:68","nodes":[],"body":{"id":53564,"nodeType":"Block","src":"1354:83:68","nodes":[],"statements":[{"expression":{"id":53556,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":53554,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"1364:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":53555,"name":"newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53549,"src":"1372:8:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1364:16:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":53557,"nodeType":"ExpressionStatement","src":"1364:16:68"},{"eventCall":{"arguments":[{"expression":{"id":53559,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"1409:3:68","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":53560,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"1409:10:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":53561,"name":"newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53549,"src":"1421:8:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"}],"id":53558,"name":"OwnerUpdated","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53515,"src":"1396:12:68","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_address_$returns$__$","typeString":"function (address,address)"}},"id":53562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1396:34:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53563,"nodeType":"EmitStatement","src":"1391:39:68"}]},"functionSelector":"13af4035","implemented":true,"kind":"function","modifiers":[{"id":53552,"kind":"modifierInvocation","modifierName":{"id":53551,"name":"onlyOwner","nodeType":"IdentifierPath","referencedDeclaration":53529,"src":"1344:9:68"},"nodeType":"ModifierInvocation","src":"1344:9:68"}],"name":"setOwner","nameLocation":"1302:8:68","parameters":{"id":53550,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53549,"mutability":"mutable","name":"newOwner","nameLocation":"1319:8:68","nodeType":"VariableDeclaration","scope":53565,"src":"1311:16:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53548,"name":"address","nodeType":"ElementaryTypeName","src":"1311:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1310:18:68"},"returnParameters":{"id":53553,"nodeType":"ParameterList","parameters":[],"src":"1354:0:68"},"scope":53566,"stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"abstract":true,"baseContracts":[],"canonicalName":"Owned","contractDependencies":[],"contractKind":"contract","documentation":{"id":53509,"nodeType":"StructuredDocumentation","src":"68:144:68","text":"@notice Simple single owner authorization mixin.\n @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)"},"fullyImplemented":true,"linearizedBaseContracts":[53566],"name":"Owned","nameLocation":"230:5:68","scope":53567,"usedErrors":[]}],"license":"AGPL-3.0-only"},"id":68} From c6af537f7efa9210b15285a7641fd31eedf51535 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 6 Mar 2025 10:37:42 -0500 Subject: [PATCH 22/26] add etherscan unit tests --- op-deployer/pkg/deployer/verify/etherscan.go | 11 +- .../pkg/deployer/verify/etherscan_test.go | 274 ++++++++++++++++++ op-deployer/pkg/deployer/verify/verifier.go | 4 +- 3 files changed, 281 insertions(+), 8 deletions(-) create mode 100644 op-deployer/pkg/deployer/verify/etherscan_test.go diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go index 2fce3f778f7d7..15578a413a2a9 100644 --- a/op-deployer/pkg/deployer/verify/etherscan.go +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -34,14 +34,14 @@ type EtherscanClient struct { rateLimiter *rate.Limiter } -func getAPIEndpoint(l1ChainID uint64) string { +func getAPIEndpoint(l1ChainID uint64) (string, error) { switch l1ChainID { case 1: - return "https://api.etherscan.io/api" // mainnet + return "https://api.etherscan.io/api", nil // mainnet case 11155111: - return "https://api-sepolia.etherscan.io/api" // sepolia + return "https://api-sepolia.etherscan.io/api", nil // sepolia default: - return "" + return "", fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) } } @@ -169,8 +169,6 @@ func (c *EtherscanClient) pollVerificationStatus(reqId string) error { } for i := 0; i < 10; i++ { // Try 10 times with increasing delays - time.Sleep(time.Duration(i+2) * time.Second) - resp, err := c.sendRateLimitedRequest(req) if err != nil { return fmt.Errorf("failed to send checkverifystatus request: %w", err) @@ -191,6 +189,7 @@ func (c *EtherscanClient) pollVerificationStatus(reqId string) error { if result.Result != "Pending in queue" { return fmt.Errorf("verification failed: %s, %s", result.Result, result.Message) } + time.Sleep(time.Duration(i+2) * time.Second) } return fmt.Errorf("verification timed out") } diff --git a/op-deployer/pkg/deployer/verify/etherscan_test.go b/op-deployer/pkg/deployer/verify/etherscan_test.go new file mode 100644 index 0000000000000..41d568d773127 --- /dev/null +++ b/op-deployer/pkg/deployer/verify/etherscan_test.go @@ -0,0 +1,274 @@ +package verify + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/time/rate" +) + +const ( + testAPIKey = "test_api_key" + testAddressHex = "0x1234567890123456789012345678901234567890" + testTxHashHex = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + testGUID = "verification_guid_12345" +) + +// createTestClient creates a new EtherscanClient with a mock server for testing +func createTestClient(t *testing.T, handler http.HandlerFunc) (*EtherscanClient, *httptest.Server) { + server := httptest.NewServer(handler) + t.Cleanup(func() { server.Close() }) + + // Use a fast rate limiter for testing + limiter := rate.NewLimiter(rate.Every(time.Millisecond), 10) + return NewEtherscanClient(testAPIKey, server.URL, limiter), server +} + +// createTestArtifact creates a contract artifact for testing +func createTestArtifact() *contractArtifact { + return &contractArtifact{ + ContractName: "TestContract", + CompilerVersion: "0.8.10", + EVMVersion: "london", + Optimizer: OptimizerSettings{ + Enabled: true, + Runs: 200, + }, + Sources: map[string]SourceContent{ + "TestContract.sol": {Content: "contract TestContract {}"}, + }, + } +} + +func TestGetAPIEndpoint(t *testing.T) { + tests := []struct { + name string + chainID uint64 + expected string + expectedError bool + }{ + {"Mainnet", 1, "https://api.etherscan.io/api", false}, + {"Sepolia", 11155111, "https://api-sepolia.etherscan.io/api", false}, + {"Unknown", 999, "", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := getAPIEndpoint(tt.chainID) + if tt.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, result) + } + }) + } +} + +func TestGetContractCreation(t *testing.T) { + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method) + assert.Contains(t, r.URL.String(), "module=contract") + assert.Contains(t, r.URL.String(), "action=getcontractcreation") + + testAddr := common.HexToAddress(testAddressHex) + assert.Contains(t, r.URL.String(), testAddr.Hex()) + + resp := EtherscanContractCreationResp{ + Status: "1", + Message: "OK", + Result: []struct { + ContractCreator string `json:"contractCreator"` + TxHash string `json:"txHash"` + }{ + { + ContractCreator: "0xabcdef1234567890abcdef1234567890abcdef12", + TxHash: testTxHashHex, + }, + }, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + }) + + testAddr := common.HexToAddress(testAddressHex) + txHash, err := client.getContractCreation(testAddr) + + require.NoError(t, err) + assert.Equal(t, testTxHashHex, txHash.Hex()) +} + +func TestGetContractCreationError(t *testing.T) { + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + resp := EtherscanContractCreationResp{ + Status: "0", + Message: "Error", + Result: nil, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + }) + + testAddr := common.HexToAddress(testAddressHex) + _, err := client.getContractCreation(testAddr) + + require.Error(t, err) + assert.Contains(t, err.Error(), "contract creation query failed") +} + +func TestVerifySourceCode(t *testing.T) { + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type")) + + err := r.ParseForm() + require.NoError(t, err) + + assert.Equal(t, testAPIKey, r.Form.Get("apikey")) + assert.Equal(t, "contract", r.Form.Get("module")) + assert.Equal(t, "verifysourcecode", r.Form.Get("action")) + assert.Equal(t, testAddressHex, r.Form.Get("contractaddress")) + + resp := EtherscanGenericResp{ + Status: "1", + Message: "OK", + Result: testGUID, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + }) + + testAddr := common.HexToAddress(testAddressHex) + artifact := createTestArtifact() + + guid, err := client.verifySourceCode(testAddr, artifact, "0x1234") + + require.NoError(t, err) + assert.Equal(t, testGUID, guid) +} + +func TestIsVerified(t *testing.T) { + tests := []struct { + name string + responseStatus string + expected bool + }{ + {"Verified", "1", true}, + {"Not Verified", "0", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method) + assert.Contains(t, r.URL.String(), "module=contract") + assert.Contains(t, r.URL.String(), "action=getabi") + + resp := EtherscanGenericResp{ + Status: tt.responseStatus, + Message: "OK", + Result: "test result", + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + }) + + testAddr := common.HexToAddress(testAddressHex) + result, err := client.isVerified(testAddr) + + require.NoError(t, err) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestPollVerificationStatus(t *testing.T) { + tests := []struct { + name string + responses []EtherscanGenericResp + expectError bool + errorMessage string + }{ + { + name: "Success After Pending", + responses: []EtherscanGenericResp{ + {Status: "0", Message: "OK", Result: "Pending in queue"}, + {Status: "1", Message: "OK", Result: "Pass - Verified"}, + }, + expectError: false, + }, + { + name: "Already Verified", + responses: []EtherscanGenericResp{ + {Status: "0", Message: "OK", Result: "Already Verified"}, + }, + expectError: false, + }, + { + name: "Verification Failed", + responses: []EtherscanGenericResp{ + {Status: "0", Message: "OK", Result: "Fail - Unable to verify"}, + }, + expectError: true, + errorMessage: "verification failed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + responseIndex := 0 + + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method) + assert.Contains(t, r.URL.String(), "module=contract") + assert.Contains(t, r.URL.String(), "action=checkverifystatus") + + // Get the appropriate response based on the current index + resp := tt.responses[responseIndex] + if responseIndex < len(tt.responses)-1 { + responseIndex++ + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + }) + + err := client.pollVerificationStatus("test_guid") + + if tt.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorMessage) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestNewStandardInput(t *testing.T) { + artifact := createTestArtifact() + + result := newStandardInput(artifact) + + assert.Equal(t, "Solidity", result.Language) + assert.Equal(t, artifact.Sources, result.Sources) + assert.Equal(t, artifact.Optimizer.Enabled, result.Settings.Optimizer.Enabled) + assert.Equal(t, artifact.Optimizer.Runs, result.Settings.Optimizer.Runs) + assert.Equal(t, artifact.EVMVersion, result.Settings.EVMVersion) + assert.True(t, result.Settings.Metadata.UseLiteralContent) + assert.Equal(t, "none", result.Settings.Metadata.BytecodeHash) + + assert.Contains(t, result.Settings.OutputSelection.All["*"].All, "abi") + assert.Contains(t, result.Settings.OutputSelection.All["*"].All, "evm.bytecode.object") + assert.Contains(t, result.Settings.OutputSelection.All["*"].All, "metadata") +} diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go index 68fab735928ae..816ce6df292ca 100644 --- a/op-deployer/pkg/deployer/verify/verifier.go +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -31,8 +31,8 @@ type Verifier struct { } func NewVerifier(apiKey string, l1ChainID uint64, artifactsFS foundry.StatDirFs, l log.Logger, l1Client *ethclient.Client) (*Verifier, error) { - etherscanUrl := getAPIEndpoint(l1ChainID) - if etherscanUrl == "" { + etherscanUrl, err := getAPIEndpoint(l1ChainID) + if err != nil { return nil, fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) } From 9ecb23d48f0affe605941c2f7c2c68cb02ef6475 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 6 Mar 2025 10:59:24 -0500 Subject: [PATCH 23/26] fix go lint errors --- op-deployer/pkg/deployer/verify/etherscan_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/etherscan_test.go b/op-deployer/pkg/deployer/verify/etherscan_test.go index 41d568d773127..e006346e7ae6d 100644 --- a/op-deployer/pkg/deployer/verify/etherscan_test.go +++ b/op-deployer/pkg/deployer/verify/etherscan_test.go @@ -95,7 +95,8 @@ func TestGetContractCreation(t *testing.T) { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) + err := json.NewEncoder(w).Encode(resp) + require.NoError(t, err) }) testAddr := common.HexToAddress(testAddressHex) @@ -114,7 +115,8 @@ func TestGetContractCreationError(t *testing.T) { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) + err := json.NewEncoder(w).Encode(resp) + require.NoError(t, err) }) testAddr := common.HexToAddress(testAddressHex) @@ -144,7 +146,8 @@ func TestVerifySourceCode(t *testing.T) { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) + err = json.NewEncoder(w).Encode(resp) + require.NoError(t, err) }) testAddr := common.HexToAddress(testAddressHex) From 524968b72139531ef6bc8f77f2eadda8cf4af343 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 6 Mar 2025 12:06:32 -0500 Subject: [PATCH 24/26] make artifact lookup compatible with fragmented opcm --- op-deployer/pkg/deployer/verify/artifacts.go | 27 +++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go index 200dcac323476..7e84f2025b26a 100644 --- a/op-deployer/pkg/deployer/verify/artifacts.go +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -19,20 +19,24 @@ type contractArtifact struct { ConstructorArgs abi.Arguments } -// Map state.json struct fields to forge artifact names +// Map state.json struct fields to forge artifact paths var contractNameExceptions = map[string]string{ - "OptimismPortalImpl": "OptimismPortal2", - "L1StandardBridgeProxy": "L1ChugSplashProxy", - "L1CrossDomainMessengerProxy": "ResolvedDelegateProxy", - "Opcm": "OPContractsManager", + "OptimismPortalImpl": "OptimismPortal2.sol/OptimismPortal2.json", + "L1StandardBridgeProxy": "L1ChugSplashProxy.sol/L1ChugSplashProxy.json", + "L1CrossDomainMessengerProxy": "ResolvedDelegateProxy.sol/ResolvedDelegateProxy.json", + "Opcm": "OPContractsManager.sol/OPContractsManager.json", + "OpcmContractsContainer": "OPContractsManager.sol/OPContractsManagerContractsContainer.json", + "OpcmGameTypeAdder": "OPContractsManager.sol/OPContractsManagerGameTypeAdder.json", + "OpcmDeployer": "OPContractsManager.sol/OPContractsManagerDeployer.json", + "OpcmUpgrader": "OPContractsManager.sol/OPContractsManagerUpgrader.json", } -func getArtifactName(name string) string { +func getArtifactPath(name string) string { lookupName := strings.TrimSuffix(name, "Address") lookupName = strings.ToUpper(string(lookupName[0])) + lookupName[1:] - if artifactName, exists := contractNameExceptions[lookupName]; exists { - return artifactName + if artifactPath, exists := contractNameExceptions[lookupName]; exists { + return artifactPath } lookupName = strings.TrimSuffix(lookupName, "Proxy") @@ -41,15 +45,14 @@ func getArtifactName(name string) string { // If it was a proxy and not a special case, return "Proxy" if strings.HasSuffix(name, "ProxyAddress") { - return "Proxy" + return path.Join("Proxy.sol", "Proxy.json") } - return lookupName + return path.Join(lookupName+".sol", lookupName+".json") } func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { - artifactName := getArtifactName(name) - artifactPath := path.Join(artifactName+".sol", artifactName+".json") + artifactPath := getArtifactPath(name) v.log.Info("Opening artifact", "path", artifactPath, "name", name) f, err := v.artifactsFS.Open(artifactPath) From f3968f008c23a2b021027b2aee250ed12afe8abc Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Thu, 6 Mar 2025 12:09:50 -0500 Subject: [PATCH 25/26] fix go lint errors --- op-deployer/pkg/deployer/verify/etherscan_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/etherscan_test.go b/op-deployer/pkg/deployer/verify/etherscan_test.go index e006346e7ae6d..5b90b1f67a59c 100644 --- a/op-deployer/pkg/deployer/verify/etherscan_test.go +++ b/op-deployer/pkg/deployer/verify/etherscan_test.go @@ -183,7 +183,8 @@ func TestIsVerified(t *testing.T) { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) + err := json.NewEncoder(w).Encode(resp) + require.NoError(t, err) }) testAddr := common.HexToAddress(testAddressHex) @@ -243,7 +244,8 @@ func TestPollVerificationStatus(t *testing.T) { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) + err := json.NewEncoder(w).Encode(resp) + require.NoError(t, err) }) err := client.pollVerificationStatus("test_guid") From a4533d200fb35d0cedd83d4899b0664132cc88b2 Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Fri, 7 Mar 2025 14:31:23 -0500 Subject: [PATCH 26/26] use require instead of assert in tests --- .../pkg/deployer/verify/etherscan_test.go | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/etherscan_test.go b/op-deployer/pkg/deployer/verify/etherscan_test.go index 5b90b1f67a59c..8bd0fe16bf78b 100644 --- a/op-deployer/pkg/deployer/verify/etherscan_test.go +++ b/op-deployer/pkg/deployer/verify/etherscan_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/time/rate" ) @@ -62,10 +61,10 @@ func TestGetAPIEndpoint(t *testing.T) { t.Run(tt.name, func(t *testing.T) { result, err := getAPIEndpoint(tt.chainID) if tt.expectedError { - assert.Error(t, err) + require.Error(t, err) } else { - assert.NoError(t, err) - assert.Equal(t, tt.expected, result) + require.NoError(t, err) + require.Equal(t, tt.expected, result) } }) } @@ -73,12 +72,12 @@ func TestGetAPIEndpoint(t *testing.T) { func TestGetContractCreation(t *testing.T) { client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "GET", r.Method) - assert.Contains(t, r.URL.String(), "module=contract") - assert.Contains(t, r.URL.String(), "action=getcontractcreation") + require.Equal(t, "GET", r.Method) + require.Contains(t, r.URL.String(), "module=contract") + require.Contains(t, r.URL.String(), "action=getcontractcreation") testAddr := common.HexToAddress(testAddressHex) - assert.Contains(t, r.URL.String(), testAddr.Hex()) + require.Contains(t, r.URL.String(), testAddr.Hex()) resp := EtherscanContractCreationResp{ Status: "1", @@ -103,7 +102,7 @@ func TestGetContractCreation(t *testing.T) { txHash, err := client.getContractCreation(testAddr) require.NoError(t, err) - assert.Equal(t, testTxHashHex, txHash.Hex()) + require.Equal(t, testTxHashHex, txHash.Hex()) } func TestGetContractCreationError(t *testing.T) { @@ -123,21 +122,21 @@ func TestGetContractCreationError(t *testing.T) { _, err := client.getContractCreation(testAddr) require.Error(t, err) - assert.Contains(t, err.Error(), "contract creation query failed") + require.Contains(t, err.Error(), "contract creation query failed") } func TestVerifySourceCode(t *testing.T) { client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "POST", r.Method) - assert.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type")) + require.Equal(t, "POST", r.Method) + require.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type")) err := r.ParseForm() require.NoError(t, err) - assert.Equal(t, testAPIKey, r.Form.Get("apikey")) - assert.Equal(t, "contract", r.Form.Get("module")) - assert.Equal(t, "verifysourcecode", r.Form.Get("action")) - assert.Equal(t, testAddressHex, r.Form.Get("contractaddress")) + require.Equal(t, testAPIKey, r.Form.Get("apikey")) + require.Equal(t, "contract", r.Form.Get("module")) + require.Equal(t, "verifysourcecode", r.Form.Get("action")) + require.Equal(t, testAddressHex, r.Form.Get("contractaddress")) resp := EtherscanGenericResp{ Status: "1", @@ -156,7 +155,7 @@ func TestVerifySourceCode(t *testing.T) { guid, err := client.verifySourceCode(testAddr, artifact, "0x1234") require.NoError(t, err) - assert.Equal(t, testGUID, guid) + require.Equal(t, testGUID, guid) } func TestIsVerified(t *testing.T) { @@ -172,9 +171,9 @@ func TestIsVerified(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "GET", r.Method) - assert.Contains(t, r.URL.String(), "module=contract") - assert.Contains(t, r.URL.String(), "action=getabi") + require.Equal(t, "GET", r.Method) + require.Contains(t, r.URL.String(), "module=contract") + require.Contains(t, r.URL.String(), "action=getabi") resp := EtherscanGenericResp{ Status: tt.responseStatus, @@ -191,7 +190,7 @@ func TestIsVerified(t *testing.T) { result, err := client.isVerified(testAddr) require.NoError(t, err) - assert.Equal(t, tt.expected, result) + require.Equal(t, tt.expected, result) }) } } @@ -233,9 +232,9 @@ func TestPollVerificationStatus(t *testing.T) { responseIndex := 0 client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "GET", r.Method) - assert.Contains(t, r.URL.String(), "module=contract") - assert.Contains(t, r.URL.String(), "action=checkverifystatus") + require.Equal(t, "GET", r.Method) + require.Contains(t, r.URL.String(), "module=contract") + require.Contains(t, r.URL.String(), "action=checkverifystatus") // Get the appropriate response based on the current index resp := tt.responses[responseIndex] @@ -252,7 +251,7 @@ func TestPollVerificationStatus(t *testing.T) { if tt.expectError { require.Error(t, err) - assert.Contains(t, err.Error(), tt.errorMessage) + require.Contains(t, err.Error(), tt.errorMessage) } else { require.NoError(t, err) } @@ -265,15 +264,15 @@ func TestNewStandardInput(t *testing.T) { result := newStandardInput(artifact) - assert.Equal(t, "Solidity", result.Language) - assert.Equal(t, artifact.Sources, result.Sources) - assert.Equal(t, artifact.Optimizer.Enabled, result.Settings.Optimizer.Enabled) - assert.Equal(t, artifact.Optimizer.Runs, result.Settings.Optimizer.Runs) - assert.Equal(t, artifact.EVMVersion, result.Settings.EVMVersion) - assert.True(t, result.Settings.Metadata.UseLiteralContent) - assert.Equal(t, "none", result.Settings.Metadata.BytecodeHash) - - assert.Contains(t, result.Settings.OutputSelection.All["*"].All, "abi") - assert.Contains(t, result.Settings.OutputSelection.All["*"].All, "evm.bytecode.object") - assert.Contains(t, result.Settings.OutputSelection.All["*"].All, "metadata") + require.Equal(t, "Solidity", result.Language) + require.Equal(t, artifact.Sources, result.Sources) + require.Equal(t, artifact.Optimizer.Enabled, result.Settings.Optimizer.Enabled) + require.Equal(t, artifact.Optimizer.Runs, result.Settings.Optimizer.Runs) + require.Equal(t, artifact.EVMVersion, result.Settings.EVMVersion) + require.True(t, result.Settings.Metadata.UseLiteralContent) + require.Equal(t, "none", result.Settings.Metadata.BytecodeHash) + + require.Contains(t, result.Settings.OutputSelection.All["*"].All, "abi") + require.Contains(t, result.Settings.OutputSelection.All["*"].All, "evm.bytecode.object") + require.Contains(t, result.Settings.OutputSelection.All["*"].All, "metadata") }