Skip to content

Commit

Permalink
- new solution for partial migration
Browse files Browse the repository at this point in the history
- added `query` operation mode for the migration tool
  • Loading branch information
iulianpascalau committed Oct 24, 2024
1 parent 9993ccc commit 68403db
Show file tree
Hide file tree
Showing 18 changed files with 638 additions and 99 deletions.
24 changes: 22 additions & 2 deletions clients/ethereum/erc20ContractsHolder.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,17 @@ func NewErc20SafeContractsHolder(args ArgsErc20SafeContractsHolder) (*erc20SafeC
}

// BalanceOf returns the ERC20 balance of the provided address
// if the ERC20 contract does not exists in the map of contract wrappers, it will create and add it first
// if the ERC20 contract does not exist in the map of contract wrappers, it will create and add it first
func (h *erc20SafeContractsHolder) BalanceOf(ctx context.Context, erc20Address ethCommon.Address, address ethCommon.Address) (*big.Int, error) {
wrapper, err := h.getOrCreateWrapper(erc20Address)
if err != nil {
return nil, err
}

return wrapper.BalanceOf(ctx, address)
}

func (h *erc20SafeContractsHolder) getOrCreateWrapper(erc20Address ethCommon.Address) (erc20ContractWrapper, error) {
h.mut.Lock()
defer h.mut.Unlock()

Expand All @@ -68,7 +77,18 @@ func (h *erc20SafeContractsHolder) BalanceOf(ctx context.Context, erc20Address e
h.contracts[erc20Address] = wrapper
}

return wrapper.BalanceOf(ctx, address)
return wrapper, nil
}

// Decimals returns the ERC20 decimals for the current ERC20 contract
// if the ERC20 contract does not exist in the map of contract wrappers, it will create and add it first
func (h *erc20SafeContractsHolder) Decimals(ctx context.Context, erc20Address ethCommon.Address) (uint8, error) {
wrapper, err := h.getOrCreateWrapper(erc20Address)
if err != nil {
return 0, err
}

return wrapper.Decimals(ctx)
}

// IsInterfaceNil returns true if there is no value under the interface
Expand Down
113 changes: 111 additions & 2 deletions clients/ethereum/erc20ContractsHolder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestNewErc20SafeContractsHolder(t *testing.T) {
})
}

