Skip to content

Commit

Permalink
perfect unit test (#63)
Browse files Browse the repository at this point in the history
* test(bitcoin): add indexer unit test

* test(bitcoin): perfect test

* refactor(bridge): refactor bridge method

* test(bridge): add unit test
  • Loading branch information
oxf71 authored Nov 30, 2023
1 parent e9892b1 commit 31b29e1
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ test-all: test-unit test-race
PACKAGES_UNIT=$(shell go list ./... | grep -Ev 'vendor|importer')
TEST_PACKAGES=./...
TEST_TARGETS := test-unit test-unit-cover test-race
SKIP_TEST_METHOD=TestNewInscriptionTool
SKIP_TEST_METHOD='(TestNewInscriptionTool|^TestLocal)'

# Test runs-specific rules. To add a new test target, just add
# a new rule, customise ARGS or TEST_PACKAGES ad libitum, and
Expand Down
66 changes: 37 additions & 29 deletions bitcoin/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
)

// Bridge bridge
// TODO: only L1 -> L2, More calls may be supported later
type Bridge struct {
EthRPCURL string
EthPrivKey *ecdsa.PrivateKey
Expand Down Expand Up @@ -53,46 +55,52 @@ func NewBridge(bridgeCfg BridgeConfig, abiFileDir string) (*Bridge, error) {
}

// Deposit to ethereum
// TODO: partition method and add test
func (b *Bridge) Deposit(bitcoinAddress string, amount int64) error {
if bitcoinAddress == "" {
return fmt.Errorf("bitcoin address is empty")
}

ctx := context.Background()
// dail ethereum rpc
client, err := ethclient.Dial(b.EthRPCURL)

toAddress, err := b.BitcoinAddressToEthAddress(bitcoinAddress)
if err != nil {
return err
}

publicKey := b.EthPrivKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("error casting public key to ECDSA")
}
nonce, err := client.PendingNonceAt(ctx, crypto.PubkeyToAddress(*publicKeyECDSA))
data, err := b.ABIPack(b.ABI, "deposit", common.HexToAddress(toAddress), new(big.Int).SetInt64(amount))
if err != nil {
return err
}
gasPrice, err := client.SuggestGasPrice(ctx)

receipt, err := b.ethContractCall(ctx, b.EthPrivKey, data)
if err != nil {
return err
}

contractAbi, err := abi.JSON(bytes.NewReader([]byte(b.ABI)))
if err != nil {
return err
if receipt.Status != 1 {
return fmt.Errorf("tx failed, receipt:%v", receipt)
}
return nil
}

toAddress, err := b.BitcoinAddressToEthAddress(bitcoinAddress)
func (b *Bridge) ethContractCall(ctx context.Context, priv *ecdsa.PrivateKey, data []byte) (*types.Receipt, error) {
client, err := ethclient.Dial(b.EthRPCURL)
if err != nil {
return err
return nil, err
}

data, err := contractAbi.Pack("deposit", common.HexToAddress(toAddress), new(big.Int).SetInt64(amount))
publicKey := priv.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("error casting public key to ECDSA")
}
nonce, err := client.PendingNonceAt(ctx, crypto.PubkeyToAddress(*publicKeyECDSA))
if err != nil {
return err
return nil, err
}
gasPrice, err := client.SuggestGasPrice(ctx)
if err != nil {
return nil, err
}

tx := types.NewTx(&types.LegacyTx{
Expand All @@ -106,31 +114,31 @@ func (b *Bridge) Deposit(bitcoinAddress string, amount int64) error {

chainID, err := client.ChainID(ctx)
if err != nil {
return err
return nil, err
}
// sign tx
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), b.EthPrivKey)
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), priv)
if err != nil {
return err
return nil, err
}

// send tx
err = client.SendTransaction(ctx, signedTx)
if err != nil {
return err
return nil, err
}

// wait tx confirm
receipt, err := bind.WaitMined(ctx, client, signedTx)
if err != nil {
return err
}
return bind.WaitMined(ctx, client, signedTx)
}

