Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/migration app #329

Merged
merged 28 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e76d3c9
- added migration batch creator components
iulianpascalau Sep 2, 2024
09c9833
- fixes
iulianpascalau Sep 2, 2024
0b73e33
- added signing capability to the migration tool
iulianpascalau Sep 3, 2024
2fe6240
- linter fix
iulianpascalau Sep 3, 2024
703a778
- made the migration tool automatically fetch the tokens list
iulianpascalau Sep 3, 2024
5d860dc
- fixes
iulianpascalau Sep 3, 2024
1a5f57d
- fixes after review: renaming
iulianpascalau Sep 4, 2024
55fd6db
Merge pull request #330 from multiversx/create-migration-app
iulianpascalau Sep 5, 2024
2b02015
Merge branch 'feat/migration-app' into signing
iulianpascalau Sep 5, 2024
67ea3dc
- fixes after review
iulianpascalau Sep 5, 2024
a08de87
- fixes
iulianpascalau Sep 5, 2024
c1ceb9c
Merge branch 'signing' into fetch-tokens-list
iulianpascalau Sep 5, 2024
afe1fb1
Merge pull request #331 from multiversx/signing
iulianpascalau Sep 5, 2024
7dfe930
Merge branch 'feat/migration-app' into fetch-tokens-list
iulianpascalau Sep 5, 2024
d276170
- minor config fix
iulianpascalau Sep 6, 2024
2b97dfc
- added migration executor implementation
iulianpascalau Sep 6, 2024
b833567
Merge branch 'fetch-tokens-list' into execute
iulianpascalau Sep 6, 2024
759a9a8
Merge pull request #333 from multiversx/fetch-tokens-list
dragos-rebegea Sep 6, 2024
ed52cc4
- fixes after merge
iulianpascalau Sep 6, 2024
55fc59f
Merge branch 'feat/migration-app' into execute
iulianpascalau Sep 6, 2024
d652f62
- fixes in test files
iulianpascalau Sep 6, 2024
dcce982
Merge remote-tracking branch 'origin/execute' into execute
iulianpascalau Sep 6, 2024
ad8c3fd
- fixes after review
iulianpascalau Sep 6, 2024
a8ceae9
Merge pull request #334 from multiversx/execute
dragos-rebegea Sep 6, 2024
2d7593c
- migration tool fixes
iulianpascalau Sep 13, 2024
4ac7748
Merge pull request #340 from multiversx/migration-fixes
iulianpascalau Sep 16, 2024
b8e82c1
Merge branch 'feat/v3' into merge-feat-v3-migration-app-2024.09.16
iulianpascalau Sep 16, 2024
c91cdef
Merge pull request #341 from multiversx/merge-feat-v3-migration-app-2…
iulianpascalau Sep 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clients/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,7 @@ var (

// ErrNoPendingBatchAvailable signals that no pending batch is available
ErrNoPendingBatchAvailable = errors.New("no pending batch available")

// ErrNilCryptoHandler signals that a nil crypto handler was provided
ErrNilCryptoHandler = errors.New("nil crypto handler")
)
40 changes: 15 additions & 25 deletions clients/ethereum/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package ethereum

