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.
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
// on-chain using the deployment tx of these contracts, or can be
// found in the bindings output from BindGen (../bindings/)
......@@ -10,19 +123,19 @@ var removeDeploymentSaltTests = []struct {
expected string
}{
{
"TestRemoveDeploymentSalt Case #1",
"Case #1",
Safe_v130InitBytecode,
"0000000000000000000000000000000000000000000000000000000000000000",
Safe_v130InitBytecodeNoSalt,
},
{
"TestRemoveDeploymentSalt Case #2",
"Case #2",
Permit2InitBytecode,
"0000000000000000000000000000000000000000d3af2663da51c10215000000",
Permit2InitBytecodeNoSalt,
},
{
"TestRemoveDeploymentSalt Case #3",
"Case #3",
EntryPointInitBytecode,
"0000000000000000000000000000000000000000000000000000000000000000",
EntryPointInitBytecodeNoSalt,
......@@ -36,15 +149,441 @@ var removeDeploymentSaltTestsFailures = []struct {
expectedError string
}{
{
"TestRemoveDeploymentSalt Failure Case #1 Invalid Regex",
"Failure Case #1 Invalid Regex",
"0x1234abc",
"[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",
"4567",
"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 {
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 opContractData.deploymentTx.Input, err = generator.removeDeploymentSalt(opContractData.deploymentTx.Input, contractMetadataEth.DeploymentSalt); err != nil {
return err
}
if !strings.EqualFold(contractMetadataEth.InitBin, opContractData.deploymentTx.Input) {
return fmt.Errorf(
"initialization bytecode on Ethereum doesn't match bytecode on Optimism. contract=%s bytecodeEth=%s bytecodeOp=%s",
contractMetadataEth.Name,
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(
"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,
)
}
if compareDeployment {
if !strings.EqualFold(contractMetadataEth.DeployedBin, opContractData.deployedBin) {
return fmt.Errorf(
"deployed bytecode on Ethereum doesn't match bytecode on Optimism. contract=%s bytecodeEth=%s bytecodeOp=%s",
contractMetadataEth.Name,
contractMetadataEth.DeployedBin,
opContractData.deployedBin,
)
}
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
}
deployedCodeComparison := strings.EqualFold(contractMetadataEth.DeployedBin, opContractData.deployedBin)
if deployedCodeShouldMatch && !deployedCodeComparison {
return fmt.Errorf(
"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"`
Input string `json:"input"`
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