diff --git a/.gitignore b/.gitignore index 5aecbe5..4d7387a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist docker/data +.idea coverage.out diff --git a/cmd/main.go b/cmd/main.go index 86c1e9d..0b6fb25 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -10,7 +10,6 @@ import ( "os/signal" "time" - beethoven "github.com/0xPolygon/beethoven" "github.com/0xPolygon/cdk-data-availability/dummyinterfaces" dbConf "github.com/0xPolygon/cdk-validium-node/db" "github.com/0xPolygon/cdk-validium-node/ethtxmanager" @@ -18,11 +17,13 @@ import ( "github.com/0xPolygon/cdk-validium-node/log" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/urfave/cli/v2" "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/sdk/metric" + beethoven "github.com/0xPolygon/beethoven" "github.com/0xPolygon/beethoven/config" "github.com/0xPolygon/beethoven/db" "github.com/0xPolygon/beethoven/etherman" @@ -93,7 +94,19 @@ func start(cliCtx *cli.Context) error { if err != nil { log.Fatal(err) } - ethMan, err := etherman.New(cliCtx.Context, c.L1.NodeURL, *auth) + + // Connect to ethereum node + ethClient, err := ethclient.DialContext(cliCtx.Context, c.L1.NodeURL) + if err != nil { + log.Fatal("error connecting to %s: %+v", c.L1.NodeURL, err) + } + + // Make sure the connection is okay + if _, err = ethClient.ChainID(cliCtx.Context); err != nil { + log.Fatal("error getting chain ID from l1 with address: %+v", err) + } + + ethMan, err := etherman.New(ethClient, *auth) if err != nil { log.Fatal(err) } diff --git a/etherman/etherman.go b/etherman/etherman.go index 560a879..32720f6 100644 --- a/etherman/etherman.go +++ b/etherman/etherman.go @@ -3,6 +3,7 @@ package etherman import ( "context" "errors" + "github.com/jackc/pgx/v4" "math/big" "time" @@ -15,8 +16,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/jackc/pgx/v4" ) type Etherman struct { @@ -24,20 +23,7 @@ type Etherman struct { auth bind.TransactOpts } -func New(ctx context.Context, url string, auth bind.TransactOpts) (Etherman, error) { - // Connect to ethereum node - ethClient, err := ethclient.DialContext(ctx, url) - if err != nil { - log.Errorf("error connecting to %s: %+v", url, err) - return Etherman{}, err - } - - // Make sure the connection is okay - if _, err = ethClient.ChainID(ctx); err != nil { - log.Errorf("error getting chain ID from l1 with %s address: %+v", url, err) - return Etherman{}, err - } - +func New(ethClient EthereumClient, auth bind.TransactOpts) (Etherman, error) { return Etherman{ ethClient: ethClient, auth: auth, @@ -49,6 +35,7 @@ func (e *Etherman) GetSequencerAddr(l1Contract common.Address) (common.Address, if err != nil { return common.Address{}, err } + return contract.TrustedSequencer(&bind.CallOpts{Pending: false}) } @@ -69,6 +56,7 @@ func (e *Etherman) BuildTrustedVerifyBatchesTxData(lastVerifiedBatch, newVerifie log.Errorf("error geting ABI: %v, Proof: %s", err) return nil, err } + return abi.Pack( "verifyBatchesTrustedAggregator", pendStateNum, @@ -177,6 +165,7 @@ func (e *Etherman) GetRevertMessage(ctx context.Context, tx *types.Transaction) if receipt.Status == types.ReceiptStatusFailed { revertMessage, err := operations.RevertReason(ctx, e.ethClient, tx, receipt.BlockNumber) + if err != nil { return "", err } diff --git a/etherman/etherman_test.go b/etherman/etherman_test.go new file mode 100644 index 0000000..4480dc0 --- /dev/null +++ b/etherman/etherman_test.go @@ -0,0 +1,758 @@ +package etherman + +import ( + "context" + "errors" + "github.com/0xPolygon/beethoven/mocks" + "github.com/0xPolygon/beethoven/tx" + cdkTypes "github.com/0xPolygon/cdk-validium-node/jsonrpc/types" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "math/big" + "testing" +) + +func signer(from common.Address, tx *types.Transaction) (*types.Transaction, error) { + return tx, nil +} + +func getEtherman(ethClientMock EthereumClient) Etherman { + ethman, _ := New( + ethClientMock, + bind.TransactOpts{ + From: common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc454e4438f44e"), + Nonce: big.NewInt(0), + Signer: signer, + Value: big.NewInt(0), + GasPrice: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasLimit: 0, + Context: context.TODO(), + NoSend: false, + }, + ) + + return ethman +} + +func TestGetSequencerAddr(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + t.Run("Returns expected error (improperly formatted output)", func(t *testing.T) { + t.Parallel() + + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "CallContract", + mock.Anything, + ethereum.CallMsg{ + From: common.HexToAddress("0x0000000000000000000000000000000000000000"), + To: &common.Address{}, + Gas: 0, + GasPrice: nil, + GasFeeCap: nil, + GasTipCap: nil, + Value: nil, + Data: []uint8{0xcf, 0xa8, 0xed, 0x47}, + }, + (*big.Int)(nil), + ).Return( // Invalid return value below to provocate error + common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000000000"), + nil, + ).Once() + + _, err := ethman.GetSequencerAddr(common.HexToAddress("0x0000000000000000000000000000000000000000")) + + assert.ErrorContains(err, "abi: improperly formatted output:") + ethClient.AssertExpectations(t) + }) + + t.Run("Returns expected sequencer address", func(t *testing.T) { + t.Parallel() + + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "CallContract", + mock.Anything, + ethereum.CallMsg{ + From: common.HexToAddress("0x0000000000000000000000000000000000000000"), + To: &common.Address{}, + Data: []uint8{0xcf, 0xa8, 0xed, 0x47}, + }, + (*big.Int)(nil), + ).Return( + common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000000"), + nil, + ).Once() + + returnValue, err := ethman.GetSequencerAddr(common.HexToAddress("0x0000000000000000000000000000000000000000")) + + assert.Equal(common.Address{}, returnValue) + assert.NoError(err) + ethClient.AssertExpectations(t) + }) +} + +func TestBuildTrustedVerifyBatches(t *testing.T) { + t.Parallel() + assert := assert.New(t) + ethman := getEtherman(new(mocks.EthereumClientMock)) + + // Because we cant mock the ABI dependency is this the only test case that we somehow + // can have here in a unit test. Further test coverage can get achieved with e2e or integration tests. + t.Run("Returns expected error on proof conversion", func(t *testing.T) { + data, err := ethman.BuildTrustedVerifyBatchesTxData( + 0, + 1, + tx.ZKP{ + NewStateRoot: common.HexToHash("0x001"), + NewLocalExitRoot: common.HexToHash("0x002"), + Proof: cdkTypes.ArgBytes("0x30030030030003003300300030033003000300330030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030003003003000300300300030030030"), + }, + ) + + assert.ErrorContains(err, "invalid proof length. Expected length: 1538, Actual length 1534") + assert.Nil(data) + }) +} + +func TestCallContract(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + t.Run("Returns expected value", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "CallContract", + context.TODO(), + ethereum.CallMsg{ + From: common.HexToAddress("0x0000000000000000000000000000000000000000"), + To: &common.Address{}, + Gas: 0, + GasPrice: nil, + GasFeeCap: nil, + GasTipCap: nil, + Value: nil, + Data: []uint8{0xcf, 0xa8, 0xed, 0x47}, // TrustedSequencer sig + }, + (*big.Int)(nil), + ).Return( // Invalid return value below to provocate error + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"), + nil, + ).Once() + + result, err := ethman.CallContract( + context.TODO(), + ethereum.CallMsg{ + From: common.HexToAddress("0x0000000000000000000000000000000000000000"), + To: &common.Address{}, + Gas: 0, + GasPrice: nil, + GasFeeCap: nil, + GasTipCap: nil, + Value: nil, + Data: []uint8{0xcf, 0xa8, 0xed, 0x47}, + }, + (*big.Int)(nil), + ) + + assert.Equal(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"), result) + assert.Equal(nil, err) + ethClient.AssertExpectations(t) + }) + + t.Run("Returns expected error", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "CallContract", + context.TODO(), + ethereum.CallMsg{ + From: common.HexToAddress("0x0000000000000000000000000000000000000000"), + To: &common.Address{}, + Gas: 0, + GasPrice: nil, + GasFeeCap: nil, + GasTipCap: nil, + Value: nil, + Data: []uint8{0xcf, 0xa8, 0xed, 0x47}, // TrustedSequencer sig + }, + (*big.Int)(nil), + ).Return( // Invalid return value below to provocate error + []uint8{}, + errors.New("NOOOPE!"), + ).Once() + + result, err := ethman.CallContract( + context.TODO(), + ethereum.CallMsg{ + From: common.HexToAddress("0x0000000000000000000000000000000000000000"), + To: &common.Address{}, + Gas: 0, + GasPrice: nil, + GasFeeCap: nil, + GasTipCap: nil, + Value: nil, + Data: []uint8{0xcf, 0xa8, 0xed, 0x47}, + }, + (*big.Int)(nil), + ) + + assert.Equal([]uint8{}, result) + assert.ErrorContains(err, "NOOOPE!") + ethClient.AssertExpectations(t) + }) +} + +func TestCheckTxWasMined(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + t.Run("Returns expected error on 'ethereum.NotFound'", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "TransactionReceipt", + context.TODO(), + common.Hash{}, + ).Return( + &types.Receipt{}, + errors.New("not found"), + ).Once() + + status, receipt, err := ethman.CheckTxWasMined(context.TODO(), common.Hash{}) + + assert.False(status) + assert.Nil(receipt) + assert.ErrorContains(err, "not found") + ethClient.AssertExpectations(t) + }) + + t.Run("Returns expected error", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "TransactionReceipt", + context.TODO(), + common.Hash{}, + ).Return( + &types.Receipt{}, + errors.New("Nooope!"), + ).Once() + + status, receipt, err := ethman.CheckTxWasMined(context.TODO(), common.Hash{}) + + assert.False(status) + assert.Nil(receipt) + assert.ErrorContains(err, "Nooope!") + ethClient.AssertExpectations(t) + }) + + t.Run("Returns the expected values", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "TransactionReceipt", + context.TODO(), + common.Hash{}, + ).Return( + &types.Receipt{}, + nil, + ).Once() + + status, receipt, err := ethman.CheckTxWasMined(context.TODO(), common.Hash{}) + + assert.True(status) + assert.Equal(&types.Receipt{}, receipt) + assert.Nil(err) + ethClient.AssertExpectations(t) + }) +} + +func TestCurrentNonce(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + t.Run("Returns the expected nonce value", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "NonceAt", + context.TODO(), + common.Address{}, + (*big.Int)(nil), + ).Return( + uint64(1), + nil, + ).Once() + + result, err := ethman.CurrentNonce(context.TODO(), common.Address{}) + + assert.Equal(uint64(1), result) + assert.Nil(err) + ethClient.AssertExpectations(t) + }) + + t.Run("Returns the expected error", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "NonceAt", + context.TODO(), + common.Address{}, + (*big.Int)(nil), + ).Return( + uint64(0), + errors.New("NA NA NA!"), + ).Once() + + result, err := ethman.CurrentNonce(context.TODO(), common.Address{}) + + assert.Equal(uint64(0), result) + assert.ErrorContains(err, "NA NA NA!") + ethClient.AssertExpectations(t) + }) +} + +func TestGetTx(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + t.Run("Returns the expected transaction", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "TransactionByHash", + context.TODO(), + common.Hash{}, + ).Return( + &types.Transaction{}, + true, + nil, + ).Once() + + transaction, status, err := ethman.GetTx(context.TODO(), common.Hash{}) + + assert.Equal(&types.Transaction{}, transaction) + assert.True(status) + assert.Nil(err) + ethClient.AssertExpectations(t) + }) + + t.Run("Returns the expected error", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "TransactionByHash", + context.TODO(), + common.Hash{}, + ).Return( + &types.Transaction{}, + false, + errors.New("NOPE NOPE!"), + ).Once() + + transaction, status, err := ethman.GetTx(context.TODO(), common.Hash{}) + + assert.Equal(&types.Transaction{}, transaction) + assert.False(status) + assert.ErrorContains(err, "NOPE NOPE!") + ethClient.AssertExpectations(t) + }) +} + +func TestGetTxReceipt(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + t.Run("Returns expected receipt", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "TransactionReceipt", + context.TODO(), + common.Hash{}, + ).Return( + &types.Receipt{}, + nil, + ).Once() + + receipt, err := ethman.GetTxReceipt(context.TODO(), common.Hash{}) + + assert.Equal(&types.Receipt{}, receipt) + assert.Nil(err) + ethClient.AssertExpectations(t) + }) + + t.Run("Returns expected error", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "TransactionReceipt", + context.TODO(), + common.Hash{}, + ).Return( + &types.Receipt{}, + errors.New("NANANA!"), + ).Once() + + receipt, err := ethman.GetTxReceipt(context.TODO(), common.Hash{}) + + assert.Equal(&types.Receipt{}, receipt) + assert.ErrorContains(err, "NANANA!") + ethClient.AssertExpectations(t) + }) +} + +func TestSendTx(t *testing.T) { + t.Parallel() + assert := assert.New(t) + transaction := types.NewTransaction( + uint64(1), + common.Address{}, + big.NewInt(1), + uint64(1), + big.NewInt(1), + []byte{}, + ) + + t.Run("Returns expected value", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "SendTransaction", + context.TODO(), + transaction, + ).Return( + nil, + ).Once() + + err := ethman.SendTx(context.TODO(), transaction) + + assert.Nil(err) + ethClient.AssertExpectations(t) + }) + + t.Run("Returns expected error", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "SendTransaction", + context.TODO(), + transaction, + ).Return( + errors.New("NANANA!"), + ).Once() + + err := ethman.SendTx(context.TODO(), transaction) + + assert.ErrorContains(err, "NANANA!") + ethClient.AssertExpectations(t) + }) +} + +func TestSuggestedGasPrice(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + t.Run("Returns expected value", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "SuggestGasPrice", + context.TODO(), + ).Return( + big.NewInt(1), + nil, + ).Once() + + result, err := ethman.SuggestedGasPrice(context.TODO()) + + assert.Equal(big.NewInt(1), result) + assert.Nil(err) + ethClient.AssertExpectations(t) + }) + + t.Run("Returns expected error", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "SuggestGasPrice", + context.TODO(), + ).Return( + (*big.Int)(nil), + errors.New("NOPE!"), + ).Once() + + result, err := ethman.SuggestedGasPrice(context.TODO()) + + assert.Equal((*big.Int)(nil), result) + assert.ErrorContains(err, "NOPE!") + ethClient.AssertExpectations(t) + }) +} + +func TestEstimateGas(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + t.Run("Returns the expected value", func(t *testing.T) { + ethclient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethclient) + + ethclient.On( + "EstimateGas", + context.TODO(), + ethereum.CallMsg{ + From: common.Address{}, + To: &common.Address{}, + Value: big.NewInt(10), + Data: []byte{}, + }, + ).Return( + uint64(1), + nil, + ).Once() + + result, err := ethman.EstimateGas( + context.TODO(), + common.Address{}, + &common.Address{}, + big.NewInt(10), + []byte{}, + ) + + assert.Equal(uint64(1), result) + assert.Nil(err) + ethclient.AssertExpectations(t) + }) + + t.Run("Returns the expected error", func(t *testing.T) { + ethclient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethclient) + + ethclient.On( + "EstimateGas", + context.TODO(), + ethereum.CallMsg{ + From: common.Address{}, + To: &common.Address{}, + Value: big.NewInt(10), + Data: []byte{}, + }, + ).Return( + uint64(0), + errors.New("NOOOPE!"), + ).Once() + + result, err := ethman.EstimateGas( + context.TODO(), + common.Address{}, + &common.Address{}, + big.NewInt(10), + []byte{}, + ) + + assert.Equal(uint64(0), result) + assert.ErrorContains(err, "NOOOPE!") + ethclient.AssertExpectations(t) + }) +} + +func TestSignTx(t *testing.T) { + t.Parallel() + assert := assert.New(t) + txData := types.NewTransaction( + uint64(1), + common.Address{}, + big.NewInt(1), + uint64(1), + big.NewInt(1), + []byte{}, + ) + + t.Run("Returns the expected value", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + transaction, err := ethman.SignTx(context.TODO(), common.Address{}, txData) + + assert.Equal(txData, transaction) + assert.Nil(err) + }) +} + +func TestGetRevertMessage(t *testing.T) { + t.Parallel() + assert := assert.New(t) + txData := types.NewTransaction( + uint64(1), + common.Address{}, + big.NewInt(1), + uint64(1), + big.NewInt(1), + []byte{0xcf, 0xa8, 0xed, 0x47}, + ) + + t.Run("Returns an empty string and the expected error", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "TransactionReceipt", + context.TODO(), + txData.Hash(), + ).Return( + &types.Receipt{}, + errors.New("NANANA!"), + ).Once() + + result, err := ethman.GetRevertMessage(context.TODO(), txData) + + assert.Equal("", result) + assert.ErrorContains(err, "NANANA!") + }) + + t.Run("Returns an empty string and the error set to nil", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "TransactionReceipt", + context.TODO(), + txData.Hash(), + ).Return( + &types.Receipt{ + Status: types.ReceiptStatusSuccessful, + }, + nil, + ).Once() + + result, err := ethman.GetRevertMessage(context.TODO(), txData) + + assert.Equal("", result) + assert.Nil(err) + }) + + t.Run("Returns the expected revert reason string", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + signedTx, _ := types.SignTx(txData, types.NewEIP155Signer(big.NewInt(1)), key) + + ethClient.On( + "TransactionReceipt", + context.TODO(), + signedTx.Hash(), + ).Return( + &types.Receipt{ + Status: types.ReceiptStatusFailed, + BlockNumber: big.NewInt(1), + }, + nil, + ).Once() + + ethClient.On( + "CallContract", + context.TODO(), + ethereum.CallMsg{ + From: addr, + To: &common.Address{}, + Gas: 1, + GasPrice: nil, + GasFeeCap: nil, + GasTipCap: nil, + Value: big.NewInt(1), + Data: []uint8{0xcf, 0xa8, 0xed, 0x47}, // TrustedSequencer sig + }, + big.NewInt(1), + ).Return( + common.Hex2Bytes("08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548454c4c4f000000000000000000000000000000000000000000000000000000"), + nil, + ).Once() + + result, err := ethman.GetRevertMessage(context.TODO(), signedTx) + + assert.Equal("HELLO", result) + assert.Nil(err) + }) +} + +func TestGetLastBlock(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + t.Run("Returns the expected values", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "BlockByNumber", + context.TODO(), + (*big.Int)(nil), + ).Return( + types.NewBlock( + &types.Header{}, + []*types.Transaction{}, + []*types.Header{}, + []*types.Receipt{}, + types.TrieHasher(nil), + ), + nil, + ) + + result, err := ethman.GetLastBlock(context.TODO(), new(mocks.TxMock)) + + assert.Equal(uint64(0), result.BlockNumber) + assert.Equal(common.HexToHash("0xb159a077fc2af79b9a9c748c9c0e50ff95b74c32946ed52418fcc093d0953f26"), result.BlockHash) + assert.Equal(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), result.ParentHash) + assert.Nil(err) + }) + + t.Run("Returns the expected error", func(t *testing.T) { + ethClient := new(mocks.EthereumClientMock) + ethman := getEtherman(ethClient) + + ethClient.On( + "BlockByNumber", + context.TODO(), + (*big.Int)(nil), + ).Return( + &types.Block{}, + errors.New("NOOPE!"), + ) + + result, err := ethman.GetLastBlock(context.TODO(), new(mocks.TxMock)) + + assert.ErrorContains(err, "NOOPE!") + assert.Nil(result) + }) +} diff --git a/mocks/ethclient_mock.go b/mocks/ethclient_mock.go new file mode 100644 index 0000000..285108e --- /dev/null +++ b/mocks/ethclient_mock.go @@ -0,0 +1,146 @@ +package mocks + +import ( + "context" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/mock" + "math/big" +) + +type EthereumClientMock struct { + mock.Mock +} + +func (e *EthereumClientMock) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + args := e.Called(ctx, hash) + + return args.Get(0).(*types.Block), args.Error(1) +} + +func (e *EthereumClientMock) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + args := e.Called(ctx, hash) + + return args.Get(0).(*types.Header), args.Error(1) +} + +func (e *EthereumClientMock) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + args := e.Called(ctx, number) + + return args.Get(0).(*types.Header), args.Error(1) +} + +func (e *EthereumClientMock) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { + args := e.Called(ctx, blockHash) + + return args.Get(0).(uint), args.Error(1) +} + +func (e *EthereumClientMock) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { + args := e.Called(ctx, blockHash) + + return args.Get(0).(*types.Transaction), args.Error(1) +} + +func (e *EthereumClientMock) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + args := e.Called(ctx, ch) + + return args.Get(0).(ethereum.Subscription), args.Error(1) +} + +func (e *EthereumClientMock) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { + args := e.Called(ctx, account, blockNumber) + + return args.Get(0).(*big.Int), args.Error(1) +} + +func (e *EthereumClientMock) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + args := e.Called(ctx, account, key, blockNumber) + + return args.Get(0).([]byte), args.Error(1) +} + +func (e *EthereumClientMock) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { + args := e.Called(ctx, account, blockNumber) + + return args.Get(0).([]byte), args.Error(1) +} + +func (e *EthereumClientMock) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + args := e.Called(ctx, q) + + return args.Get(0).([]types.Log), args.Error(1) +} + +func (e *EthereumClientMock) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + args := e.Called(ctx, q, ch) + + return args.Get(0).(ethereum.Subscription), args.Error(1) +} + +func (e *EthereumClientMock) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + args := e.Called(ctx, account) + + return args.Get(0).([]byte), args.Error(1) +} + +func (e *EthereumClientMock) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + args := e.Called(ctx, account) + + return args.Get(0).(uint64), args.Error(1) +} + +func (e *EthereumClientMock) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + args := e.Called(ctx) + + return args.Get(0).(*big.Int), args.Error(1) +} + +func (e *EthereumClientMock) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + args := e.Called(ctx, call, blockNumber) + + return args.Get(0).([]byte), args.Error(1) +} + +func (e *EthereumClientMock) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + args := e.Called(ctx, txHash) + + return args.Get(0).(*types.Receipt), args.Error(1) +} + +func (e *EthereumClientMock) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { + args := e.Called(ctx, account, blockNumber) + + return args.Get(0).(uint64), args.Error(1) +} + +func (e *EthereumClientMock) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + args := e.Called(ctx, txHash) + + return args.Get(0).(*types.Transaction), args.Bool(1), args.Error(2) +} + +func (e *EthereumClientMock) SendTransaction(ctx context.Context, tx *types.Transaction) error { + args := e.Called(ctx, tx) + + return args.Error(0) +} + +func (e *EthereumClientMock) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + args := e.Called(ctx) + + return args.Get(0).(*big.Int), args.Error(1) +} + +func (e *EthereumClientMock) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + args := e.Called(ctx, call) + + return args.Get(0).(uint64), args.Error(1) +} + +func (e *EthereumClientMock) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + args := e.Called(ctx, number) + + return args.Get(0).(*types.Block), args.Error(1) +} diff --git a/mocks/tx_mock.go b/mocks/tx_mock.go new file mode 100644 index 0000000..0edeb53 --- /dev/null +++ b/mocks/tx_mock.go @@ -0,0 +1,72 @@ +package mocks + +import ( + "context" + "github.com/jackc/pgconn" + "github.com/jackc/pgx/v4" + "github.com/stretchr/testify/mock" +) + +var _ pgx.Tx = (*TxMock)(nil) + +type TxMock struct { + mock.Mock +} + +func (tx *TxMock) Begin(ctx context.Context) (pgx.Tx, error) { + return nil, nil +} + +func (tx *TxMock) BeginFunc(ctx context.Context, f func(pgx.Tx) error) (err error) { + return nil +} + +func (tx *TxMock) Commit(ctx context.Context) error { + args := tx.Called(ctx) + + return args.Error(0) +} + +func (tx *TxMock) Rollback(ctx context.Context) error { + args := tx.Called(ctx) + + return args.Error(0) +} + +func (tx *TxMock) CopyFrom(ctx context.Context, tableName pgx.Identifier, + columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) { + return 0, nil +} + +func (tx *TxMock) SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults { + return nil +} + +func (tx *TxMock) LargeObjects() pgx.LargeObjects { + return pgx.LargeObjects{} +} + +func (tx *TxMock) Prepare(ctx context.Context, name, sql string) (*pgconn.StatementDescription, error) { + return nil, nil +} + +func (tx *TxMock) Exec(ctx context.Context, sql string, arguments ...interface{}) (commandTag pgconn.CommandTag, err error) { + return nil, nil +} + +func (tx *TxMock) Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) { + return nil, nil +} + +func (tx *TxMock) QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row { + return nil +} + +func (tx *TxMock) QueryFunc(ctx context.Context, sql string, + args []interface{}, scans []interface{}, f func(pgx.QueryFuncRow) error) (pgconn.CommandTag, error) { + return nil, nil +} + +func (tx *TxMock) Conn() *pgx.Conn { + return nil +} diff --git a/rpc/rpc_test.go b/rpc/rpc_test.go index 64a4d6a..6ffdfd1 100644 --- a/rpc/rpc_test.go +++ b/rpc/rpc_test.go @@ -3,6 +3,7 @@ package rpc import ( "context" "errors" + "github.com/0xPolygon/beethoven/mocks" "math/big" "testing" @@ -13,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -94,70 +94,6 @@ func (e *ethTxManagerMock) ProcessPendingMonitoredTxs(ctx context.Context, owner e.Called(ctx, owner, failedResultHandler, dbTx) } -var _ pgx.Tx = (*txMock)(nil) - -type txMock struct { - mock.Mock -} - -func (tx *txMock) Begin(ctx context.Context) (pgx.Tx, error) { - return nil, nil -} - -func (tx *txMock) BeginFunc(ctx context.Context, f func(pgx.Tx) error) (err error) { - return nil -} - -func (tx *txMock) Commit(ctx context.Context) error { - args := tx.Called(ctx) - - return args.Error(0) -} - -func (tx *txMock) Rollback(ctx context.Context) error { - args := tx.Called(ctx) - - return args.Error(0) -} - -func (tx *txMock) CopyFrom(ctx context.Context, tableName pgx.Identifier, - columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) { - return 0, nil -} - -func (tx *txMock) SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults { - return nil -} - -func (tx *txMock) LargeObjects() pgx.LargeObjects { - return pgx.LargeObjects{} -} - -func (tx *txMock) Prepare(ctx context.Context, name, sql string) (*pgconn.StatementDescription, error) { - return nil, nil -} - -func (tx *txMock) Exec(ctx context.Context, sql string, arguments ...interface{}) (commandTag pgconn.CommandTag, err error) { - return nil, nil -} - -func (tx *txMock) Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) { - return nil, nil -} - -func (tx *txMock) QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row { - return nil -} - -func (tx *txMock) QueryFunc(ctx context.Context, sql string, - args []interface{}, scans []interface{}, f func(pgx.QueryFuncRow) error) (pgconn.CommandTag, error) { - return nil, nil -} - -func (tx *txMock) Conn() *pgx.Conn { - return nil -} - var _ ZkEVMClientInterface = (*zkEVMClientMock)(nil) type zkEVMClientMock struct { @@ -217,7 +153,7 @@ func TestInteropEndpointsGetTxStatus(t *testing.T) { txHash := common.HexToHash("0xsomeTxHash") - txMock := new(txMock) + txMock := new(mocks.TxMock) txMock.On("Rollback", mock.Anything).Return(nil).Once() dbMock := new(dbMock) @@ -260,7 +196,7 @@ func TestInteropEndpointsGetTxStatus(t *testing.T) { }, } - txMock := new(txMock) + txMock := new(mocks.TxMock) txMock.On("Rollback", mock.Anything).Return(nil).Once() dbMock := new(dbMock) @@ -326,7 +262,7 @@ func TestInteropEndpointsSendTx(t *testing.T) { zkEVMClientCreatorMock := new(zkEVMClientCreatorMock) zkEVMClientMock := new(zkEVMClientMock) dbMock := new(dbMock) - txMock := new(txMock) + txMock := new(mocks.TxMock) ethTxManagerMock := new(ethTxManagerMock) executeTestFn := func() { diff --git a/sonar-project.properties b/sonar-project.properties index 6dc046f..785e314 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,11 +7,11 @@ sonar.projectName=Beethoven sonar.organization=0xpolygon sonar.sources=. -sonar.exclusions=**/*_test.go,**/vendor/** - +sonar.exclusions=**/*_test.go,**/vendor/**,mocks/**,cmd/main.go + sonar.tests=. sonar.test.inclusions=**/*_test.go -sonar.test.exclusions=**/vendor/** +sonar.test.exclusions=**/vendor/**,mocks/** sonar.issue.enforceSemantic=true # =====================================================