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: ...@@ -512,6 +512,30 @@ jobs:
command: make && git diff --exit-code command: make && git diff --exit-code
working_directory: op-bindings 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: js-lint-test:
parameters: parameters:
package_name: package_name:
...@@ -1979,3 +2003,16 @@ workflows: ...@@ -1979,3 +2003,16 @@ workflows:
context: context:
- oplabs-gcr - oplabs-gcr
- slack - 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: ...@@ -80,3 +80,6 @@ clean:
test: test:
go test ./... go test ./...
test-bindgen-e2e:
RUN_E2E=true go test -count=1 ./bindgen/...
...@@ -60,7 +60,8 @@ ...@@ -60,7 +60,8 @@
"name": "Create2Deployer", "name": "Create2Deployer",
"verified": true, "verified": true,
"deployments": { "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.
package main package main
import (
"github.com/ethereum-optimism/optimism/op-bindings/etherscan"
"github.com/ethereum/go-ethereum/common"
)
var fetchContractDataTests = []struct {
name string
contractVerified bool
chain string
deploymentAddress string
expectedContractData contractData
}{
{
"MultiCall3 on ETH",
true,
"eth",
"0xcA11bde05977b3631167028862bE2a173976CA11",
contractData{
MultiCall3Abi,
MultiCall3DeployedBytecode,
etherscan.Transaction{
Input: MultiCall3InitBytecode,
Hash: "0x00d9fcb7848f6f6b0aae4fb709c133d69262b902156c85a473ef23faa60760bd",
To: "",
},
},
},
{
"MultiCall3 on OP",
true,
"op",
"0xcA11bde05977b3631167028862bE2a173976CA11",
contractData{
MultiCall3Abi,
MultiCall3DeployedBytecode,
etherscan.Transaction{
Input: MultiCall3InitBytecode,
Hash: "0xb62f9191a2cf399c0d2afd33f5b8baf7c6b52af6dd2386e44121b1bab91b80e5",
To: "",
},
},
},
{
"SafeSingletonFactory on ETH",
false,
"eth",
"0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7",
contractData{
"",
SafeSingletonFactoryDeployedBytecode,
etherscan.Transaction{
Input: SafeSingletonFactoryInitBytecode,
Hash: "0x69c275b5304db980105b7a6d731f9e1157a3fe29e7ff6ff95235297df53e9928",
To: "",
},
},
},
{
"Permit2 on ETH",
true,
"eth",
"0x000000000022D473030F116dDEE9F6B43aC78BA3",
contractData{
Permit2Abi,
Permit2DeployedBytecode,
etherscan.Transaction{
Input: Permit2InitBytecode,
Hash: "0xf2f1fe96c16ee674bb7fcee166be52465a418927d124f5f1d231b36eae65d377",
To: "0x4e59b44847b379578588920ca78fbf26c0b4956c",
},
},
},
}
// Not currently being tested due to complexity of test setup:
// - FetchDeploymentTxHash failure
// Not being tested because the contract would need to have deployed bytecode to
// pass FetchDeployedBytecode, which means Etherscan should have indexed the deployment tx
// - FetchDeploymentTx failure
// Not being tested for the same reason and there would be no way to pass FetchDeploymentTxHash,
// but not be able to retrieve tx details
var fetchContractDataTestsFailures = []struct {
name string
contractVerified bool
chain string
deploymentAddress string
expectedError string
}{
{
"MultiCall3 on Foo",
true,
"foo",
"0xcA11bde05977b3631167028862bE2a173976CA11",
"unknown chain, unable to retrieve a contract data client for chain: foo",
},
{
// This test case is covering fetching an ABI for a non-verified contract that's we're saying is verified
"SafeSingletonFactory on ETH",
true,
"eth",
"0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7",
"error fetching ABI: operation failed permanently after 3 attempts: there was an issue with the Etherscan request",
},
{
// This test case is covering fetching the deployed bytecode for a non-existent contract
"Nonexistent on ETH",
false,
"eth",
"0x914d7Fec6aaC8cd542e72Bca78B30650d455555",
"error fetching deployed bytecode: API response result is not expected bytecode string",
},
}
// The Init bytecode used for these tests can either be sourced // The Init bytecode used for these tests can either be sourced
// on-chain using the deployment tx of these contracts, or can be // on-chain using the deployment tx of these contracts, or can be
// found in the bindings output from BindGen (../bindings/) // found in the bindings output from BindGen (../bindings/)
...@@ -10,19 +123,19 @@ var removeDeploymentSaltTests = []struct { ...@@ -10,19 +123,19 @@ var removeDeploymentSaltTests = []struct {
expected string expected string
}{ }{
{ {
"TestRemoveDeploymentSalt Case #1", "Case #1",
Safe_v130InitBytecode, Safe_v130InitBytecode,
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
Safe_v130InitBytecodeNoSalt, Safe_v130InitBytecodeNoSalt,
}, },
{ {
"TestRemoveDeploymentSalt Case #2", "Case #2",
Permit2InitBytecode, Permit2InitBytecode,
"0000000000000000000000000000000000000000d3af2663da51c10215000000", "0000000000000000000000000000000000000000d3af2663da51c10215000000",
Permit2InitBytecodeNoSalt, Permit2InitBytecodeNoSalt,
}, },
{ {
"TestRemoveDeploymentSalt Case #3", "Case #3",
EntryPointInitBytecode, EntryPointInitBytecode,
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
EntryPointInitBytecodeNoSalt, EntryPointInitBytecodeNoSalt,
...@@ -36,15 +149,441 @@ var removeDeploymentSaltTestsFailures = []struct { ...@@ -36,15 +149,441 @@ var removeDeploymentSaltTestsFailures = []struct {
expectedError string expectedError string
}{ }{
{ {
"TestRemoveDeploymentSalt Failure Case #1 Invalid Regex", "Failure Case #1 Invalid Regex",
"0x1234abc", "0x1234abc",
"[invalid-regex", "[invalid-regex",
"failed to compile regular expression: error parsing regexp: missing closing ]: `[invalid-regex)`", "failed to compile regular expression: error parsing regexp: missing closing ]: `[invalid-regex)`",
}, },
{ {
"TestRemoveDeploymentSalt Failure Case #2 Salt Not Found", "Failure Case #2 Salt Not Found",
"0x1234abc", "0x1234abc",
"4567", "4567",
"expected salt: 4567 to be at the beginning of the contract initialization code: 0x1234abc, but it wasn't", "expected salt: 4567 to be at the beginning of the contract initialization code: 0x1234abc, but it wasn't",
}, },
} }
var compareInitBytecodeWithOpTests = []struct {
name string
contractMetadataEth remoteContractMetadata
initCodeShouldMatch bool
}{
{
name: "Safe_v130 Init Bytecode Should Match",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"),
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: Safe_v130InitBytecodeNoSalt,
DeployedBin: "",
},
initCodeShouldMatch: true,
},
{
name: "Safe_v130 Compare Init Bytecode Only On OP",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: Safe_v130InitBytecodeNoSalt,
DeployedBin: "",
},
initCodeShouldMatch: true,
},
{
name: "Create2Deployer's Init Bytecode Should Not Match",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Create2Deployer",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2"),
Eth: common.HexToAddress("0xF49600926c7109BD66Ab97a2c036bf696e58Dbc2"),
},
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: Create2DeployerInitBytecode,
DeployedBin: Create2DeployerDeployedBytecode,
},
initCodeShouldMatch: false,
},
}
var compareInitBytecodeWithOpTestsFailures = []struct {
name string
contractMetadataEth remoteContractMetadata
initCodeShouldMatch bool
expectedError string
}{
{
name: "Safe_v130 Mismatch Init Bytecode",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"),
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: Permit2InitBytecodeNoSalt,
DeployedBin: "",
},
initCodeShouldMatch: true,
expectedError: "expected initialization bytecode to match on Ethereum and Optimism, but it doesn't.",
},
{
name: "Safe_v130 No Deployment on Optimism",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: Safe_v130InitBytecode,
DeployedBin: Safe_v130DeployedBytecode,
},
initCodeShouldMatch: true,
expectedError: "no deployment address on Optimism provided for Safe_v130",
},
{
name: "MultiCall3 Expected Init Code Not to Match, but it Does",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "MultiCall3",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11"),
Eth: common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11"),
},
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: MultiCall3InitBytecode,
DeployedBin: MultiCall3DeployedBytecode,
},
initCodeShouldMatch: false,
expectedError: "expected initialization bytecode on Ethereum to not match on Optimism, but it did.",
},
{
name: "Safe_v130 No Init Bytecode Provided",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"),
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: "",
DeployedBin: Safe_v130DeployedBytecode,
},
initCodeShouldMatch: false,
expectedError: "no initialization bytecode provided for ETH deployment for comparison",
},
}
var compareDeployedBytecodeWithOpTests = []struct {
name string
contractMetadataEth remoteContractMetadata
deployedCodeShouldMatch bool
}{
{
name: "Safe_v130 Deployed Bytecode Should Match",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"),
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: "",
DeployedBin: Safe_v130DeployedBytecode,
},
deployedCodeShouldMatch: true,
},
{
name: "Safe_v130 Compare Deployed Bytecode Only On OP",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: Safe_v130InitBytecodeNoSalt,
DeployedBin: Safe_v130DeployedBytecode,
},
deployedCodeShouldMatch: true,
},
{
name: "Permit2's Deployed Bytecode Should Not Match",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Permit2",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0x000000000022D473030F116dDEE9F6B43aC78BA3"),
Eth: common.HexToAddress("0x000000000022D473030F116dDEE9F6B43aC78BA3"),
},
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: Permit2InitBytecode,
DeployedBin: Permit2DeployedBytecode,
},
deployedCodeShouldMatch: false,
},
}
var compareDeployedBytecodeWithOpTestsFailures = []struct {
name string
contractMetadataEth remoteContractMetadata
deployedCodeShouldMatch bool
expectedError string
}{
{
name: "Safe_v130 Mismatch Deplolyed Bytecode",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"),
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: "",
DeployedBin: Permit2DeployedBytecode,
},
deployedCodeShouldMatch: true,
expectedError: "expected deployed bytecode to match on Ethereum and Optimism, but it doesn't.",
},
{
name: "Safe_v130 No Deployment on Optimism",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: "",
DeployedBin: Permit2DeployedBytecode,
},
deployedCodeShouldMatch: true,
expectedError: "no deployment address on Optimism provided for Safe_v130",
},
{
name: "Safe_v130 Expected Deployed Code Not to Match, but it Does",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"),
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: Safe_v130InitBytecode,
DeployedBin: Safe_v130DeployedBytecode,
},
deployedCodeShouldMatch: false,
expectedError: "expected deployed bytecode on Ethereum to not match on Optimism, but it does.",
},
{
name: "Safe_v130 No Deployed Bytecode Provided",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"),
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: Safe_v130InitBytecode,
DeployedBin: "",
},
deployedCodeShouldMatch: false,
expectedError: "no deployed bytecode provided for ETH deployment for comparison",
},
}
var compareDeployedBytecodeWithRpcTests = []struct {
name string
contractMetadataEth remoteContractMetadata
chain string
}{
{
name: "Safe_v130 Compare Against ETH",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.Address{},
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: "",
DeployedBin: Safe_v130DeployedBytecode,
},
chain: "eth",
},
{
name: "Safe_v130 Compare Against OP",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.HexToAddress("0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"),
Eth: common.Address{},
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: "",
DeployedBin: Safe_v130DeployedBytecode,
},
chain: "op",
},
}
var compareDeployedBytecodeWithRpcTestsFailures = []struct {
name string
contractMetadataEth remoteContractMetadata
chain string
expectedError string
}{
{
name: "Safe_v130 Compare Against foo",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.Address{},
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: "",
DeployedBin: "",
},
chain: "foo",
expectedError: "unknown chain: foo, unable to retrieve a RPC client",
},
{
name: "Safe_v130 Bytecode Mismatch",
contractMetadataEth: remoteContractMetadata{
remoteContract: remoteContract{
Name: "Safe_v130",
Verified: true,
Deployments: deployments{
Op: common.Address{},
Eth: common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938"),
},
DeploymentSalt: "0000000000000000000000000000000000000000000000000000000000000000",
Deployer: common.Address{},
ABI: "",
InitBytecode: "",
},
Package: "bindings",
InitBin: "",
DeployedBin: Permit2DeployedBytecode,
},
chain: "eth",
expectedError: "Safe_v130 deployment bytecode from RPC doesn't match bytecode from Etherscan.",
},
}
...@@ -27,7 +27,7 @@ type contractDataClient interface { ...@@ -27,7 +27,7 @@ type contractDataClient interface {
FetchAbi(ctx context.Context, address string) (string, error) FetchAbi(ctx context.Context, address string) (string, error)
FetchDeployedBytecode(ctx context.Context, address string) (string, error) FetchDeployedBytecode(ctx context.Context, address string) (string, error)
FetchDeploymentTxHash(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 { type deployments struct {
......
...@@ -18,7 +18,7 @@ import ( ...@@ -18,7 +18,7 @@ import (
type contractData struct { type contractData struct {
abi string abi string
deployedBin string deployedBin string
deploymentTx etherscan.TxInfo deploymentTx etherscan.Transaction
} }
func (generator *bindGenGeneratorRemote) standardHandler(contractMetadata *remoteContractMetadata) error { func (generator *bindGenGeneratorRemote) standardHandler(contractMetadata *remoteContractMetadata) error {
...@@ -47,8 +47,11 @@ func (generator *bindGenGeneratorRemote) standardHandler(contractMetadata *remot ...@@ -47,8 +47,11 @@ func (generator *bindGenGeneratorRemote) standardHandler(contractMetadata *remot
return err return err
} }
if err := generator.compareBytecodeWithOp(contractMetadata, true, true); err != nil { if err := generator.compareInitBytecodeWithOp(contractMetadata, true); err != nil {
return fmt.Errorf("error comparing contract bytecode for %s: %w", contractMetadata.Name, err) 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) return generator.writeAllOutputs(contractMetadata, remoteContractMetadataTemplate)
...@@ -66,10 +69,16 @@ func (generator *bindGenGeneratorRemote) create2DeployerHandler(contractMetadata ...@@ -66,10 +69,16 @@ func (generator *bindGenGeneratorRemote) create2DeployerHandler(contractMetadata
return err 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 // because we're predeploying a modified version of Create2Deployer that has not yet been
// deployed to OP. // deployed to OP.
// For context: https://github.com/ethereum-optimism/op-geth/pull/126 // 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) return generator.writeAllOutputs(contractMetadata, remoteContractMetadataTemplate)
} }
...@@ -85,9 +94,6 @@ func (generator *bindGenGeneratorRemote) multiSendHandler(contractMetadata *remo ...@@ -85,9 +94,6 @@ func (generator *bindGenGeneratorRemote) multiSendHandler(contractMetadata *remo
contractMetadata.ABI = fetchedData.abi contractMetadata.ABI = fetchedData.abi
contractMetadata.DeployedBin = fetchedData.deployedBin contractMetadata.DeployedBin = fetchedData.deployedBin
if err = generator.compareDeployedBytecodeWithRpc(contractMetadata, "eth"); err != nil {
return err
}
if err = generator.compareDeployedBytecodeWithRpc(contractMetadata, "op"); err != nil { if err = generator.compareDeployedBytecodeWithRpc(contractMetadata, "op"); err != nil {
return err return err
} }
...@@ -114,8 +120,11 @@ func (generator *bindGenGeneratorRemote) senderCreatorHandler(contractMetadata * ...@@ -114,8 +120,11 @@ func (generator *bindGenGeneratorRemote) senderCreatorHandler(contractMetadata *
// The SenderCreator contract is deployed by EntryPoint, so the transaction data // The SenderCreator contract is deployed by EntryPoint, so the transaction data
// from the deployment transaction is for the entire EntryPoint deployment. // 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 // 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 { if err := generator.compareInitBytecodeWithOp(contractMetadata, false); err != nil {
return fmt.Errorf("error comparing contract bytecode for %s: %w", contractMetadata.Name, err) 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) return generator.writeAllOutputs(contractMetadata, remoteContractMetadataTemplate)
...@@ -128,6 +137,7 @@ func (generator *bindGenGeneratorRemote) permit2Handler(contractMetadata *remote ...@@ -128,6 +137,7 @@ func (generator *bindGenGeneratorRemote) permit2Handler(contractMetadata *remote
} }
contractMetadata.ABI = fetchedData.abi contractMetadata.ABI = fetchedData.abi
contractMetadata.DeployedBin = fetchedData.deployedBin
if contractMetadata.InitBin, err = generator.removeDeploymentSalt(fetchedData.deploymentTx.Input, contractMetadata.DeploymentSalt); err != nil { if contractMetadata.InitBin, err = generator.removeDeploymentSalt(fetchedData.deploymentTx.Input, contractMetadata.DeploymentSalt); err != nil {
return err return err
} }
...@@ -140,10 +150,13 @@ func (generator *bindGenGeneratorRemote) permit2Handler(contractMetadata *remote ...@@ -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 // are dependent on block.chainid
if err := generator.compareBytecodeWithOp(contractMetadata, true, false); err != nil { if err := generator.compareDeployedBytecodeWithOp(contractMetadata, false); err != nil {
return fmt.Errorf("error comparing contract bytecode for %s: %w", contractMetadata.Name, err) return fmt.Errorf("%s: %w", contractMetadata.Name, err)
} }
return generator.writeAllOutputs(contractMetadata, permit2MetadataTemplate) return generator.writeAllOutputs(contractMetadata, permit2MetadataTemplate)
...@@ -206,37 +219,77 @@ func (generator *bindGenGeneratorRemote) removeDeploymentSalt(deploymentData, de ...@@ -206,37 +219,77 @@ func (generator *bindGenGeneratorRemote) removeDeploymentSalt(deploymentData, de
return re.ReplaceAllString(deploymentData, ""), nil 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 // 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()) opContractData, err := generator.fetchContractData(false, "op", contractMetadataEth.Deployments.Op.Hex())
if err != nil { if err != nil {
return err return err
} }
if compareInitialization {
if opContractData.deploymentTx.Input, err = generator.removeDeploymentSalt(opContractData.deploymentTx.Input, contractMetadataEth.DeploymentSalt); err != nil { if opContractData.deploymentTx.Input, err = generator.removeDeploymentSalt(opContractData.deploymentTx.Input, contractMetadataEth.DeploymentSalt); err != nil {
return err 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( 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.Name,
contractMetadataEth.InitBin, contractMetadataEth.InitBin,
opContractData.deploymentTx.Input, 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 { deployedCodeComparison := strings.EqualFold(contractMetadataEth.DeployedBin, opContractData.deployedBin)
if !strings.EqualFold(contractMetadataEth.DeployedBin, opContractData.deployedBin) { if deployedCodeShouldMatch && !deployedCodeComparison {
return fmt.Errorf( 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.Name,
contractMetadataEth.DeployedBin, contractMetadataEth.DeployedBin,
opContractData.deployedBin, opContractData.deployedBin,
) )
}
} }
return nil return nil
......
package main package main
import ( import (
"fmt"
"os"
"reflect"
"strings"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-bindings/etherscan"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestRemoveDeploymentSalt(t *testing.T) { var generator bindGenGeneratorRemote = bindGenGeneratorRemote{}
generator := 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 { for _, tt := range removeDeploymentSaltTests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, _ := generator.removeDeploymentSalt(tt.deploymentData, tt.deploymentSalt) got, _ := generator.removeDeploymentSalt(tt.deploymentData, tt.deploymentSalt)
...@@ -18,8 +83,6 @@ func TestRemoveDeploymentSalt(t *testing.T) { ...@@ -18,8 +83,6 @@ func TestRemoveDeploymentSalt(t *testing.T) {
} }
func TestRemoveDeploymentSaltFailures(t *testing.T) { func TestRemoveDeploymentSaltFailures(t *testing.T) {
generator := bindGenGeneratorRemote{}
for _, tt := range removeDeploymentSaltTestsFailures { for _, tt := range removeDeploymentSaltTestsFailures {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := generator.removeDeploymentSalt(tt.deploymentData, tt.deploymentSalt) _, err := generator.removeDeploymentSalt(tt.deploymentData, tt.deploymentSalt)
...@@ -27,3 +90,111 @@ func TestRemoveDeploymentSaltFailures(t *testing.T) { ...@@ -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 { ...@@ -30,10 +30,10 @@ type rpcResponse struct {
Result json.RawMessage `json:"result"` Result json.RawMessage `json:"result"`
} }
type TxInfo struct { type Transaction struct {
TxHash string `json:"txHash"` Hash string `json:"hash"`
To string `json:"to"`
Input string `json:"input"` Input string `json:"input"`
To string `json:"to"`
} }
const apiMaxRetries = 3 const apiMaxRetries = 3
...@@ -174,7 +174,9 @@ func (c *client) FetchDeploymentTxHash(ctx context.Context, address string) (str ...@@ -174,7 +174,9 @@ func (c *client) FetchDeploymentTxHash(ctx context.Context, address string) (str
return "", err return "", err
} }
var results []TxInfo var results []struct {
Hash string `json:"txHash"`
}
err = json.Unmarshal(response.Result, &results) err = json.Unmarshal(response.Result, &results)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to unmarshal API response as []txInfo: %w", err) 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 ...@@ -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 "", 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 := url.Values{}
params.Set("txHash", txHash) params.Set("txHash", txHash)
params.Set("tag", "latest") params.Set("tag", "latest")
url := constructUrl(c.baseUrl, "eth_getTransactionByHash", "proxy", params) url := constructUrl(c.baseUrl, "eth_getTransactionByHash", "proxy", params)
response, err := c.fetchEtherscanRpc(ctx, url) response, err := c.fetchEtherscanRpc(ctx, url)
if err != nil { if err != nil {
return TxInfo{}, err return Transaction{}, err
} }
resultBytes, err := json.Marshal(response.Result) resultBytes, err := json.Marshal(response.Result)
if err != nil { 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) err = json.Unmarshal(resultBytes, &tx)
if err != nil { 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 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