import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"
"sync"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/multiversx/mx-bridge-eth-go/clients"
Expand All @@ -34,7 +32,7 @@ type ArgsEthereumClient struct {
Log chainCore.Logger
AddressConverter core.AddressConverter
Broadcaster Broadcaster
PrivateKey *ecdsa.PrivateKey
CryptoHandler CryptoHandler
TokensMapper TokensMapper
SignatureHolder SignaturesHolder
SafeContractAddress common.Address
Expand All @@ -52,8 +50,7 @@ type client struct {
log chainCore.Logger
addressConverter core.AddressConverter
broadcaster Broadcaster
privateKey *ecdsa.PrivateKey
publicKey *ecdsa.PublicKey
cryptoHandler CryptoHandler
tokensMapper TokensMapper
signatureHolder SignaturesHolder
safeContractAddress common.Address
Expand All @@ -76,20 +73,13 @@ func NewEthereumClient(args ArgsEthereumClient) (*client, error) {
return nil, err
}

publicKey := args.PrivateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return nil, errPublicKeyCast
}

c := &client{
clientWrapper: args.ClientWrapper,
erc20ContractsHandler: args.Erc20ContractsHandler,
log: args.Log,
addressConverter: args.AddressConverter,
broadcaster: args.Broadcaster,
privateKey: args.PrivateKey,
publicKey: publicKeyECDSA,
cryptoHandler: args.CryptoHandler,
tokensMapper: args.TokensMapper,
signatureHolder: args.SignatureHolder,
safeContractAddress: args.SafeContractAddress,
Expand All @@ -102,7 +92,7 @@ func NewEthereumClient(args ArgsEthereumClient) (*client, error) {
}

c.log.Info("NewEthereumClient",
"relayer address", crypto.PubkeyToAddress(*publicKeyECDSA),
"relayer address", c.cryptoHandler.GetAddress(),
"safe contract address", c.safeContractAddress.String())

return c, err
Expand All @@ -124,8 +114,8 @@ func checkArgs(args ArgsEthereumClient) error {
if check.IfNil(args.Broadcaster) {
return errNilBroadcaster
}
if args.PrivateKey == nil {
return clients.ErrNilPrivateKey
if check.IfNil(args.CryptoHandler) {
return clients.ErrNilCryptoHandler
}
if check.IfNil(args.TokensMapper) {
return clients.ErrNilTokensMapper
Expand Down Expand Up @@ -256,7 +246,7 @@ func (c *client) WasExecuted(ctx context.Context, batchID uint64) (bool, error)

// BroadcastSignatureForMessageHash will send the signature for the provided message hash
func (c *client) BroadcastSignatureForMessageHash(msgHash common.Hash) {
signature, err := crypto.Sign(msgHash.Bytes(), c.privateKey)
signature, err := c.cryptoHandler.Sign(msgHash)
if err != nil {
c.log.Error("error generating signature", "msh hash", msgHash, "error", err)
return
Expand All @@ -267,6 +257,11 @@ func (c *client) BroadcastSignatureForMessageHash(msgHash common.Hash) {

// GenerateMessageHash will generate the message hash based on the provided batch
func (c *client) GenerateMessageHash(batch *batchProcessor.ArgListsBatch, batchId uint64) (common.Hash, error) {
return GenerateMessageHash(batch, batchId)
}

// GenerateMessageHash will generate the message hash based on the provided batch
func GenerateMessageHash(batch *batchProcessor.ArgListsBatch, batchId uint64) (common.Hash, error) {
if batch == nil {
return common.Hash{}, clients.ErrNilBatch
}
Expand Down Expand Up @@ -336,9 +331,7 @@ func (c *client) ExecuteTransfer(
return "", fmt.Errorf("%w in client.ExecuteTransfer", clients.ErrMultisigContractPaused)
}

fromAddress := crypto.PubkeyToAddress(*c.publicKey)

nonce, err := c.getNonce(ctx, fromAddress)
nonce, err := c.getNonce(ctx, c.cryptoHandler.GetAddress())
if err != nil {
return "", err
}
Expand All @@ -348,7 +341,7 @@ func (c *client) ExecuteTransfer(
return "", err
}

auth, err := bind.NewKeyedTransactorWithChainID(c.privateKey, chainId)
auth, err := c.cryptoHandler.CreateKeyedTransactor(chainId)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -496,10 +489,7 @@ func (c *client) WhitelistedTokens(ctx context.Context, token common.Address) (b
}

func (c *client) checkRelayerFundsForFee(ctx context.Context, transferFee *big.Int) error {

ethereumRelayerAddress := crypto.PubkeyToAddress(*c.publicKey)

existingBalance, err := c.clientWrapper.BalanceAt(ctx, ethereumRelayerAddress, nil)
existingBalance, err := c.clientWrapper.BalanceAt(ctx, c.cryptoHandler.GetAddress(), nil)
if err != nil {
return err
}
Expand Down
89 changes: 67 additions & 22 deletions clients/ethereum/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,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/crypto"
"github.com/multiversx/mx-bridge-eth-go/clients"
"github.com/multiversx/mx-bridge-eth-go/clients/ethereum/contract"
bridgeCore "github.com/multiversx/mx-bridge-eth-go/core"
Expand All @@ -33,8 +32,6 @@ var expectedRecipients = []common.Address{common.BytesToAddress([]byte("to1")),
var expectedNonces = []*big.Int{big.NewInt(10), big.NewInt(30)}

func createMockEthereumClientArgs() ArgsEthereumClient {
sk, _ := crypto.HexToECDSA("9bb971db41e3815a669a71c3f1bcb24e0b81f21e04bf11faa7a34b9b40e7cfb1")

addressConverter, err := converters.NewAddressConverter()
if err != nil {
panic(err)
Expand All @@ -46,7 +43,7 @@ func createMockEthereumClientArgs() ArgsEthereumClient {
Log: logger.GetOrCreate("test"),
AddressConverter: addressConverter,
Broadcaster: &testsCommon.BroadcasterStub{},
PrivateKey: sk,
CryptoHandler: &bridgeTests.CryptoHandlerStub{},
TokensMapper: &bridgeTests.TokensMapperStub{
ConvertTokenCalled: func(ctx context.Context, sourceBytes []byte) ([]byte, error) {
return append([]byte("ERC20"), sourceBytes...), nil
Expand Down Expand Up @@ -137,12 +134,12 @@ func TestNewEthereumClient(t *testing.T) {
assert.Equal(t, errNilBroadcaster, err)
assert.True(t, check.IfNil(c))
})
t.Run("nil private key", func(t *testing.T) {
t.Run("nil crypto handler", func(t *testing.T) {
args := createMockEthereumClientArgs()
args.PrivateKey = nil
args.CryptoHandler = nil
c, err := NewEthereumClient(args)

assert.Equal(t, clients.ErrNilPrivateKey, err)
assert.Equal(t, clients.ErrNilCryptoHandler, err)
assert.True(t, check.IfNil(c))
})
t.Run("nil tokens mapper", func(t *testing.T) {
Expand Down Expand Up @@ -475,23 +472,54 @@ func TestClient_GenerateMessageHash(t *testing.T) {
func TestClient_BroadcastSignatureForMessageHash(t *testing.T) {
t.Parallel()

expectedSig := "b556014dd984183e4662dc3204e522a5a92093fd6f64bb2da9c1b66b8d5ad12d774e05728b83c76bf09bb91af93ede4118f59aa949c7d02c86051dd0fa140c9900"
broadcastCalled := false
t.Run("sign failed should not broadcast", func(t *testing.T) {
t.Parallel()

hash := common.HexToHash("c99286352d865e33f1747761cbd440a7906b9bd8a5261cb6909e5ba18dd19b08")
args := createMockEthereumClientArgs()
args.Broadcaster = &testsCommon.BroadcasterStub{
BroadcastSignatureCalled: func(signature []byte, messageHash []byte) {
assert.Equal(t, hash.Bytes(), messageHash)
assert.Equal(t, expectedSig, hex.EncodeToString(signature))
broadcastCalled = true
},
}
expectedError := errors.New("expected error")
hash := common.HexToHash("hash")
args := createMockEthereumClientArgs()
args.Broadcaster = &testsCommon.BroadcasterStub{
BroadcastSignatureCalled: func(signature []byte, messageHash []byte) {
assert.Fail(t, "should have not called bradcast")
},
}
args.CryptoHandler = &bridgeTests.CryptoHandlerStub{
SignCalled: func(msgHash common.Hash) ([]byte, error) {
assert.Equal(t, hash.Bytes(), msgHash.Bytes())
return nil, expectedError
},
}

c, _ := NewEthereumClient(args)
c.BroadcastSignatureForMessageHash(hash)
c, _ := NewEthereumClient(args)
c.BroadcastSignatureForMessageHash(hash)
})
t.Run("should work", func(t *testing.T) {
t.Parallel()

expectedSig := "expected sig"
broadcastCalled := false

hash := common.HexToHash("hash")
args := createMockEthereumClientArgs()
args.Broadcaster = &testsCommon.BroadcasterStub{
BroadcastSignatureCalled: func(signature []byte, messageHash []byte) {
assert.Equal(t, hash.Bytes(), messageHash)
assert.Equal(t, expectedSig, string(signature))
broadcastCalled = true
},
}
args.CryptoHandler = &bridgeTests.CryptoHandlerStub{
SignCalled: func(msgHash common.Hash) ([]byte, error) {
assert.Equal(t, hash.Bytes(), msgHash.Bytes())
return []byte(expectedSig), nil
},
}

assert.True(t, broadcastCalled)
c, _ := NewEthereumClient(args)
c.BroadcastSignatureForMessageHash(hash)

assert.True(t, broadcastCalled)
})
}

func TestClient_WasExecuted(t *testing.T) {
Expand All @@ -517,6 +545,11 @@ func TestClient_ExecuteTransfer(t *testing.T) {
t.Parallel()

args := createMockEthereumClientArgs()
args.CryptoHandler = &bridgeTests.CryptoHandlerStub{
CreateKeyedTransactorCalled: func(chainId *big.Int) (*bind.TransactOpts, error) {
return &bind.TransactOpts{}, nil
},
}
batch := createMockTransferBatch()
argLists := batchProcessor.ExtractListMvxToEth(batch)
signatures := make([][]byte, 10)
Expand Down Expand Up @@ -589,6 +622,18 @@ func TestClient_ExecuteTransfer(t *testing.T) {
assert.Equal(t, "", hash)
assert.True(t, errors.Is(err, expectedErr))
})
t.Run("create keyed transactor fails", func(t *testing.T) {
expectedErr := errors.New("expected error create keyed transactor")
c, _ := NewEthereumClient(args)
c.cryptoHandler = &bridgeTests.CryptoHandlerStub{
CreateKeyedTransactorCalled: func(chainId *big.Int) (*bind.TransactOpts, error) {
return nil, expectedErr
},
}
hash, err := c.ExecuteTransfer(context.Background(), common.Hash{}, argLists, batch.ID, 10)
assert.Equal(t, "", hash)
assert.True(t, errors.Is(err, expectedErr))
})
t.Run("get current gas price fails", func(t *testing.T) {
expectedErr := errors.New("expected error get current gas price")
c, _ := NewEthereumClient(args)
Expand All @@ -599,7 +644,7 @@ func TestClient_ExecuteTransfer(t *testing.T) {
}
hash, err := c.ExecuteTransfer(context.Background(), common.Hash{}, argLists, batch.ID, 10)
assert.Equal(t, "", hash)
assert.True(t, errors.Is(err, expectedErr))
assert.ErrorIs(t, err, expectedErr)
})
t.Run("not enough quorum", func(t *testing.T) {
c, _ := NewEthereumClient(args)
Expand Down
63 changes: 63 additions & 0 deletions clients/ethereum/cryptoHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ethereum

import (
"crypto/ecdsa"
"math/big"
"os"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/multiversx/mx-bridge-eth-go/core/converters"
)

type cryptoHandler struct {
privateKey *ecdsa.PrivateKey
publicKey *ecdsa.PublicKey
address common.Address
}

// NewCryptoHandler creates a new instance of type cryptoHandler able to sign messages and provide the containing public key
func NewCryptoHandler(privateKeyFilename string) (*cryptoHandler, error) {
privateKeyBytes, err := os.ReadFile(privateKeyFilename)
if err != nil {
return nil, err
}
privateKeyString := converters.TrimWhiteSpaceCharacters(string(privateKeyBytes))
privateKey, err := ethCrypto.HexToECDSA(privateKeyString)
if err != nil {
return nil, err
}

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return nil, errPublicKeyCast
}

return &cryptoHandler{
privateKey: privateKey,
publicKey: publicKeyECDSA,
address: ethCrypto.PubkeyToAddress(*publicKeyECDSA),
}, nil
}

// Sign signs the provided message hash with the containing private key
func (handler *cryptoHandler) Sign(msgHash common.Hash) ([]byte, error) {
return ethCrypto.Sign(msgHash.Bytes(), handler.privateKey)
}

// GetAddress returns the corresponding address of the containing public key
func (handler *cryptoHandler) GetAddress() common.Address {
return handler.address
}

// CreateKeyedTransactor creates a keyed transactor used to create transactions on Ethereum chain
func (handler *cryptoHandler) CreateKeyedTransactor(chainId *big.Int) (*bind.TransactOpts, error) {
return bind.NewKeyedTransactorWithChainID(handler.privateKey, chainId)
}

// IsInterfaceNil returns true if there is no value under the interface
func (handler *cryptoHandler) IsInterfaceNil() bool {
return handler == nil
}
Loading
Loading