if receipt.Status != 1 {
return fmt.Errorf("tx failed, receipt:%v", receipt)
// ABIPack the given method name to conform the ABI. Method call's data
func (b *Bridge) ABIPack(abiData string, method string, args ...interface{}) ([]byte, error) {
contractAbi, err := abi.JSON(bytes.NewReader([]byte(abiData)))
if err != nil {
return nil, err
}

return nil
return contractAbi.Pack(method, args...)
}

// BitcoinAddressToEthAddress bitcoin address to eth address
Expand Down
84 changes: 84 additions & 0 deletions bitcoin/bridge_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package bitcoin_test

import (
"errors"
"math/big"
"os"
"path"
"testing"
Expand All @@ -9,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/evmos/ethermint/bitcoin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewBridge(t *testing.T) {
Expand Down Expand Up @@ -41,3 +44,84 @@ func TestNewBridge(t *testing.T) {
assert.Equal(t, string(abi), bridge.ABI)
assert.Equal(t, bridgeCfg.GasLimit, bridge.GasLimit)
}

// TestLocalDeposit only test in local
func TestLocalDeposit(t *testing.T) {
bridge := bridgeWithConfig(t)
testCase := []struct {
name string
args []interface{}
err error
}{
{
name: "success",
args: []interface{}{"tb1qjda2l5spwyv4ekwe9keddymzuxynea2m2kj0qy", int64(1234)},
err: nil,
},
{
name: "fail: address empty",
args: []interface{}{"", int64(1234)},
err: errors.New("bitcoin address is empty"),
},
}

for _, tc := range testCase {
t.Run(tc.name, func(t *testing.T) {
err := bridge.Deposit(tc.args[0].(string), tc.args[1].(int64))
if err != nil {
assert.Equal(t, tc.err, err)
}
})
}
}

func TestABIPack(t *testing.T) {
t.Run("success", func(t *testing.T) {
abiData, err := os.ReadFile(path.Join("./testdata", "abi.json"))
if err != nil {
t.Fatal(err)
}
expectedMethod := "deposit"
expectedArgs := []interface{}{common.HexToAddress("0x12345678"), new(big.Int).SetInt64(1111)}
expectedResult := []byte{71, 231, 239, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 52, 86, 120, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4, 87}

// Create a mock bridge object
b := &bitcoin.Bridge{}

// Call the ABIPack method
result, err := b.ABIPack(string(abiData), expectedMethod, expectedArgs...)

// Check for errors
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

// Compare the result with the expected result
require.Equal(t, result, expectedResult)
})

t.Run("Invalid ABI data", func(t *testing.T) {
abiData := `{"inputs": [{"type": "address", "name": "to"}, {"type": "uint256", "name": "value"}`
expectedError := errors.New("unexpected EOF")

// Create a mock bridge object
b := &bitcoin.Bridge{}

// Call the ABIPack method
_, err := b.ABIPack(abiData, "method", "arg1", "arg2")

require.EqualError(t, err, expectedError.Error())
})
}

func bridgeWithConfig(t *testing.T) *bitcoin.Bridge {
config, err := bitcoin.LoadBitcoinConfig("./testdata")
require.NoError(t, err)

bridge, err := bitcoin.NewBridge(config.Bridge, "./testdata")
require.NoError(t, err)
return bridge
}
16 changes: 16 additions & 0 deletions bitcoin/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ import (
)

func TestConfig(t *testing.T) {
// clean BITCOIN env set
// This is because the value set by the environment variable affects viper reading file
os.Unsetenv("BITCOIN_NETWORK_NAME")
os.Unsetenv("BITCOIN_RPC_HOST")
os.Unsetenv("BITCOIN_RPC_PORT")
os.Unsetenv("BITCOIN_RPC_USER")
os.Unsetenv("BITCOIN_RPC_PASS")
os.Unsetenv("BITCOIN_WALLET_NAME")
os.Unsetenv("BITCOIN_DESTINATION")
os.Unsetenv("BITCOIN_ENABLE_INDEXER")
os.Unsetenv("BITCOIN_INDEXER_LISTEN_ADDRESS")
os.Unsetenv("BITCOIN_BRIDGE_ETH_RPC_URL")
os.Unsetenv("BITCOIN_BRIDGE_CONTRACT_ADDRESS")
os.Unsetenv("BITCOIN_BRIDGE_ETH_PRIV_KEY")
os.Unsetenv("BITCOIN_BRIDGE_ABI")
os.Unsetenv("BITCOIN_BRIDGE_GAS_LIMIT")
config, err := bitcoin.LoadBitcoinConfig("./testdata")
require.NoError(t, err)
require.Equal(t, "signet", config.NetworkName)
Expand Down
69 changes: 69 additions & 0 deletions bitcoin/indexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/rpcclient"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/evmos/ethermint/bitcoin"
"github.com/evmos/ethermint/types"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -128,6 +129,54 @@ func TestParseAddress(t *testing.T) {
}
}

// TestLocalParseTx only test in local
// data source: testnet network
func TestLocalParseTx(t *testing.T) {
indexer := bitcoinIndexerWithConfig(t)
testCases := []struct {
name string
height int64
dest []*types.BitcoinTxParseResult
}{
{
name: "success",
height: 2540186,
dest: []*types.BitcoinTxParseResult{
{
From: []string{"tb1qravmtnqvtpnmugeg7q90ck69lzznflu4w9amnw"},
To: "tb1qjda2l5spwyv4ekwe9keddymzuxynea2m2kj0qy",
Value: 2306,
},
},
},
{
name: "success empty",
height: 2540180,
dest: []*types.BitcoinTxParseResult{},
},
}

for _, tc := range testCases {
results, err := indexer.ParseBlock(tc.height)
require.NoError(t, err)
require.Equal(t, results, tc.dest)
}
}

// TestLocalLatestBlock only test in local
func TestLocalLatestBlock(t *testing.T) {
indexer := bitcoinIndexerWithConfig(t)
_, err := indexer.LatestBlock()
require.NoError(t, err)
}

// TestLocalBlockChainInfo only test in local
func TestLocalBlockChainInfo(t *testing.T) {
indexer := bitcoinIndexerWithConfig(t)
_, err := indexer.BlockChainInfo()
require.NoError(t, err)
}

func mockRpcClient(t *testing.T) *rpcclient.Client {
connCfg := &rpcclient.ConnConfig{
Host: "127.0.0.1:38332",
Expand All @@ -148,3 +197,23 @@ func mockBitcoinIndexer(t *testing.T, chainParams *chaincfg.Params) *bitcoin.Ind
require.NoError(t, err)
return indexer
}

func bitcoinIndexerWithConfig(t *testing.T) *bitcoin.Indexer {
config, err := bitcoin.LoadBitcoinConfig("./testdata")
require.NoError(t, err)
connCfg := &rpcclient.ConnConfig{
Host: config.RPCHost + ":" + config.RPCPort,
User: config.RPCUser,
Pass: config.RPCPass,
HTTPPostMode: true,
DisableTLS: true,
}
client, err := rpcclient.New(connCfg, nil)
require.NoError(t, err)
bitcoinParam := bitcoin.ChainParams(config.NetworkName)
indexer, err := bitcoin.NewBitcoinIndexer(client,
bitcoinParam,
config.IndexerListenAddress)
require.NoError(t, err)
return indexer
}

0 comments on commit 31b29e1

Please sign in to comment.