Commit e5066e33 authored by Wyatt Barnes's avatar Wyatt Barnes Committed by GitHub

Init BindGen E2E tests (#8651)

* Init BindGen unit tests

* Init BindGen E2E tests
parent fd3e7e82
......@@ -512,6 +512,30 @@ jobs:
command: make && git diff --exit-code
working_directory: op-bindings
bindgen-remote:
docker:
- image: <<pipeline.parameters.ci_builder_image>>
resource_class: xlarge
steps:
- checkout
- run:
name: bindgen remote bindings
command: make bindgen-remote && git diff --exit-code
working_directory: op-bindings
- notify-failures-on-develop
bindgen-test:
docker:
- image: <<pipeline.parameters.ci_builder_image>>
resource_class: xlarge
steps:
- checkout
- run:
name: bindgen test
command: make test-bindgen-e2e
working_directory: op-bindings
- notify-failures-on-develop
js-lint-test:
parameters:
package_name:
......@@ -1979,3 +2003,16 @@ workflows:
context:
- oplabs-gcr
- slack
scheduled-bindgen:
when:
equal: [ build_daily, <<pipeline.schedule.name>> ]
jobs:
- bindgen-remote:
context:
- slack
- oplabs-etherscan
- bindgen-test:
context:
- slack
- oplabs-etherscan
......@@ -80,3 +80,6 @@ clean:
test:
go test ./...
test-bindgen-e2e:
RUN_E2E=true go test -count=1 ./bindgen/...
......@@ -60,7 +60,8 @@
"name": "Create2Deployer",
"verified": true,
"deployments": {
"eth": "0xF49600926c7109BD66Ab97a2c036bf696e58Dbc2"
"eth": "0xF49600926c7109BD66Ab97a2c036bf696e58Dbc2",
"op": "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2"
}
},
{
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
......@@ -27,7 +27,7 @@ type contractDataClient interface {
FetchAbi(ctx context.Context, address string) (string, error)
FetchDeployedBytecode(ctx context.Context, address string) (string, error)
FetchDeploymentTxHash(ctx context.Context, address string) (string, error)
FetchDeploymentTx(ctx context.Context, txHash string) (etherscan.TxInfo, error)
FetchDeploymentTx(ctx context.Context, txHash string) (etherscan.Transaction, error)
}
type deployments struct {
......
......@@ -18,7 +18,7 @@ import (
type contractData struct {
abi string
deployedBin string
deploymentTx etherscan.TxInfo
deploymentTx etherscan.Transaction
}
func (generator *bindGenGeneratorRemote) standardHandler(contractMetadata *remoteContractMetadata) error {
......@@ -47,8 +47,11 @@ func (generator *bindGenGeneratorRemote) standardHandler(contractMetadata *remot
return err
}
if err := generator.compareBytecodeWithOp(contractMetadata, true, true); err != nil {
return fmt.Errorf("error comparing contract bytecode for %s: %w", contractMetadata.Name, err)
if err := generator.compareInitBytecodeWithOp(contractMetadata, true); err != nil {
return fmt.Errorf("%s: %w", contractMetadata.Name, err)
}
if err := generator.compareDeployedBytecodeWithOp(contractMetadata, true); err != nil {
return fmt.Errorf("%s: %w", contractMetadata.Name, err)
}
return generator.writeAllOutputs(contractMetadata, remoteContractMetadataTemplate)
......@@ -66,10 +69,16 @@ func (generator *bindGenGeneratorRemote) create2DeployerHandler(contractMetadata
return err
}
// We're not comparing the bytecode for Create2Deployer with deployment on OP,
// We're expecting the bytecode for Create2Deployer to not match the deployment on OP,
// because we're predeploying a modified version of Create2Deployer that has not yet been
// deployed to OP.
// For context: https://github.com/ethereum-optimism/op-geth/pull/126
if err := generator.compareInitBytecodeWithOp(contractMetadata, false); err != nil {
return fmt.Errorf("%s: %w", contractMetadata.Name, err)
}
if err := generator.compareDeployedBytecodeWithOp(contractMetadata, false); err != nil {
return fmt.Errorf("%s: %w", contractMetadata.Name, err)
}
return generator.writeAllOutputs(contractMetadata, remoteContractMetadataTemplate)
}
......@@ -85,9 +94,6 @@ func (generator *bindGenGeneratorRemote) multiSendHandler(contractMetadata *remo
contractMetadata.ABI = fetchedData.abi
contractMetadata.DeployedBin = fetchedData.deployedBin
if err = generator.compareDeployedBytecodeWithRpc(contractMetadata, "eth"); err != nil {
return err
}
if err = generator.compareDeployedBytecodeWithRpc(contractMetadata, "op"); err != nil {
return err
}
......@@ -114,8 +120,11 @@ func (generator *bindGenGeneratorRemote) senderCreatorHandler(contractMetadata *
// The SenderCreator contract is deployed by EntryPoint, so the transaction data
// from the deployment transaction is for the entire EntryPoint deployment.
// So, we're manually providing the initialization bytecode and therefore it isn't being compared here
if err := generator.compareBytecodeWithOp(contractMetadata, false, true); err != nil {
return fmt.Errorf("error comparing contract bytecode for %s: %w", contractMetadata.Name, err)
if err := generator.compareInitBytecodeWithOp(contractMetadata, false); err != nil {
return fmt.Errorf("%s: %w", contractMetadata.Name, err)
}
if err := generator.compareDeployedBytecodeWithOp(contractMetadata, true); err != nil {
return fmt.Errorf("%s: %w", contractMetadata.Name, err)
}
return generator.writeAllOutputs(contractMetadata, remoteContractMetadataTemplate)
......@@ -128,6 +137,7 @@ func (generator *bindGenGeneratorRemote) permit2Handler(contractMetadata *remote
}
contractMetadata.ABI = fetchedData.abi
contractMetadata.DeployedBin = fetchedData.deployedBin
if contractMetadata.InitBin, err = generator.removeDeploymentSalt(fetchedData.deploymentTx.Input, contractMetadata.DeploymentSalt); err != nil {
return err
}
......@@ -140,10 +150,13 @@ func (generator *bindGenGeneratorRemote) permit2Handler(contractMetadata *remote
)
}
// We're not comparing deployed bytecode because Permit2 has immutable Solidity variables that
if err := generator.compareInitBytecodeWithOp(contractMetadata, true); err != nil {
return fmt.Errorf("%s: %w", contractMetadata.Name, err)
}
// We're asserting the deployed bytecode doesn't match, because Permit2 has immutable Solidity variables that
// are dependent on block.chainid
if err := generator.compareBytecodeWithOp(contractMetadata, true, false); err != nil {
return fmt.Errorf("error comparing contract bytecode for %s: %w", contractMetadata.Name, err)
if err := generator.compareDeployedBytecodeWithOp(contractMetadata, false); err != nil {
return fmt.Errorf("%s: %w", contractMetadata.Name, err)
}
return generator.writeAllOutputs(contractMetadata, permit2MetadataTemplate)
......@@ -206,37 +219,77 @@ func (generator *bindGenGeneratorRemote) removeDeploymentSalt(deploymentData, de
return re.ReplaceAllString(deploymentData, ""), nil
}
func (generator *bindGenGeneratorRemote) compareBytecodeWithOp(contractMetadataEth *remoteContractMetadata, compareInitialization, compareDeployment bool) error {
func (generator *bindGenGeneratorRemote) compareInitBytecodeWithOp(contractMetadataEth *remoteContractMetadata, initCodeShouldMatch bool) error {
if contractMetadataEth.InitBin == "" {
return fmt.Errorf("no initialization bytecode provided for ETH deployment for comparison")
}
var zeroAddress common.Address
if contractMetadataEth.Deployments.Op == zeroAddress {
return fmt.Errorf("no deployment address on Optimism provided for %s", contractMetadataEth.Name)
}
// Passing false here, because true will retrieve contract's ABI, but we don't need it for bytecode comparison
opContractData, err := generator.fetchContractData(false, "op", contractMetadataEth.Deployments.Op.Hex())
if err != nil {
return err
}
if compareInitialization {
if opContractData.deploymentTx.Input, err = generator.removeDeploymentSalt(opContractData.deploymentTx.Input, contractMetadataEth.DeploymentSalt); err != nil {
return err
}
if !strings.EqualFold(contractMetadataEth.InitBin, opContractData.deploymentTx.Input) {
initCodeComparison := strings.EqualFold(contractMetadataEth.InitBin, opContractData.deploymentTx.Input)
if initCodeShouldMatch && !initCodeComparison {
return fmt.Errorf(
"expected initialization bytecode to match on Ethereum and Optimism, but it doesn't. contract=%s bytecodeEth=%s bytecodeOp=%s",
contractMetadataEth.Name,
contractMetadataEth.InitBin,
opContractData.deploymentTx.Input,
)
} else if !initCodeShouldMatch && initCodeComparison {
return fmt.Errorf(
"initialization bytecode on Ethereum doesn't match bytecode on Optimism. contract=%s bytecodeEth=%s bytecodeOp=%s",
"expected initialization bytecode on Ethereum to not match on Optimism, but it did. contract=%s bytecodeEth=%s bytecodeOp=%s",
contractMetadataEth.Name,
contractMetadataEth.InitBin,
opContractData.deploymentTx.Input,
)
}
return nil
}
func (generator *bindGenGeneratorRemote) compareDeployedBytecodeWithOp(contractMetadataEth *remoteContractMetadata, deployedCodeShouldMatch bool) error {
if contractMetadataEth.DeployedBin == "" {
return fmt.Errorf("no deployed bytecode provided for ETH deployment for comparison")
}
var zeroAddress common.Address
if contractMetadataEth.Deployments.Op == zeroAddress {
return fmt.Errorf("no deployment address on Optimism provided for %s", contractMetadataEth.Name)
}
// Passing false here, because true will retrieve contract's ABI, but we don't need it for bytecode comparison
opContractData, err := generator.fetchContractData(false, "op", contractMetadataEth.Deployments.Op.Hex())
if err != nil {
return err
}
if compareDeployment {
if !strings.EqualFold(contractMetadataEth.DeployedBin, opContractData.deployedBin) {
deployedCodeComparison := strings.EqualFold(contractMetadataEth.DeployedBin, opContractData.deployedBin)
if deployedCodeShouldMatch && !deployedCodeComparison {
return fmt.Errorf(
"deployed bytecode on Ethereum doesn't match bytecode on Optimism. contract=%s bytecodeEth=%s bytecodeOp=%s",
"expected deployed bytecode to match on Ethereum and Optimism, but it doesn't. contract=%s bytecodeEth=%s bytecodeOp=%s",
contractMetadataEth.Name,
contractMetadataEth.DeployedBin,
opContractData.deployedBin,
)
} else if !deployedCodeShouldMatch && deployedCodeComparison {
return fmt.Errorf(
"expected deployed bytecode on Ethereum to not match on Optimism, but it does. contract=%s bytecodeEth=%s bytecodeOp=%s",
contractMetadataEth.Name,
contractMetadataEth.DeployedBin,
opContractData.deployedBin,
)
}
}
return nil
......
package main
import (
"fmt"
"os"
"reflect"
"strings"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/etherscan"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)
func TestRemoveDeploymentSalt(t *testing.T) {
generator := bindGenGeneratorRemote{}
var generator bindGenGeneratorRemote = bindGenGeneratorRemote{}
func configureGenerator(t *testing.T) error {
if os.Getenv("RUN_E2E") == "" {
t.Log("Not running test, RUN_E2E env not set")
t.Skip()
}
generator.contractDataClients.eth = etherscan.NewEthereumClient(os.Getenv("ETHERSCAN_APIKEY_ETH"))
generator.contractDataClients.op = etherscan.NewOptimismClient(os.Getenv("ETHERSCAN_APIKEY_OP"))
var err error
if generator.rpcClients.eth, err = ethclient.Dial(os.Getenv("RPC_URL_ETH")); err != nil {
return fmt.Errorf("error initializing Ethereum client: %w", err)
}
if generator.rpcClients.op, err = ethclient.Dial(os.Getenv("RPC_URL_OP")); err != nil {
return fmt.Errorf("error initializing Optimism client: %w", err)
}
return nil
}
func TestFetchContractData(t *testing.T) {
if err := configureGenerator(t); err != nil {
t.Error(err)
}
for _, tt := range fetchContractDataTests {
t.Run(tt.name, func(t *testing.T) {
contractData, err := generator.fetchContractData(tt.contractVerified, tt.chain, tt.deploymentAddress)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(contractData, tt.expectedContractData) {
t.Errorf("Retrieved contract data doesn't match expected. Expected: %s Retrieved: %s", tt.expectedContractData, contractData)
}
})
}
}
func TestFetchContractDataFailures(t *testing.T) {
if err := configureGenerator(t); err != nil {
t.Error(err)
}
for _, tt := range fetchContractDataTestsFailures {
t.Run(tt.name, func(t *testing.T) {
_, err := generator.fetchContractData(tt.contractVerified, tt.chain, tt.deploymentAddress)
if err == nil {
t.Errorf("Expected error: %s but didn't receive it", tt.expectedError)
return
}
if !strings.Contains(err.Error(), tt.expectedError) {
t.Errorf("Expected error: %s Received: %s", tt.expectedError, err)
return
}
})
}
}
func TestRemoveDeploymentSalt(t *testing.T) {
for _, tt := range removeDeploymentSaltTests {
t.Run(tt.name, func(t *testing.T) {
got, _ := generator.removeDeploymentSalt(tt.deploymentData, tt.deploymentSalt)
......@@ -18,8 +83,6 @@ func TestRemoveDeploymentSalt(t *testing.T) {
}
func TestRemoveDeploymentSaltFailures(t *testing.T) {
generator := bindGenGeneratorRemote{}
for _, tt := range removeDeploymentSaltTestsFailures {
t.Run(tt.name, func(t *testing.T) {
_, err := generator.removeDeploymentSalt(tt.deploymentData, tt.deploymentSalt)
......@@ -27,3 +90,111 @@ func TestRemoveDeploymentSaltFailures(t *testing.T) {
})
}
}
func TestCompareInitBytecodeWithOp(t *testing.T) {
if err := configureGenerator(t); err != nil {
t.Error(err)
}
for _, tt := range compareInitBytecodeWithOpTests {
t.Run(tt.name, func(t *testing.T) {
err := generator.compareInitBytecodeWithOp(&tt.contractMetadataEth, tt.initCodeShouldMatch)
if err != nil {
t.Error(err)
}
})
}
}
func TestCompareInitBytecodeWithOpFailures(t *testing.T) {
if err := configureGenerator(t); err != nil {
t.Error(err)
}
for _, tt := range compareInitBytecodeWithOpTestsFailures {
t.Run(tt.name, func(t *testing.T) {
err := generator.compareInitBytecodeWithOp(&tt.contractMetadataEth, tt.initCodeShouldMatch)
if err == nil {
t.Errorf("Expected error: %s but didn't receive it", tt.expectedError)
return
}
if !strings.Contains(err.Error(), tt.expectedError) {
t.Errorf("Expected error: %s Received: %s", tt.expectedError, err)
return
}
})
}
}
func TestCompareDeployedBytecodeWithOp(t *testing.T) {
if err := configureGenerator(t); err != nil {
t.Error(err)
}
for _, tt := range compareDeployedBytecodeWithOpTests {
t.Run(tt.name, func(t *testing.T) {
err := generator.compareDeployedBytecodeWithOp(&tt.contractMetadataEth, tt.deployedCodeShouldMatch)
if err != nil {
t.Error(err)
}
})
}
}
func TestCompareDeployedBytecodeWithOpFailures(t *testing.T) {
if err := configureGenerator(t); err != nil {
t.Error(err)
}
for _, tt := range compareDeployedBytecodeWithOpTestsFailures {
t.Run(tt.name, func(t *testing.T) {
err := generator.compareDeployedBytecodeWithOp(&tt.contractMetadataEth, tt.deployedCodeShouldMatch)
if err == nil {
t.Errorf("Expected error: %s but didn't receive it", tt.expectedError)
return
}
if !strings.Contains(err.Error(), tt.expectedError) {
t.Errorf("Expected error: %s Received: %s", tt.expectedError, err)
return
}
})
}
}
func TestCompareDeployedBytecodeWithRpc(t *testing.T) {
if err := configureGenerator(t); err != nil {
t.Error(err)
}
for _, tt := range compareDeployedBytecodeWithRpcTests {
t.Run(tt.name, func(t *testing.T) {
err := generator.compareDeployedBytecodeWithRpc(&tt.contractMetadataEth, tt.chain)
if err != nil {
t.Error(err)
}
})
}
}
func TestCompareDeployedBytecodeWithRpcFailures(t *testing.T) {
if err := configureGenerator(t); err != nil {
t.Error(err)
}
for _, tt := range compareDeployedBytecodeWithRpcTestsFailures {
t.Run(tt.name, func(t *testing.T) {
err := generator.compareDeployedBytecodeWithRpc(&tt.contractMetadataEth, tt.chain)
if err == nil {
t.Errorf("Expected error: %s but didn't receive it", tt.expectedError)
return
}
if !strings.Contains(err.Error(), tt.expectedError) {
t.Errorf("Expected error: %s Received: %s", tt.expectedError, err)
return
}
})
}
}
......@@ -30,10 +30,10 @@ type rpcResponse struct {
Result json.RawMessage `json:"result"`
}
type TxInfo struct {
TxHash string `json:"txHash"`
To string `json:"to"`
type Transaction struct {
Hash string `json:"hash"`
Input string `json:"input"`
To string `json:"to"`
}
const apiMaxRetries = 3
......@@ -174,7 +174,9 @@ func (c *client) FetchDeploymentTxHash(ctx context.Context, address string) (str
return "", err
}
var results []TxInfo
var results []struct {
Hash string `json:"txHash"`
}
err = json.Unmarshal(response.Result, &results)
if err != nil {
return "", fmt.Errorf("failed to unmarshal API response as []txInfo: %w", err)
......@@ -184,28 +186,28 @@ func (c *client) FetchDeploymentTxHash(ctx context.Context, address string) (str
return "", fmt.Errorf("API response result is an empty array")
}
return results[0].TxHash, nil
return results[0].Hash, nil
}
func (c *client) FetchDeploymentTx(ctx context.Context, txHash string) (TxInfo, error) {
func (c *client) FetchDeploymentTx(ctx context.Context, txHash string) (Transaction, error) {
params := url.Values{}
params.Set("txHash", txHash)
params.Set("tag", "latest")
url := constructUrl(c.baseUrl, "eth_getTransactionByHash", "proxy", params)
response, err := c.fetchEtherscanRpc(ctx, url)
if err != nil {
return TxInfo{}, err
return Transaction{}, err
}
resultBytes, err := json.Marshal(response.Result)
if err != nil {
return TxInfo{}, fmt.Errorf("failed to marshal Result into JSON: %w", err)
return Transaction{}, fmt.Errorf("failed to marshal Result into JSON: %w", err)
}
var tx TxInfo
var tx Transaction
err = json.Unmarshal(resultBytes, &tx)
if err != nil {
return TxInfo{}, fmt.Errorf("API response result is not expected txInfo struct: %w", err)
return Transaction{}, fmt.Errorf("API response result is not expected txInfo struct: %w", err)
}
return tx, nil
......
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