func TestBalanceOf(t *testing.T) {
func TestErc20SafeContractsHolder_BalanceOf(t *testing.T) {
t.Parallel()

t.Run("address does not exists on map nor blockchain", func(t *testing.T) {
Expand Down Expand Up @@ -121,7 +121,7 @@ func TestBalanceOf(t *testing.T) {
assert.Equal(t, 1, len(ch.contracts))
})

t.Run("new contract addres while another contracts already exists", func(t *testing.T) {
t.Run("new contract address while another contracts already exists", func(t *testing.T) {
var returnedBalance int64 = 1000
args := createMockArgsContractsHolder()
args.EthClient = &bridgeTests.ContractBackendStub{
Expand Down Expand Up @@ -160,6 +160,108 @@ func TestBalanceOf(t *testing.T) {
})
}

func TestErc20SafeContractsHolder_Decimals(t *testing.T) {
t.Parallel()

t.Run("address does not exists on map nor blockchain", func(t *testing.T) {
expectedError := errors.New("no contract code at given address")
args := createMockArgsContractsHolder()
args.EthClient = &bridgeTests.ContractBackendStub{
CallContractCalled: func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
return nil, expectedError
},
}
ch, err := NewErc20SafeContractsHolder(args)
assert.Nil(t, err)
assert.False(t, check.IfNil(ch))
assert.Equal(t, 0, len(ch.contracts))

result, err := ch.Decimals(context.Background(), testsCommon.CreateRandomEthereumAddress())
assert.Equal(t, expectedError, err)
assert.Zero(t, result)
assert.Equal(t, 1, len(ch.contracts))
})
t.Run("address exists only on blockchain", func(t *testing.T) {
returnedDecimals := byte(37)
args := createMockArgsContractsHolder()
args.EthClient = &bridgeTests.ContractBackendStub{
CallContractCalled: func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
return convertByteValueToByteSlice(returnedDecimals), nil
},
}
ch, err := NewErc20SafeContractsHolder(args)
assert.Nil(t, err)
assert.False(t, check.IfNil(ch))
assert.Equal(t, 0, len(ch.contracts))

result, err := ch.Decimals(context.Background(), testsCommon.CreateRandomEthereumAddress())
assert.Nil(t, err)
assert.Equal(t, returnedDecimals, result)
assert.Equal(t, 1, len(ch.contracts))
})
t.Run("address exists also in contracts map", func(t *testing.T) {
returnedDecimals := byte(38)
args := createMockArgsContractsHolder()
args.EthClient = &bridgeTests.ContractBackendStub{
CallContractCalled: func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
return convertByteValueToByteSlice(returnedDecimals), nil
},
}
ch, err := NewErc20SafeContractsHolder(args)
contractAddress := testsCommon.CreateRandomEthereumAddress()
assert.Nil(t, err)
assert.False(t, check.IfNil(ch))
assert.Equal(t, 0, len(ch.contracts))

result, err := ch.Decimals(context.Background(), contractAddress)
// first time the contract does not exist in the map, so it should add it
assert.Nil(t, err)
assert.Equal(t, returnedDecimals, result)
assert.Equal(t, 1, len(ch.contracts))

result, err = ch.Decimals(context.Background(), contractAddress)
// second time the contract already exists in the map, so it should just use it
assert.Nil(t, err)
assert.Equal(t, returnedDecimals, result)
assert.Equal(t, 1, len(ch.contracts))
})
t.Run("new contract address while another contracts already exists", func(t *testing.T) {
returnedDecimals := byte(39)
args := createMockArgsContractsHolder()
args.EthClient = &bridgeTests.ContractBackendStub{
CallContractCalled: func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
return convertByteValueToByteSlice(returnedDecimals), nil
},
}
ch, err := NewErc20SafeContractsHolder(args)
contractAddress1 := testsCommon.CreateRandomEthereumAddress()
contractAddress2 := testsCommon.CreateRandomEthereumAddress()
assert.Nil(t, err)
assert.False(t, check.IfNil(ch))
assert.Equal(t, 0, len(ch.contracts))

result, err := ch.Decimals(context.Background(), contractAddress1)
assert.Nil(t, err)
assert.Equal(t, returnedDecimals, result)
assert.Equal(t, 1, len(ch.contracts))

result, err = ch.Decimals(context.Background(), contractAddress1)
assert.Nil(t, err)
assert.Equal(t, returnedDecimals, result)
assert.Equal(t, 1, len(ch.contracts))

result, err = ch.Decimals(context.Background(), contractAddress2)
assert.Nil(t, err)
assert.Equal(t, returnedDecimals, result)
assert.Equal(t, 2, len(ch.contracts))

result, err = ch.Decimals(context.Background(), contractAddress2)
assert.Nil(t, err)
assert.Equal(t, returnedDecimals, result)
assert.Equal(t, 2, len(ch.contracts))
})
}

func convertBigToAbiCompatible(number *big.Int) []byte {
numberAsBytes := number.Bytes()
size := len(numberAsBytes)
Expand All @@ -170,3 +272,10 @@ func convertBigToAbiCompatible(number *big.Int) []byte {
}
return bs
}

func convertByteValueToByteSlice(value byte) []byte {
result := make([]byte, 32)
result[len(result)-1] = value

return result
}
2 changes: 2 additions & 0 deletions clients/ethereum/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type ClientWrapper interface {
// Erc20ContractsHolder defines the Ethereum ERC20 contract operations
type Erc20ContractsHolder interface {
BalanceOf(ctx context.Context, erc20Address common.Address, address common.Address) (*big.Int, error)
Decimals(ctx context.Context, erc20Address common.Address) (uint8, error)
IsInterfaceNil() bool
}

Expand Down Expand Up @@ -71,6 +72,7 @@ type SignaturesHolder interface {

type erc20ContractWrapper interface {
BalanceOf(ctx context.Context, account common.Address) (*big.Int, error)
Decimals(ctx context.Context) (uint8, error)
IsInterfaceNil() bool
}

Expand Down
6 changes: 6 additions & 0 deletions clients/ethereum/wrappers/erc20ContractWrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ func (wrapper *erc20ContractWrapper) BalanceOf(ctx context.Context, account comm
return wrapper.erc20Contract.BalanceOf(&bind.CallOpts{Context: ctx}, account)
}

// Decimals returns the ERC20 set decimals for the token
func (wrapper *erc20ContractWrapper) Decimals(ctx context.Context) (uint8, error) {
wrapper.statusHandler.AddIntMetric(core.MetricNumEthClientRequests, 1)
return wrapper.erc20Contract.Decimals(&bind.CallOpts{Context: ctx})
}

// IsInterfaceNil returns true if there is no value under the interface
func (wrapper *erc20ContractWrapper) IsInterfaceNil() bool {
return wrapper == nil
Expand Down
19 changes: 19 additions & 0 deletions clients/ethereum/wrappers/erc20ContractWrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,22 @@ func TestErc20ContractWrapper_BalanceOf(t *testing.T) {
assert.True(t, handlerCalled)
assert.Equal(t, 1, statusHandler.GetIntMetric(core.MetricNumEthClientRequests))
}

func TestErc20ContractWrapper_Decimals(t *testing.T) {
t.Parallel()

args, statusHandler := createMockArgsErc20ContractWrapper()
handlerCalled := false
args.Erc20Contract = &interactors.GenericErc20ContractStub{
DecimalsCalled: func() (uint8, error) {
handlerCalled = true
return 37, nil
},
}
wrapper, _ := NewErc20ContractWrapper(args)
decimals, err := wrapper.Decimals(context.TODO())
assert.Nil(t, err)
assert.Equal(t, byte(37), decimals)
assert.True(t, handlerCalled)
assert.Equal(t, 1, statusHandler.GetIntMetric(core.MetricNumEthClientRequests))
}
1 change: 1 addition & 0 deletions clients/ethereum/wrappers/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

type genericErc20Contract interface {
BalanceOf(opts *bind.CallOpts, account common.Address) (*big.Int, error)
Decimals(opts *bind.CallOpts) (uint8, error)
}

type multiSigContract interface {
Expand Down
10 changes: 5 additions & 5 deletions cmd/migration/config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
Chain = "Ethereum"
NetworkAddress = "http://127.0.0.1:8545" # a network address
PrivateKeyFile = "keys/ethereum.sk" # the path to the file containing the relayer eth private key
MultisigContractAddress = "92266a070ae1eBA4F778781c2177AaF4747Ea1b8"
SafeContractAddress = "29C63343d302564e3695ca14AB31F5eec427Af3E"
MultisigContractAddress = "1Ff78EB04d44a803E73c44FEf8790c5cAbD14596"
SafeContractAddress = "92A26975433A61CF1134802586aa669bAB8B69f3"
GasLimitBase = 350000
GasLimitForEach = 30000
[Eth.GasStation]
Expand All @@ -19,9 +19,9 @@
GasPriceSelector = "SafeGasPrice" # selector used to provide the gas price

[MultiversX]
NetworkAddress = "https://testnet-gateway.multiversx.com" # the network address
MultisigContractAddress = "erd1qqqqqqqqqqqqqpgqe34pfpl27yq9hq79kms9glwc6c8efm5u3kuq02d609"
SafeContractAddress = "erd1qqqqqqqqqqqqqpgq3quw8f6mplxn6up7l5wsre0dm8r9wrds3kuq7axccv"
NetworkAddress = "https://gateway.multiversx.com" # the network address
MultisigContractAddress = "erd1qqqqqqqqqqqqqpgqxexs26vrvhwh2m4he62d6y3jzmv3qkujyfkq8yh4z2"
SafeContractAddress = "erd1qqqqqqqqqqqqqpgqhxkc48lt5uv2hejj4wtjqvugfm4wgv6gyfkqw0uuxl"
[MultiversX.Proxy]
CacherExpirationSeconds = 600 # the caching time in seconds

Expand Down
15 changes: 9 additions & 6 deletions cmd/migration/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ var (
}
mode = cli.StringFlag{
Name: "mode",
Usage: "This flag specifies the operation mode. Usage: sign or execute",
Value: signMode,
Usage: "This flag specifies the operation mode. Usage: query, sign or execute",
Value: queryMode,
}
migrationJsonFile = cli.StringFlag{
Name: "migration-file",
Expand All @@ -43,9 +43,12 @@ var (
Usage: "The new safe address on Ethereum",
Value: "",
}
denominatedAmount = cli.Uint64Flag{
Name: "denominated-amount",
Usage: "The dominated amount that will be used on all deposits. Very useful in an initial test",
partialMigration = cli.StringFlag{
Name: "partial-migration",
Usage: "If a partial migration is wanted, this option can be very handy. We can specify an unlimited tuples in a single string, like this: " +
"`-partial-migration token1:amount1,token2:amount2,token1:amount3` and so on. You can see that the same token can be specified multiple times, " +
"the amounts will be added. The amount should be specified as a denominated value (does not contain all decimals, the conversion will be done " +
"automatically by the tool). Real example: `-partial-migration token1:amount1,token2:amount2,token1:amount3`",
}
)

Expand All @@ -57,7 +60,7 @@ func getFlags() []cli.Flag {
migrationJsonFile,
signatureJsonFile,
newSafeAddress,
denominatedAmount,
partialMigration,
}
}
func getFlagsConfig(ctx *cli.Context) config.ContextFlagsConfig {
Expand Down
15 changes: 15 additions & 0 deletions cmd/migration/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/multiversx/mx-bridge-eth-go/executors/ethereum"
)

// BatchCreator defines the operations implemented by an entity that can create an Ethereum batch message that can be used
// in signing or transfer execution
type BatchCreator interface {
CreateBatchInfo(ctx context.Context, newSafeAddress common.Address, partialMigration map[string]*big.Float) (*ethereum.BatchInfo, error)
}
Loading

0 comments on commit 68403db

Please sign in to comment.