Commit e4536f9e authored by Wyatt Barnes's avatar Wyatt Barnes

BindGen - local contracts refactor

parent 5fe703da
......@@ -3,6 +3,8 @@ SHELL := /usr/bin/env bash
pkg := bindings
monorepo-base := $(shell dirname $(realpath .))
contracts-dir := $(monorepo-base)/packages/contracts-bedrock
contracts-list := ./artifacts.json
log-level := info
all: version mkdir bindings
......@@ -15,16 +17,22 @@ compile:
forge clean && \
pnpm build
bindings: compile bindings-build
bindings: bindgen-local
bindings-build:
go run ./gen/main.go \
-forge-artifacts $(contracts-dir)/forge-artifacts \
-out ./bindings \
-contracts ./artifacts.json \
-source-maps MIPS,PreimageOracle \
-package $(pkg) \
-monorepo-base $(monorepo-base)
bindings-build: bindgen-generate-local
bindgen-local: compile bindgen-generate-local
bindgen-generate-local:
go run ./bindgen/ \
generate \
--metadata-out ./$(pkg) \
--bindings-package $(pkg) \
--contracts-list $(contracts-list) \
--log.level $(log-level) \
local \
--source-maps-list MIPS,PreimageOracle \
--forge-artifacts $(contracts-dir)/forge-artifacts
mkdir:
mkdir -p $(pkg)
......
[
"SystemConfig",
"L1CrossDomainMessenger",
"L1StandardBridge",
"OptimismPortal",
"L2OutputOracle",
"AddressManager",
"L1Block",
"L2ToL1MessagePasser",
"GasPriceOracle",
"L2CrossDomainMessenger",
"L2StandardBridge",
"L2ERC721Bridge",
"L1ERC721Bridge",
"OptimismMintableERC721Factory",
"SequencerFeeVault",
"BaseFeeVault",
"L1FeeVault",
"OptimismMintableERC20Factory",
"OptimismMintableERC20",
"LegacyERC20ETH",
"Proxy",
"ProxyAdmin",
"LegacyMessagePasser",
"ERC20",
"WETH9",
"DeployerWhitelist",
"L1BlockNumber",
"DisputeGameFactory",
"FaultDisputeGame",
"OutputBisectionGame",
"AlphabetVM",
"AlphabetVM2",
"StandardBridge",
"CrossDomainMessenger",
"MIPS",
"PreimageOracle",
"BlockOracle",
"EAS",
"SchemaRegistry",
"ProtocolVersions",
"Safe",
"SafeProxyFactory",
"DelayedVetoable",
"ISemver",
"StorageSetter",
"SuperchainConfig"
]
{
"local": [
"SystemConfig",
"L1CrossDomainMessenger",
"L1StandardBridge",
"OptimismPortal",
"L2OutputOracle",
"AddressManager",
"L1Block",
"L2ToL1MessagePasser",
"GasPriceOracle",
"L2CrossDomainMessenger",
"L2StandardBridge",
"L2ERC721Bridge",
"L1ERC721Bridge",
"OptimismMintableERC721Factory",
"SequencerFeeVault",
"BaseFeeVault",
"L1FeeVault",
"OptimismMintableERC20Factory",
"OptimismMintableERC20",
"LegacyERC20ETH",
"Proxy",
"ProxyAdmin",
"LegacyMessagePasser",
"ERC20",
"WETH9",
"DeployerWhitelist",
"L1BlockNumber",
"DisputeGameFactory",
"FaultDisputeGame",
"OutputBisectionGame",
"AlphabetVM",
"AlphabetVM2",
"StandardBridge",
"CrossDomainMessenger",
"MIPS",
"PreimageOracle",
"BlockOracle",
"EAS",
"SchemaRegistry",
"ProtocolVersions",
"Safe",
"SafeProxyFactory",
"DelayedVetoable",
"ISemver",
"StorageSetter",
"SuperchainConfig"
]
}
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/ethereum-optimism/optimism/op-bindings/ast"
"github.com/ethereum-optimism/optimism/op-bindings/foundry"
)
type bindGenGeneratorLocal struct {
bindGenGeneratorBase
sourceMapsList string
forgeArtifactsPath string
}
type localContractMetadata struct {
Name string
StorageLayout string
DeployedBin string
Package string
DeployedSourceMap string
HasImmutableReferences bool
}
func (generator *bindGenGeneratorLocal) generateBindings() error {
contracts, err := readContractList(generator.logger, generator.contractsListPath)
if err != nil {
return fmt.Errorf("error reading contract list %s: %w", generator.contractsListPath, err)
}
if len(contracts.Local) == 0 {
return fmt.Errorf("no contracts parsed from given contract list: %s", generator.contractsListPath)
}
return generator.processContracts(contracts.Local)
}
func (generator *bindGenGeneratorLocal) processContracts(contracts []string) error {
tempArtifactsDir, err := mkTempArtifactsDir(generator.logger)
if err != nil {
return err
}
defer func() {
err := os.RemoveAll(tempArtifactsDir)
if err != nil {
generator.logger.Error("Error removing temporary artifact directory", "path", tempArtifactsDir, "err", err.Error())
} else {
generator.logger.Debug("Successfully removed temporary artifact directory")
}
}()
sourceMapsList := strings.Split(generator.sourceMapsList, ",")
sourceMapsSet := make(map[string]struct{})
for _, k := range sourceMapsList {
sourceMapsSet[k] = struct{}{}
}
contractArtifactPaths, err := generator.getContractArtifactPaths()
if err != nil {
return err
}
contractMetadataFileTemplate := template.Must(template.New("localContractMetadata").Parse(localContractMetadataTemplate))
for _, contractName := range contracts {
generator.logger.Info("Generating bindings and metadata for local contract", "contract", contractName)
forgeArtifact, err := generator.readForgeArtifact(contractName, contractArtifactPaths)
if err != nil {
return err
}
abiFilePath, bytecodeFilePath, err := writeContractArtifacts(generator.logger, tempArtifactsDir, contractName, forgeArtifact.Abi, []byte(forgeArtifact.Bytecode.Object.String()))
if err != nil {
return err
}
err = genContractBindings(generator.logger, abiFilePath, bytecodeFilePath, generator.bindingsPackageName, contractName)
if err != nil {
return err
}
deployedSourceMap, canonicalStorageStr, err := generator.canonicalizeStorageLayout(forgeArtifact, sourceMapsSet, contractName)
if err != nil {
return err
}
re := regexp.MustCompile(`\s+`)
immutableRefs, err := json.Marshal(re.ReplaceAllString(string(forgeArtifact.DeployedBytecode.ImmutableReferences), ""))
if err != nil {
return fmt.Errorf("error marshaling immutable references: %w", err)
}
hasImmutables := string(immutableRefs) != `""`
contractMetaData := localContractMetadata{
Name: contractName,
StorageLayout: canonicalStorageStr,
DeployedBin: forgeArtifact.DeployedBytecode.Object.String(),
Package: generator.bindingsPackageName,
DeployedSourceMap: deployedSourceMap,
HasImmutableReferences: hasImmutables,
}
if err := generator.writeContractMetadata(contractMetaData, contractName, contractMetadataFileTemplate); err != nil {
return err
}
}
return nil
}
func (generator *bindGenGeneratorLocal) getContractArtifactPaths() (map[string]string, error) {
// If some contracts have the same name then the path to their
// artifact depends on their full import path. Scan over all artifacts
// and hold a mapping from the contract name to the contract path.
// Walk walks the directory deterministically, so the earliest instance
// of the contract with the same name will be used
artifactPaths := make(map[string]string)
if err := filepath.Walk(generator.forgeArtifactsPath,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, ".json") {
base := filepath.Base(path)
name := strings.TrimSuffix(base, ".json")
// remove the compiler version from the name
re := regexp.MustCompile(`\.\d+\.\d+\.\d+`)
sanitized := re.ReplaceAllString(name, "")
_, ok := artifactPaths[sanitized]
if !ok {
artifactPaths[sanitized] = path
} else {
generator.logger.Warn("Multiple versions of forge artifacts exist, using lesser version", "contract", sanitized)
}
}
return nil
}); err != nil {
return artifactPaths, err
}
return artifactPaths, nil
}
func (generator *bindGenGeneratorLocal) readForgeArtifact(contractName string, contractArtifactPaths map[string]string) (foundry.Artifact, error) {
var forgeArtifact foundry.Artifact
contractArtifactPath := path.Join(generator.forgeArtifactsPath, contractName+".sol", contractName+".json")
forgeArtifactRaw, err := os.ReadFile(contractArtifactPath)
if errors.Is(err, os.ErrNotExist) {
generator.logger.Debug("Cannot find forge-artifact at standard path, trying provided path", "contract", contractName, "standardPath", contractArtifactPath, "providedPath", contractArtifactPaths[contractName])
contractArtifactPath = contractArtifactPaths[contractName]
forgeArtifactRaw, err = os.ReadFile(contractArtifactPath)
if errors.Is(err, os.ErrNotExist) {
return forgeArtifact, fmt.Errorf("cannot find forge-artifact of %q", contractName)
}
}
generator.logger.Debug("Using forge-artifact", "path", contractArtifactPath)
if err := json.Unmarshal(forgeArtifactRaw, &forgeArtifact); err != nil {
return forgeArtifact, fmt.Errorf("failed to parse forge artifact of %q: %w", contractName, err)
}
return forgeArtifact, nil
}
func (generator *bindGenGeneratorLocal) canonicalizeStorageLayout(forgeArtifact foundry.Artifact, sourceMapsSet map[string]struct{}, contractName string) (string, string, error) {
artifactStorageStruct := forgeArtifact.StorageLayout
canonicalStorageStruct := ast.CanonicalizeASTIDs(&artifactStorageStruct, generator.monorepoBasePath)
canonicalStorageJson, err := json.Marshal(canonicalStorageStruct)
if err != nil {
return "", "", fmt.Errorf("error marshaling canonical storage: %w", err)
}
canonicalStorageStr := strings.Replace(string(canonicalStorageJson), "\"", "\\\"", -1)
deployedSourceMap := ""
if _, ok := sourceMapsSet[contractName]; ok {
deployedSourceMap = forgeArtifact.DeployedBytecode.SourceMap
}
return deployedSourceMap, canonicalStorageStr, nil
}
func (generator *bindGenGeneratorLocal) writeContractMetadata(contractMetaData localContractMetadata, contractName string, fileTemplate *template.Template) error {
metadataFilePath := filepath.Join(generator.metadataOut, strings.ToLower(contractName)+"_more.go")
metadataFile, err := os.OpenFile(
metadataFilePath,
os.O_RDWR|os.O_CREATE|os.O_TRUNC,
0o600,
)
if err != nil {
return fmt.Errorf("error opening %s's metadata file at %s: %w", contractName, metadataFilePath, err)
}
defer metadataFile.Close()
if err := fileTemplate.Execute(metadataFile, contractMetaData); err != nil {
return fmt.Errorf("error writing %s's contract metadata at %s: %w", contractName, metadataFilePath, err)
}
generator.logger.Debug("Successfully wrote contract metadata", "contract", contractName, "path", metadataFilePath)
return nil
}
// associated with a local Ethereum contract. This template is used to produce
// Go code containing necessary constants and initialization logic for the contract's
// storage layout, deployed bytecode, and optionally its deployed source map.
//
// The template expects the following fields to be provided:
// - Package: The name of the Go package for the generated bindings.
// - Name: The name of the contract.
// - StorageLayout: Canonicalized storage layout of the contract as a JSON string.
// - DeployedBin: The deployed bytecode of the contract.
// - DeployedSourceMap (optional): The source map of the deployed contract.
var localContractMetadataTemplate = `// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package {{.Package}}
import (
"encoding/json"
"github.com/ethereum-optimism/optimism/op-bindings/solc"
)
const {{.Name}}StorageLayoutJSON = "{{.StorageLayout}}"
var {{.Name}}StorageLayout = new(solc.StorageLayout)
var {{.Name}}DeployedBin = "{{.DeployedBin}}"
{{if .DeployedSourceMap}}
var {{.Name}}DeployedSourceMap = "{{.DeployedSourceMap}}"
{{end}}
func init() {
if err := json.Unmarshal([]byte({{.Name}}StorageLayoutJSON), {{.Name}}StorageLayout); err != nil {
panic(err)
}
layouts["{{.Name}}"] = {{.Name}}StorageLayout
deployedBytecodes["{{.Name}}"] = {{.Name}}DeployedBin
immutableReferences["{{.Name}}"] = {{.HasImmutableReferences}}
}
`
package main
import (
"fmt"
"os"
"github.com/ethereum-optimism/optimism/op-e2e/config"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
)
type bindGenGeneratorBase struct {
metadataOut string
bindingsPackageName string
monorepoBasePath string
contractsListPath string
logger log.Logger
}
const (
// Base Flags
MetadataOutFlagName = "metadata-out"
BindingsPackageNameFlagName = "bindings-package"
ContractsListFlagName = "contracts-list"
// Local Contracts Flags
SourceMapsListFlagName = "source-maps-list"
ForgeArtifactsFlagName = "forge-artifacts"
)
func main() {
oplog.SetupDefaults()
app := &cli.App{
Name: "BindGen",
Usage: "Generate contract bindings using Foundry artifacts and/or remotely sourced contract data",
Commands: []*cli.Command{
{
Name: "generate",
Usage: "Generate contract bindings",
Flags: baseFlags(),
Subcommands: []*cli.Command{
{
Name: "local",
Usage: "Generate bindings for locally sourced contracts",
Flags: localFlags(),
Action: generateBindings,
},
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Crit("BindGen error", "error", err.Error())
}
}
func setupLogger(c *cli.Context) log.Logger {
logger := oplog.NewLogger(oplog.AppOut(c), oplog.ReadCLIConfig(c))
oplog.SetGlobalLogHandler(logger.GetHandler())
return logger
}
func generateBindings(c *cli.Context) error {
logger := setupLogger(c)
switch c.Command.Name {
case "local":
localBindingsGenerator, err := parseConfigLocal(logger, c)
if err != nil {
return err
}
if err := localBindingsGenerator.generateBindings(); err != nil {
return fmt.Errorf("error generating local bindings: %w", err)
}
return nil
default:
return fmt.Errorf("unknown command: %s", c.Command.Name)
}
}
func parseConfigBase(logger log.Logger, c *cli.Context) (bindGenGeneratorBase, error) {
cwd, err := os.Getwd()
if err != nil {
return bindGenGeneratorBase{}, err
}
monoRepoPath, err := config.FindMonorepoRoot(cwd)
if err != nil {
return bindGenGeneratorBase{}, err
}
return bindGenGeneratorBase{
metadataOut: c.String(MetadataOutFlagName),
bindingsPackageName: c.String(BindingsPackageNameFlagName),
monorepoBasePath: monoRepoPath,
contractsListPath: c.String(ContractsListFlagName),
logger: logger,
}, nil
}
func parseConfigLocal(logger log.Logger, c *cli.Context) (bindGenGeneratorLocal, error) {
baseConfig, err := parseConfigBase(logger, c)
if err != nil {
return bindGenGeneratorLocal{}, err
}
return bindGenGeneratorLocal{
bindGenGeneratorBase: baseConfig,
sourceMapsList: c.String(SourceMapsListFlagName),
forgeArtifactsPath: c.String(ForgeArtifactsFlagName),
}, nil
}
func baseFlags() []cli.Flag {
baseFlags := []cli.Flag{
&cli.StringFlag{
Name: MetadataOutFlagName,
Usage: "Output directory to put contract metadata files in",
Required: true,
},
&cli.StringFlag{
Name: BindingsPackageNameFlagName,
Usage: "Go package name given to generated bindings",
Required: true,
},
&cli.StringFlag{
Name: ContractsListFlagName,
Usage: "Path to file containing list of contract names to generate bindings for",
Required: true,
},
}
return append(baseFlags, oplog.CLIFlags("bindgen")...)
}
func localFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: SourceMapsListFlagName,
Usage: "Comma-separated list of contracts to generate source-maps for",
},
&cli.StringFlag{
Name: ForgeArtifactsFlagName,
Usage: "Path to forge-artifacts directory, containing compiled contract artifacts",
Required: true,
},
}
}
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path"
"strings"
"github.com/ethereum/go-ethereum/log"
)
type contractsList struct {
Local []string `json:"local"`
}
// readContractList reads a JSON file from the given `filePath` and unmarshals
// its content into the provided result interface. It logs the path of the file
// being read.
//
// Parameters:
// - logger: An instance of go-ethereum/log
// - filePath: The path to the JSON file to be read.
// - result: A pointer to the structure where the JSON data will be unmarshaled.
//
// Returns:
// - An error if reading the file or unmarshaling fails, nil otherwise.
func readContractList(logger log.Logger, filePath string) (contractsList, error) {
logger.Debug("Reading contract list", "filePath", filePath)
var contracts contractsList
contractData, err := os.ReadFile(filePath)
if err != nil {
return contracts, err
}
return contracts, json.Unmarshal(contractData, &contracts)
}
// mkTempArtifactsDir creates a temporary directory with a "op-bindings" prefix
// for holding contract artifacts. The path to the created directory is logged.
//
// Parameters:
// - logger: An instance of go-ethereum/log
//
// Returns:
// - The path to the created temporary directory.
// - An error if the directory creation fails, nil otherwise.
func mkTempArtifactsDir(logger log.Logger) (string, error) {
dir, err := os.MkdirTemp("", "op-bindings")
if err != nil {
return "", err
}
logger.Debug("Created temporary artifacts directory", "dir", dir)
return dir, nil
}
// writeContractArtifacts writes the provided ABI and bytecode data to respective
// files in the specified temporary directory. The naming convention for these
// files is based on the provided contract name. The ABI data is written to a file
// with a ".abi" extension, and the bytecode data is written to a file with a ".bin"
// extension.
//
// Parameters:
// - logger: An instance of go-ethereum/log
// - tempDirPath: The directory path where the ABI and bytecode files will be written.
// - contractName: The name of the contract, used to create the filenames.
// - abi: The ABI data of the contract.
// - bytecode: The bytecode of the contract.
//
// Returns:
// - The full path to the written ABI file.
// - The full path to the written bytecode file.
// - An error if writing either file fails, nil otherwise.
func writeContractArtifacts(logger log.Logger, tempDirPath, contractName string, abi, bytecode []byte) (string, string, error) {
logger.Debug("Writing ABI and bytecode to temporary artifacts directory", "contractName", contractName, "tempDirPath", tempDirPath)
abiFilePath := path.Join(tempDirPath, contractName+".abi")
if err := os.WriteFile(abiFilePath, abi, 0o600); err != nil {
return "", "", fmt.Errorf("error writing %s's ABI file: %w", contractName, err)
}
bytecodeFilePath := path.Join(tempDirPath, contractName+".bin")
if err := os.WriteFile(bytecodeFilePath, bytecode, 0o600); err != nil {
return "", "", fmt.Errorf("error writing %s's bytecode file: %w", contractName, err)
}
return abiFilePath, bytecodeFilePath, nil
}
// genContractBindings generates Go bindings for an Ethereum contract using
// the provided ABI and bytecode files. The bindings are generated using the
// `abigen` tool and are written to the specified Go package directory. The
// generated file's name is based on the provided contract name and will have
// a ".go" extension. The generated bindings will be part of the provided Go
// package.
//
// Parameters:
// - logger: An instance of go-ethereum/log
// - abiFilePath: The path to the ABI file for the contract.
// - bytecodeFilePath: The path to the bytecode file for the contract.
// - goPackageName: The name of the Go package where the bindings will be written.
// - contractName: The name of the contract, used for naming the output file and
// defining the type in the generated bindings.
//
// Returns:
// - An error if there's an issue during any step of the binding generation process,
// nil otherwise.
//
// Note: This function relies on the external `abigen` tool, which should be
// installed and available in the system's PATH.
func genContractBindings(logger log.Logger, abiFilePath, bytecodeFilePath, goPackageName, contractName string) error {
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("error getting cwd: %w", err)
}
outFilePath := path.Join(cwd, goPackageName, strings.ToLower(contractName)+".go")
logger.Debug("Generating contract bindings", "contractName", contractName, "outFilePath", outFilePath)
cmd := exec.Command("abigen", "--abi", abiFilePath, "--bin", bytecodeFilePath, "--pkg", goPackageName, "--type", contractName, "--out", outFilePath)
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return fmt.Errorf("error running abigen for %s: %w", contractName, err)
}
return nil
}
package main
import (
"encoding/json"
"errors"
"flag"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/ethereum-optimism/optimism/op-bindings/ast"
"github.com/ethereum-optimism/optimism/op-bindings/foundry"
)
type flags struct {
ForgeArtifacts string
Contracts string
SourceMaps string
OutDir string
Package string
MonorepoBase string
}
type data struct {
Name string
StorageLayout string
DeployedBin string
Package string
DeployedSourceMap string
HasImmutableReferences bool
}
func main() {
var f flags
flag.StringVar(&f.ForgeArtifacts, "forge-artifacts", "", "Forge artifacts directory, to load sourcemaps from, if available")
flag.StringVar(&f.OutDir, "out", "", "Output directory to put code in")
flag.StringVar(&f.Contracts, "contracts", "artifacts.json", "Path to file containing list of contracts to generate bindings for")
flag.StringVar(&f.SourceMaps, "source-maps", "", "Comma-separated list of contracts to generate source-maps for")
flag.StringVar(&f.Package, "package", "artifacts", "Go package name")
flag.StringVar(&f.MonorepoBase, "monorepo-base", "", "Base of the monorepo")
flag.Parse()
if f.MonorepoBase == "" {
log.Fatal("must provide -monorepo-base")
}
log.Printf("Using monorepo base %s\n", f.MonorepoBase)
contractData, err := os.ReadFile(f.Contracts)
if err != nil {
log.Fatal("error reading contract list: %w\n", err)
}
contracts := []string{}
if err := json.Unmarshal(contractData, &contracts); err != nil {
log.Fatal("error parsing contract list: %w\n", err)
}
sourceMaps := strings.Split(f.SourceMaps, ",")
sourceMapsSet := make(map[string]struct{})
for _, k := range sourceMaps {
sourceMapsSet[k] = struct{}{}
}
if len(contracts) == 0 {
log.Fatalf("must define a list of contracts")
}
t := template.Must(template.New("artifact").Parse(tmpl))
// Make a temp dir to hold all the inputs for abigen
dir, err := os.MkdirTemp("", "op-bindings")
if err != nil {
log.Fatal(err)
}
log.Printf("Using package %s\n", f.Package)
defer os.RemoveAll(dir)
log.Printf("created temp dir %s\n", dir)
// If some contracts have the same name then the path to their
// artifact depends on their full import path. Scan over all artifacts
// and hold a mapping from the contract name to the contract path.
// Walk walks the directory deterministically, so the later instance
// of the contract with the same name will be used
re := regexp.MustCompile(`\.\d+\.\d+\.\d+`)
artifactPaths := make(map[string]string)
if err := filepath.Walk(f.ForgeArtifacts,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, ".json") {
base := filepath.Base(path)
name := strings.TrimSuffix(base, ".json")
// remove the compiler version from the name
sanitized := re.ReplaceAllString(name, "")
if _, ok := artifactPaths[sanitized]; !ok {
artifactPaths[sanitized] = path
}
}
return nil
}); err != nil {
log.Fatal(err)
}
for _, name := range contracts {
log.Printf("generating code for %s\n", name)
artifactPath := path.Join(f.ForgeArtifacts, name+".sol", name+".json")
forgeArtifactData, err := os.ReadFile(artifactPath)
if errors.Is(err, os.ErrNotExist) {
log.Printf("cannot find forge-artifact for %s at standard path %s, trying %s\n", name, artifactPath, artifactPaths[name])
artifactPath = artifactPaths[name]
forgeArtifactData, err = os.ReadFile(artifactPath)
if errors.Is(err, os.ErrNotExist) {
log.Fatalf("cannot find forge-artifact of %q\n", name)
}
}
log.Printf("using forge-artifact %s\n", artifactPath)
var artifact foundry.Artifact
if err := json.Unmarshal(forgeArtifactData, &artifact); err != nil {
log.Fatalf("failed to parse forge artifact of %q: %v\n", name, err)
}
rawAbi := artifact.Abi
if err != nil {
log.Fatalf("error marshaling abi: %v\n", err)
}
abiFile := path.Join(dir, name+".abi")
if err := os.WriteFile(abiFile, rawAbi, 0o600); err != nil {
log.Fatalf("error writing file: %v\n", err)
}
rawBytecode := artifact.Bytecode.Object.String()
if err != nil {
log.Fatalf("error marshaling bytecode: %v\n", err)
}
bytecodeFile := path.Join(dir, name+".bin")
if err := os.WriteFile(bytecodeFile, []byte(rawBytecode), 0o600); err != nil {
log.Fatalf("error writing file: %v\n", err)
}
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("error getting cwd: %v\n", err)
}
lowerName := strings.ToLower(name)
outFile := path.Join(cwd, f.Package, lowerName+".go")
cmd := exec.Command("abigen", "--abi", abiFile, "--bin", bytecodeFile, "--pkg", f.Package, "--type", name, "--out", outFile)
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
log.Fatalf("error running abigen: %v\n", err)
}
storage := artifact.StorageLayout
canonicalStorage := ast.CanonicalizeASTIDs(&storage, f.MonorepoBase)
ser, err := json.Marshal(canonicalStorage)
if err != nil {
log.Fatalf("error marshaling storage: %v\n", err)
}
serStr := strings.Replace(string(ser), "\"", "\\\"", -1)
deployedSourceMap := ""
if _, ok := sourceMapsSet[name]; ok {
deployedSourceMap = artifact.DeployedBytecode.SourceMap
}
re := regexp.MustCompile(`\s+`)
immutableRefs, err := json.Marshal(re.ReplaceAllString(string(artifact.DeployedBytecode.ImmutableReferences), ""))
if err != nil {
log.Fatalf("error marshaling immutable references: %v\n", err)
}
hasImmutables := string(immutableRefs) != `""`
d := data{
Name: name,
StorageLayout: serStr,
DeployedBin: artifact.DeployedBytecode.Object.String(),
Package: f.Package,
DeployedSourceMap: deployedSourceMap,
HasImmutableReferences: hasImmutables,
}
fname := filepath.Join(f.OutDir, strings.ToLower(name)+"_more.go")
outfile, err := os.OpenFile(
fname,
os.O_RDWR|os.O_CREATE|os.O_TRUNC,
0o600,
)
if err != nil {
log.Fatalf("error opening %s: %v\n", fname, err)
}
if err := t.Execute(outfile, d); err != nil {
log.Fatalf("error writing template %s: %v", outfile.Name(), err)
}
outfile.Close()
log.Printf("wrote file %s\n", outfile.Name())
}
}
var tmpl = `// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package {{.Package}}
import (
"encoding/json"
"github.com/ethereum-optimism/optimism/op-bindings/solc"
)
const {{.Name}}StorageLayoutJSON = "{{.StorageLayout}}"
var {{.Name}}StorageLayout = new(solc.StorageLayout)
var {{.Name}}DeployedBin = "{{.DeployedBin}}"
{{if .DeployedSourceMap}}
var {{.Name}}DeployedSourceMap = "{{.DeployedSourceMap}}"
{{end}}
func init() {
if err := json.Unmarshal([]byte({{.Name}}StorageLayoutJSON), {{.Name}}StorageLayout); err != nil {
panic(err)
}
layouts["{{.Name}}"] = {{.Name}}StorageLayout
deployedBytecodes["{{.Name}}"] = {{.Name}}DeployedBin
immutableReferences["{{.Name}}"] = {{.HasImmutableReferences}}
}
`
......@@ -49,7 +49,7 @@ func init() {
if err != nil {
panic(err)
}
root, err := findMonorepoRoot(cwd)
root, err := FindMonorepoRoot(cwd)
if err != nil {
panic(err)
}
......@@ -159,9 +159,9 @@ func allExist(filenames ...string) error {
return nil
}
// findMonorepoRoot will recursively search upwards for a go.mod file.
// FindMonorepoRoot will recursively search upwards for a go.mod file.
// This depends on the structure of the monorepo having a go.mod file at the root.
func findMonorepoRoot(startDir string) (string, error) {
func FindMonorepoRoot(startDir string) (string, error) {
dir, err := filepath.Abs(startDir)
if err != nil {
return "", err
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment