Commit ba174f4d authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

op-bindings: delete hardhat package (#10300)

This package was helpful when we still relied on hardhat
for the compiler toolchain. Now that we use foundry, this
is no longer used anywhere. Deleting to reduce dead code
in the repo.
parent c5a2ec41
package hardhat
import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"sync"
"github.com/ethereum-optimism/optimism/op-bindings/solc"
)
var (
ErrCannotFindDeployment = errors.New("cannot find deployment")
ErrCannotFindArtifact = errors.New("cannot find artifact")
)
// `Hardhat` encapsulates all of the functionality required to interact
// with hardhat style artifacts.
type Hardhat struct {
ArtifactPaths []string
DeploymentPaths []string
network string
amu sync.Mutex
dmu sync.Mutex
bmu sync.Mutex
artifacts []*Artifact
deployments []*Deployment
buildInfos []*BuildInfo //nolint:unused
}
// New creates a new `Hardhat` struct and reads all of the files from
// disk so that they are cached for the end user. A network is passed
// that corresponds to the network that they deployments are associated
// with. A slice of artifact paths and deployment paths are passed
// so that a single `Hardhat` instance can operate on multiple sets
// of artifacts and deployments. The deployments paths should be
// the root of the deployments directory that contains additional
// directories for each particular network.
func New(network string, artifacts, deployments []string) (*Hardhat, error) {
hh := &Hardhat{
network: network,
ArtifactPaths: artifacts,
DeploymentPaths: deployments,
}
if err := hh.init(); err != nil {
return nil, err
}
return hh, nil
}
// init is called in the constructor and will cache required files to disk.
func (h *Hardhat) init() error {
h.amu.Lock()
defer h.amu.Unlock()
h.dmu.Lock()
defer h.dmu.Unlock()
if err := h.initArtifacts(); err != nil {
return err
}
if err := h.initDeployments(); err != nil {
return err
}
return nil
}
// initDeployments reads all of the deployment json files from disk and then
// caches the deserialized `Deployment` structs.
func (h *Hardhat) initDeployments() error {
knownDeployments := make(map[string]string)
for _, deploymentPath := range h.DeploymentPaths {
fileSystem := os.DirFS(filepath.Join(deploymentPath, h.network))
err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if strings.Contains(path, "solcInputs") {
return nil
}
if !strings.HasSuffix(path, ".json") {
return nil
}
name := filepath.Join(deploymentPath, h.network, path)
file, err := os.ReadFile(name)
if err != nil {
return err
}
var deployment Deployment
if err := json.Unmarshal(file, &deployment); err != nil {
return err
}
deployment.Name = filepath.Base(name[:len(name)-5])
if knownDeployments[deployment.Name] != "" {
return fmt.Errorf(
"discovered duplicate deployment %s. old: %s, new: %s",
deployment.Name,
knownDeployments[deployment.Name],
name,
)
}
h.deployments = append(h.deployments, &deployment)
knownDeployments[deployment.Name] = name
return nil
})
if err != nil {
return err
}
}
return nil
}
// initArtifacts reads all of the artifact json files from disk and then caches
// the deserialized `Artifact` structs.
func (h *Hardhat) initArtifacts() error {
for _, artifactPath := range h.ArtifactPaths {
fileSystem := os.DirFS(artifactPath)
err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
name := filepath.Join(artifactPath, path)
if strings.Contains(name, "build-info") {
return nil
}
if strings.HasSuffix(name, ".dbg.json") {
return nil
}
file, err := os.ReadFile(name)
if err != nil {
return err
}
var artifact Artifact
if err := json.Unmarshal(file, &artifact); err != nil {
return err
}
h.artifacts = append(h.artifacts, &artifact)
return nil
})
if err != nil {
return err
}
}
return nil
}
// GetArtifact returns the artifact that corresponds to the contract.
// This method supports just the contract name and the fully qualified
// contract name.
func (h *Hardhat) GetArtifact(name string) (*Artifact, error) {
h.amu.Lock()
defer h.amu.Unlock()
if IsFullyQualifiedName(name) {
fqn := ParseFullyQualifiedName(name)
for _, artifact := range h.artifacts {
contractNameMatches := artifact.ContractName == fqn.ContractName
sourceNameMatches := artifact.SourceName == fqn.SourceName
if contractNameMatches && sourceNameMatches {
return artifact, nil
}
}
return nil, fmt.Errorf("%w: %s", ErrCannotFindArtifact, name)
}
for _, artifact := range h.artifacts {
if name == artifact.ContractName {
return artifact, nil
}
}
return nil, fmt.Errorf("%w: %s", ErrCannotFindArtifact, name)
}
// GetDeployment returns the deployment that corresponds to the contract.
// It does not support fully qualified contract names.
func (h *Hardhat) GetDeployment(name string) (*Deployment, error) {
h.dmu.Lock()
defer h.dmu.Unlock()
fqn := ParseFullyQualifiedName(name)
for _, deployment := range h.deployments {
if deployment.Name == fqn.ContractName {
return deployment, nil
}
}
return nil, fmt.Errorf("%w: %s", ErrCannotFindDeployment, name)
}
// GetBuildInfo returns the build info that corresponds to the contract.
// It does not support fully qualified contract names.
func (h *Hardhat) GetBuildInfo(name string) (*BuildInfo, error) {
h.bmu.Lock()
defer h.bmu.Unlock()
fqn := ParseFullyQualifiedName(name)
buildInfos := make([]*BuildInfo, 0)
for _, artifactPath := range h.ArtifactPaths {
fileSystem := os.DirFS(artifactPath)
err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
name := filepath.Join(artifactPath, path)
if !strings.HasSuffix(name, ".dbg.json") {
return nil
}
// Remove ".dbg.json"
target := filepath.Base(name[:len(name)-9])
if fqn.ContractName != target {
return nil
}
file, err := os.ReadFile(name)
if err != nil {
return err
}
var debugFile DebugFile
if err := json.Unmarshal(file, &debugFile); err != nil {
return err
}
relPath := filepath.Join(filepath.Dir(name), debugFile.BuildInfo)
if err != nil {
return err
}
debugPath, _ := filepath.Abs(relPath)
buildInfoFile, err := os.ReadFile(debugPath)
if err != nil {
return err
}
var buildInfo BuildInfo
if err := json.Unmarshal(buildInfoFile, &buildInfo); err != nil {
return err
}
buildInfos = append(buildInfos, &buildInfo)
return nil
})
if err != nil {
return nil, err
}
}
// TODO(tynes): handle multiple contracts with same name when required
if len(buildInfos) > 1 {
return nil, fmt.Errorf("Multiple contracts with name %s", name)
}
if len(buildInfos) == 0 {
return nil, fmt.Errorf("Cannot find BuildInfo for %s", name)
}
return buildInfos[0], nil
}
// TODO(tynes): handle fully qualified names properly
func (h *Hardhat) GetStorageLayout(name string) (*solc.StorageLayout, error) {
fqn := ParseFullyQualifiedName(name)
buildInfo, err := h.GetBuildInfo(name)
if err != nil {
return nil, err
}
for _, source := range buildInfo.Output.Contracts {
for name, contract := range source {
if name == fqn.ContractName {
return &contract.StorageLayout, nil
}
}
}
return nil, fmt.Errorf("contract not found for %s", fqn.ContractName)
}
package hardhat_test
import (
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/hardhat"
"github.com/stretchr/testify/require"
)
func TestGetFullyQualifiedName(t *testing.T) {
t.Parallel()
cases := []struct {
fqn hardhat.QualifiedName
expect string
}{
{
fqn: hardhat.QualifiedName{"contract.sol", "C"},
expect: "contract.sol:C",
},
{
fqn: hardhat.QualifiedName{"folder/contract.sol", "C"},
expect: "folder/contract.sol:C",
},
{
fqn: hardhat.QualifiedName{"folder/a:b/contract.sol", "C"},
expect: "folder/a:b/contract.sol:C",
},
}
for _, test := range cases {
got := hardhat.GetFullyQualifiedName(test.fqn.SourceName, test.fqn.ContractName)
require.Equal(t, got, test.expect)
}
}
func TestParseFullyQualifiedName(t *testing.T) {
t.Parallel()
cases := []struct {
fqn string
expect hardhat.QualifiedName
}{
{
fqn: "contract.sol:C",
expect: hardhat.QualifiedName{"contract.sol", "C"},
},
{
fqn: "folder/contract.sol:C",
expect: hardhat.QualifiedName{"folder/contract.sol", "C"},
},
{
fqn: "folder/a:b/contract.sol:C",
expect: hardhat.QualifiedName{"folder/a:b/contract.sol", "C"},
},
}
for _, test := range cases {
got := hardhat.ParseFullyQualifiedName(test.fqn)
require.Equal(t, got, test.expect)
}
}
func TestIsFullyQualifiedName(t *testing.T) {
t.Parallel()
cases := []struct {
fqn string
expect bool
}{
{
fqn: "contract.sol:C",
expect: true,
},
{
fqn: "folder/contract.sol:C",
expect: true,
},
{
fqn: "folder/a:b/contract.sol:C",
expect: true,
},
{
fqn: "C",
expect: false,
},
{
fqn: "contract.sol",
expect: false,
},
{
fqn: "folder/contract.sol",
expect: false,
},
}
for _, test := range cases {
got := hardhat.IsFullyQualifiedName(test.fqn)
require.Equal(t, got, test.expect)
}
}
func TestHardhatGetArtifact(t *testing.T) {
t.Parallel()
hh, err := hardhat.New(
"goerli",
[]string{"testdata/artifacts"},
[]string{"testdata/deployments"},
)
require.Nil(t, err)
artifact, err := hh.GetArtifact("HelloWorld")
require.Nil(t, err)
require.NotNil(t, artifact)
}
func TestHardhatGetBuildInfo(t *testing.T) {
t.Parallel()
hh, err := hardhat.New(
"goerli",
[]string{"testdata/artifacts"},
[]string{"testdata/deployments"},
)
require.Nil(t, err)
buildInfo, err := hh.GetBuildInfo("HelloWorld")
require.Nil(t, err)
require.NotNil(t, buildInfo)
}
func TestHardhatGetDeployments(t *testing.T) {
t.Parallel()
hh, err := hardhat.New(
"goerli",
[]string{"testdata/artifacts"},
[]string{"testdata/deployments"},
)
require.Nil(t, err)
deployment, err := hh.GetDeployment("OptimismPortal")
require.Nil(t, err)
require.NotNil(t, deployment)
}
func TestHardhatGetDeploymentsDuplicates(t *testing.T) {
t.Parallel()
// Set the network to an empty string to simulate
// an invalid network name.
_, err := hardhat.New(
"",
[]string{"testdata/artifacts"},
[]string{"testdata/deployments"},
)
require.Error(t, err)
require.Contains(t, err.Error(), "duplicate deployment")
}
func TestHardhatGetStorageLayout(t *testing.T) {
t.Parallel()
hh, err := hardhat.New(
"goerli",
[]string{"testdata/artifacts"},
[]string{"testdata/deployments"},
)
require.Nil(t, err)
storageLayout, err := hh.GetStorageLayout("HelloWorld")
require.Nil(t, err)
require.NotNil(t, storageLayout)
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/41b5106372a301360350245ee188494f.json"
}
{
"_format": "hh-sol-artifact-1",
"contractName": "HelloWorld",
"sourceName": "contracts/HelloWorld.sol",
"abi": [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "addr",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "addresses",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "boolean",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gm",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "small",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "time",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b504260005561014d806100246000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c806316ada547146100675780636cf3c25e14610083578063767800de146100a2578063c0129d43146100cd578063c5b57bdb146100da578063edf26d9b146100fe575b600080fd5b61007060005481565b6040519081526020015b60405180910390f35b6003546100909060ff1681565b60405160ff909116815260200161007a565b6001546100b5906001600160a01b031681565b6040516001600160a01b03909116815260200161007a565b6000805442909155610070565b6001546100ee90600160a01b900460ff1681565b604051901515815260200161007a565b6100b561010c366004610127565b6002602052600090815260409020546001600160a01b031681565b60006020828403121561013957600080fd5b503591905056fea164736f6c634300080f000a",
"deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100625760003560e01c806316ada547146100675780636cf3c25e14610083578063767800de146100a2578063c0129d43146100cd578063c5b57bdb146100da578063edf26d9b146100fe575b600080fd5b61007060005481565b6040519081526020015b60405180910390f35b6003546100909060ff1681565b60405160ff909116815260200161007a565b6001546100b5906001600160a01b031681565b6040516001600160a01b03909116815260200161007a565b6000805442909155610070565b6001546100ee90600160a01b900460ff1681565b604051901515815260200161007a565b6100b561010c366004610127565b6002602052600090815260409020546001600160a01b031681565b60006020828403121561013957600080fd5b503591905056fea164736f6c634300080f000a",
"linkReferences": {},
"deployedLinkReferences": {}
}
package hardhat
import (
"encoding/json"
"strings"
"github.com/ethereum-optimism/optimism/op-bindings/solc"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// Deployment represents a hardhat-deploy artifact file
type Deployment struct {
Name string
Abi abi.ABI `json:"abi"`
Address common.Address `json:"address"`
Args []interface{} `json:"-"`
Bytecode hexutil.Bytes `json:"bytecode"`
DeployedBytecode hexutil.Bytes `json:"deployedBytecode"`
Devdoc json.RawMessage `json:"devdoc"`
Metadata string `json:"metadata"`
Receipt json.RawMessage `json:"receipt"`
SolcInputHash string `json:"solcInputHash"`
StorageLayout solc.StorageLayout `json:"storageLayout"`
TransactionHash common.Hash `json:"transactionHash"`
Userdoc json.RawMessage `json:"userdoc"`
}
// UnmarshalJSON is a custom unmarshaler for Deployment, handling the Args field. This changed recently
// when `foundry` migrated to `alloy` types, and now the Args field within the contract artifact has
// a different serialization format.
//
// This custom unmarshaller should be removed when this is fixed upstream.
//
// Old Example:
// ```
// "args": [
//
// "0xCE9FeE676767A25feb9722986148Fcd87085a14e",
// "OVM_L1CrossDomainMessenger"
//
// ],
// ```
//
// New Example:
// ```
// "args": "[\"0x45ce2021212883d655348778aC99707d63D49aBc\",\"\\OVM_L1CrossDomainMessenger\\\"]"
// ```
func (d *Deployment) UnmarshalJSON(data []byte) error {
// Create a type alias to prevent recursion
type DeploymentAlias Deployment
// Unmarshal all fields except for `Args`
var alias DeploymentAlias
if err := json.Unmarshal(data, &alias); err != nil {
return err
}
// Unmarshal `Args` manually.
tmp := struct {
Args json.RawMessage `json:"args"`
}{}
if err := json.Unmarshal(data, &tmp); err != nil {
return err
}
// Strip the `args` string of escapes and quotes.
stripped := strings.ReplaceAll(strings.Trim(string(tmp.Args), "\""), "\\", "")
// Unmarshal the stripped version of the `args` field.
var args []interface{}
if err := json.Unmarshal([]byte(stripped), &args); err != nil {
return err
}
// Set the `Args` field in the `Deployment` to the correctly unmarshaled value
alias.Args = args
// Assign the unmarshaled alias back to the original struct
*d = Deployment(alias)
return nil
}
// Receipt represents the receipt held in a hardhat-deploy
// artifact file
type Receipt struct {
To *common.Address `json:"to"`
From common.Address `json:"from"`
ContractAddress *common.Address `json:"contractAddress"`
TransactionIndex uint `json:"transactionIndex"`
GasUsed uint `json:"gasUsed,string"`
LogsBloom hexutil.Bytes `json:"logsBloom"`
BlockHash common.Hash `json:"blockHash"`
TransactionHash common.Hash `json:"transactionHash"`
Logs []Log `json:"logs"`
BlockNumber uint `json:"blockNumber"`
CumulativeGasUsed uint `json:"cumulativeGasUsed,string"`
Status uint `json:"status"`
Byzantium bool `json:"byzantium"`
}
// Log represents the logs in the hardhat deploy artifact receipt
type Log struct {
TransactionIndex uint `json:"transactionIndex"`
BlockNumber uint `json:"blockNumber"`
TransactionHash common.Hash `json:"transactionHash"`
Address common.Address `json:"address"`
Topics []common.Hash `json:"topics"`
Data hexutil.Bytes `json:"data"`
LogIndex uint `json:"logIndex"`
Blockhash common.Hash `json:"blockHash"`
}
// Artifact represents a hardhat compilation artifact
// The Bytecode and DeployedBytecode are not guaranteed
// to be hexutil.Bytes when there are link references.
// In the future, custom json marshalling can be used
// to place the link reference values in the correct location.
type Artifact struct {
Format string `json:"_format"`
ContractName string `json:"contractName"`
SourceName string `json:"sourceName"`
Abi abi.ABI `json:"abi"`
Bytecode hexutil.Bytes `json:"bytecode"`
DeployedBytecode hexutil.Bytes `json:"deployedBytecode"`
LinkReferences LinkReferences `json:"linkReferences"`
DeployedLinkReferences LinkReferences `json:"deployedLinkReferences"`
}
// LinkReferences represents the linked contracts
type LinkReferences map[string]LinkReference
// LinkReference represents a single linked contract
type LinkReference map[string][]LinkReferenceOffset
// LinkReferenceOffset represents the offsets in a link reference
type LinkReferenceOffset struct {
Length uint `json:"length"`
Start uint `json:"start"`
}
// DebugFile represents the debug file that contains the path
// to the build info file
type DebugFile struct {
Format string `json:"_format"`
BuildInfo string `json:"buildInfo"`
}
// BuildInfo represents a hardhat build info artifact that is created
// after compilation
type BuildInfo struct {
Format string `json:"_format"`
Id string `json:"id"`
SolcVersion string `json:"solcVersion"`
SolcLongVersion string `json:"solcLongVersion"`
Input solc.CompilerInput `json:"input"`
Output solc.CompilerOutput `json:"output"`
}
package hardhat
import "strings"
type QualifiedName struct {
SourceName string
ContractName string
}
func ParseFullyQualifiedName(name string) QualifiedName {
names := strings.Split(name, ":")
if len(names) == 1 {
return QualifiedName{
SourceName: "",
ContractName: names[0],
}
}
contractName := names[len(names)-1]
sourceName := strings.Join(names[0:len(names)-1], ":")
return QualifiedName{
ContractName: contractName,
SourceName: sourceName,
}
}
func GetFullyQualifiedName(sourceName, contractName string) string {
return sourceName + ":" + contractName
}
func IsFullyQualifiedName(name string) bool {
return strings.Contains(name, ":")
}
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