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/native esdts bridging #278

Merged
merged 26 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6b6a315
do the balance check on for non mintBurn tokens
dragos-rebegea Nov 15, 2023
48c98ac
Merge remote-tracking branch 'origin/main' into main
dragos-rebegea Nov 15, 2023
b190a3c
add mintBurn check for multiversx2ethereum
dragos-rebegea Nov 28, 2023
6a954c7
- minor test refactoring
iulianpascalau Nov 29, 2023
3f245a3
update clients stub
dragos-rebegea Dec 4, 2023
db6acdc
update dependencies and update stubs
dragos-rebegea Dec 6, 2023
307e0db
fixes after review
dragos-rebegea Dec 7, 2023
d16bb1c
fix linter
dragos-rebegea Dec 7, 2023
dce9bdd
Merge remote-tracking branch 'origin/feat/native-esdts-bridging' into…
dragos-rebegea Dec 20, 2023
6b556f4
fixes after review
dragos-rebegea Dec 20, 2023
d6580c8
fixes after review
dragos-rebegea Dec 20, 2023
a8c7e8c
fixes after review
dragos-rebegea Dec 21, 2023
0267f24
Merge pull request #266 from multiversx/mint-burn-tokens
iulianpascalau Dec 22, 2023
c448f57
Merge branch 'feat/v3' into merge-v3-feat-native-esdts
iulianpascalau Dec 22, 2023
d793ff8
- fixes after merge
iulianpascalau Dec 22, 2023
3172fd0
- linter fixes
iulianpascalau Dec 22, 2023
417bc87
Merge branch 'merge-v3-feat-native-esdts' into mint-burn-tokens-integ…
iulianpascalau Dec 27, 2023
eb5baab
Merge pull request #280 from multiversx/merge-v3-feat-native-esdts
iulianpascalau Dec 27, 2023
e033297
Merge branch 'feat/native-esdts-bridging' into mint-burn-tokens-integ…
iulianpascalau Dec 27, 2023
b2bcf5b
- renamed several fields in the deposit batch
iulianpascalau Dec 27, 2023
24fd052
Merge remote-tracking branch 'origin/batch-refactor' into mint-burn-t…
iulianpascalau Dec 27, 2023
c0316b8
- refactoring the balances checks
iulianpascalau Dec 27, 2023
83b271e
Merge remote-tracking branch 'origin/mint-burn-tokens-integration-tes…
iulianpascalau Dec 27, 2023
8accbcf
Merge pull request #281 from multiversx/batch-refactor
dragos-rebegea Dec 27, 2023
5c3db66
Merge branch 'feat/native-esdts-bridging' into mint-burn-tokens-integ…
iulianpascalau Dec 27, 2023
8ed7c4f
Merge pull request #268 from multiversx/mint-burn-tokens-integration-…
iulianpascalau Dec 29, 2023
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
118 changes: 115 additions & 3 deletions bridges/ethMultiversX/bridgeExecutor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"context"
"encoding/hex"
"fmt"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/multiversx/mx-bridge-eth-go/clients"
"github.com/multiversx/mx-bridge-eth-go/clients/ethereum/contract"
"github.com/multiversx/mx-bridge-eth-go/core"
"github.com/multiversx/mx-bridge-eth-go/core/batchProcessor"
"github.com/multiversx/mx-chain-core-go/core/check"
logger "github.com/multiversx/mx-chain-logger-go"
)
Expand Down Expand Up @@ -182,7 +184,7 @@ func (executor *bridgeExecutor) GetLastExecutedEthBatchIDFromMultiversX(ctx cont
return batchID, err
}

// VerifyLastDepositNonceExecutedOnEthereumBatch will check the deposit nonces from the fetched batch from Ethereum client
// VerifyLastDepositNonceExecutedOnEthereumBatch will check the deposit Nonces from the fetched batch from Ethereum client
func (executor *bridgeExecutor) VerifyLastDepositNonceExecutedOnEthereumBatch(ctx context.Context) error {
if executor.batch == nil {
return ErrNilBatch
Expand Down Expand Up @@ -524,7 +526,8 @@ func (executor *bridgeExecutor) SignTransferOnEthereum() error {
return ErrNilBatch
}

hash, err := executor.ethereumClient.GenerateMessageHash(executor.batch)
argLists := batchProcessor.ExtractListMvxToEth(executor.batch)
hash, err := executor.ethereumClient.GenerateMessageHash(argLists, executor.batch.ID)
if err != nil {
return err
}
Expand All @@ -550,7 +553,11 @@ func (executor *bridgeExecutor) PerformTransferOnEthereum(ctx context.Context) e

executor.log.Debug("fetched quorum size", "quorum", quorumSize.Int64())

hash, err := executor.ethereumClient.ExecuteTransfer(ctx, executor.msgHash, executor.batch, int(quorumSize.Int64()))
argLists := batchProcessor.ExtractListMvxToEth(executor.batch)

executor.log.Info("executing transfer " + executor.batch.String())

hash, err := executor.ethereumClient.ExecuteTransfer(ctx, executor.msgHash, argLists, executor.batch.ID, int(quorumSize.Int64()))
if err != nil {
return err
}
Expand All @@ -561,6 +568,111 @@ func (executor *bridgeExecutor) PerformTransferOnEthereum(ctx context.Context) e
return nil
}

func (executor *bridgeExecutor) checkCumulatedTransfers(ctx context.Context, tokens []common.Address, convertedTokens [][]byte, amounts []*big.Int) error {
for i, token := range tokens {
err := executor.checkToken(ctx, token, convertedTokens[i], amounts[i])
if err != nil {
return err
}
}
return nil
}

func (executor *bridgeExecutor) checkToken(ctx context.Context, token common.Address, convertedToken []byte, amount *big.Int) error {
isMintBurnToken, err := executor.isMintBurnToken(ctx, token, convertedToken)
if err != nil {
return err
}

if isMintBurnToken {
return executor.checkRequiredMintBurnBalance(ctx, token, convertedToken)
}

return executor.ethereumClient.CheckRequiredBalance(ctx, token, amount)
}

func (executor *bridgeExecutor) isMintBurnToken(ctx context.Context, token common.Address, convertedToken []byte) (bool, error) {
isMintBurnOnEthereum := executor.isMintBurnOnEthereum(ctx, token)
isMintBurnOnMultiversX := executor.isMintBurnOnMultiversX(ctx, convertedToken)
if isMintBurnOnEthereum != isMintBurnOnMultiversX {
return false, fmt.Errorf("%w isMintBurnOnEthereum = %v, isMintBurnOnMultiversX = %v", ErrInvalidSetupMintBurnToken, isMintBurnOnEthereum, isMintBurnOnMultiversX)
}
return isMintBurnOnEthereum, nil
}

func (executor *bridgeExecutor) checkRequiredMintBurnBalance(ctx context.Context, ethToken common.Address, mvxToken []byte) error {
ethBalance, err := executor.ethereumClient.TokenMintedBalances(ctx, ethToken)
if err != nil {
return err
}

mvxBalance, err := executor.multiversXClient.AccumulatedBurnedTokens(ctx, mvxToken)
if err != nil {
return err
}
if ethBalance.Cmp(mvxBalance) > 0 {
return fmt.Errorf("%w, balance for ERC20 token %s is %s and the balance for ESDT token %s is %s",
ErrMintBurnBalance, ethToken.String(), ethBalance.String(), mvxToken, mvxBalance.String())
}

executor.log.Debug("bridgeExecutor.checkRequiredMintBurnBalance",
"ERC20 token", ethToken.String(),
"ERC20 native balance", ethBalance.String(),
"ESDT token", mvxToken,
"ESDT native balance", mvxBalance.String(),
)

return nil
}

func (executor *bridgeExecutor) isMintBurnOnEthereum(ctx context.Context, erc20Address common.Address) bool {
isMintBurn, err := executor.ethereumClient.WhitelistedTokensMintBurn(ctx, erc20Address)
if err != nil {
return false
}
return isMintBurn
}

func (executor *bridgeExecutor) isMintBurnOnMultiversX(ctx context.Context, token []byte) bool {
isMintBurn, err := executor.multiversXClient.IsMintBurnAllowed(ctx, token)
if err != nil {
return false
}
return isMintBurn
}

// CheckAvailableTokens checks the available balances handling also the native tokens
func (executor *bridgeExecutor) CheckAvailableTokens(ctx context.Context, ethTokens []common.Address, mvxTokens [][]byte, amounts []*big.Int) error {
ethTokens, mvxTokens, amounts = executor.getCumulatedTransfers(ethTokens, mvxTokens, amounts)

return executor.checkCumulatedTransfers(ctx, ethTokens, mvxTokens, amounts)
}

func (executor *bridgeExecutor) getCumulatedTransfers(ethTokens []common.Address, mvxTokens [][]byte, amounts []*big.Int) ([]common.Address, [][]byte, []*big.Int) {
cumulatedAmounts := make(map[common.Address]*big.Int)
uniqueTokens := make([]common.Address, 0)
uniqueConvertedTokens := make([][]byte, 0)

for i, token := range ethTokens {
existingValue, exists := cumulatedAmounts[token]
if exists {
existingValue.Add(existingValue, amounts[i])
continue
}

cumulatedAmounts[token] = amounts[i]
uniqueTokens = append(uniqueTokens, token)
uniqueConvertedTokens = append(uniqueConvertedTokens, mvxTokens[i])
}

finalAmounts := make([]*big.Int, len(uniqueTokens))
for i, token := range uniqueTokens {
finalAmounts[i] = cumulatedAmounts[token]
}

return uniqueTokens, uniqueConvertedTokens, finalAmounts
}

// ProcessQuorumReachedOnEthereum returns true if the proposed transfer reached the set quorum
func (executor *bridgeExecutor) ProcessQuorumReachedOnEthereum(ctx context.Context) (bool, error) {
return executor.ethereumClient.IsQuorumReached(ctx, executor.msgHash)
Expand Down
149 changes: 144 additions & 5 deletions bridges/ethMultiversX/bridgeExecutor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/multiversx/mx-bridge-eth-go/clients"
"github.com/multiversx/mx-bridge-eth-go/clients/ethereum/contract"
"github.com/multiversx/mx-bridge-eth-go/core"
"github.com/multiversx/mx-bridge-eth-go/core/batchProcessor"
"github.com/multiversx/mx-bridge-eth-go/testsCommon"
bridgeTests "github.com/multiversx/mx-bridge-eth-go/testsCommon/bridge"
"github.com/multiversx/mx-chain-core-go/core/check"
Expand Down Expand Up @@ -1154,7 +1155,7 @@ func TestMultiversXToEthBridgeExecutor_SignTransferOnEthereum(t *testing.T) {

args := createMockExecutorArgs()
args.EthereumClient = &bridgeTests.EthereumClientStub{
GenerateMessageHashCalled: func(batch *clients.TransferBatch) (common.Hash, error) {
GenerateMessageHashCalled: func(batch *batchProcessor.ArgListsBatch, batchID uint64) (common.Hash, error) {
return common.Hash{}, expectedErr
},
}
Expand All @@ -1171,7 +1172,7 @@ func TestMultiversXToEthBridgeExecutor_SignTransferOnEthereum(t *testing.T) {
wasCalledBroadcastSignatureForMessageHashCalled := false
args := createMockExecutorArgs()
args.EthereumClient = &bridgeTests.EthereumClientStub{
GenerateMessageHashCalled: func(batch *clients.TransferBatch) (common.Hash, error) {
GenerateMessageHashCalled: func(batch *batchProcessor.ArgListsBatch, batchID uint64) (common.Hash, error) {
wasCalledGenerateMessageHashCalled = true
return common.Hash{}, nil
},
Expand Down Expand Up @@ -1224,7 +1225,7 @@ func TestMultiversXToEthBridgeExecutor_PerformTransferOnEthereum(t *testing.T) {
GetQuorumSizeCalled: func(ctx context.Context) (*big.Int, error) {
return big.NewInt(0), nil
},
ExecuteTransferCalled: func(ctx context.Context, msgHash common.Hash, batch *clients.TransferBatch, quorum int) (string, error) {
ExecuteTransferCalled: func(ctx context.Context, msgHash common.Hash, batch *batchProcessor.ArgListsBatch, batchId uint64, quorum int) (string, error) {
return "", expectedErr
},
}
Expand All @@ -1247,9 +1248,16 @@ func TestMultiversXToEthBridgeExecutor_PerformTransferOnEthereum(t *testing.T) {
wasCalledGetQuorumSizeCalled = true
return big.NewInt(int64(providedQuorum)), nil
},
ExecuteTransferCalled: func(ctx context.Context, msgHash common.Hash, batch *clients.TransferBatch, quorum int) (string, error) {
ExecuteTransferCalled: func(ctx context.Context, msgHash common.Hash, batch *batchProcessor.ArgListsBatch, batchId uint64, quorum int) (string, error) {
assert.True(t, providedHash == msgHash)
assert.True(t, providedBatch == batch)
assert.True(t, providedBatch.ID == batchId)
for i := 0; i < len(providedBatch.Deposits); i++ {
assert.Equal(t, providedBatch.Deposits[i].Amount, batch.Amounts[i])
assert.Equal(t, providedBatch.Deposits[i].Nonce, batch.Nonces[i].Uint64())
assert.Equal(t, providedBatch.Deposits[i].ToBytes, batch.Recipients[i].Bytes())
assert.Equal(t, providedBatch.Deposits[i].SourceTokenBytes, batch.EthTokens[i].Bytes())
assert.Equal(t, providedBatch.Deposits[i].DestinationTokenBytes, batch.MvxTokenBytes[i])
}
assert.True(t, providedQuorum == quorum)

wasCalledExecuteTransferCalled = true
Expand Down Expand Up @@ -1726,3 +1734,134 @@ func TestBridgeExecutor_ValidateBatch(t *testing.T) {
assert.True(t, result)
assert.True(t, validateBatchCalled)
}

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

t.Run("wrong balances should error", func(t *testing.T) {
args := createMockExecutorArgs()
tokenMintedBalancesCalled := false
args.EthereumClient = &bridgeTests.EthereumClientStub{
WhitelistedTokensMintBurnCalled: func(ctx context.Context, token common.Address) (bool, error) {
return true, nil
},
TokenMintedBalancesCalled: func(ctx context.Context, token common.Address) (*big.Int, error) {
tokenMintedBalancesCalled = true

return big.NewInt(3701), nil // eth holds a higher balance than the mvx
},
}

accumulatedBurnedTokensCalled := false
args.MultiversXClient = &bridgeTests.MultiversXClientStub{
IsMintBurnAllowedCalled: func(ctx context.Context, token []byte) (bool, error) {
return true, nil
},
AccumulatedBurnedTokensCalled: func(ctx context.Context, token []byte) (*big.Int, error) {
accumulatedBurnedTokensCalled = true
return big.NewInt(3700), nil
},
}

executor, _ := NewBridgeExecutor(args)

err := executor.CheckAvailableTokens(
context.Background(),
[]common.Address{
common.BytesToAddress([]byte("eth token")),
},
[][]byte{
[]byte("mvx token"),
},
[]*big.Int{
big.NewInt(1),
})
assert.ErrorIs(t, err, ErrMintBurnBalance)
assert.True(t, tokenMintedBalancesCalled)
assert.True(t, accumulatedBurnedTokensCalled)
})
t.Run("should work with the same balances", func(t *testing.T) {
args := createMockExecutorArgs()
tokenMintedBalancesCalled := false
args.EthereumClient = &bridgeTests.EthereumClientStub{
WhitelistedTokensMintBurnCalled: func(ctx context.Context, token common.Address) (bool, error) {
return true, nil
},
TokenMintedBalancesCalled: func(ctx context.Context, token common.Address) (*big.Int, error) {
tokenMintedBalancesCalled = true

return big.NewInt(3700), nil
},
}

accumulatedBurnedTokensCalled := false
args.MultiversXClient = &bridgeTests.MultiversXClientStub{
IsMintBurnAllowedCalled: func(ctx context.Context, token []byte) (bool, error) {
return true, nil
},
AccumulatedBurnedTokensCalled: func(ctx context.Context, token []byte) (*big.Int, error) {
accumulatedBurnedTokensCalled = true
return big.NewInt(3700), nil
},
}

executor, _ := NewBridgeExecutor(args)

err := executor.CheckAvailableTokens(
context.Background(),
[]common.Address{
common.BytesToAddress([]byte("eth token")),
},
[][]byte{
[]byte("mvx token"),
},
[]*big.Int{
big.NewInt(1),
})
assert.Nil(t, err)
assert.True(t, tokenMintedBalancesCalled)
assert.True(t, accumulatedBurnedTokensCalled)
})
t.Run("should work with higher values on the mvx than eth", func(t *testing.T) {
args := createMockExecutorArgs()
tokenMintedBalancesCalled := false
args.EthereumClient = &bridgeTests.EthereumClientStub{
WhitelistedTokensMintBurnCalled: func(ctx context.Context, token common.Address) (bool, error) {
return true, nil
},
TokenMintedBalancesCalled: func(ctx context.Context, token common.Address) (*big.Int, error) {
tokenMintedBalancesCalled = true

return big.NewInt(3699), nil // eth holds less than mvx
},
}

accumulatedBurnedTokensCalled := false
args.MultiversXClient = &bridgeTests.MultiversXClientStub{
IsMintBurnAllowedCalled: func(ctx context.Context, token []byte) (bool, error) {
return true, nil
},
AccumulatedBurnedTokensCalled: func(ctx context.Context, token []byte) (*big.Int, error) {
accumulatedBurnedTokensCalled = true
return big.NewInt(3700), nil
},
}

executor, _ := NewBridgeExecutor(args)

err := executor.CheckAvailableTokens(
context.Background(),
[]common.Address{
common.BytesToAddress([]byte("eth token")),
},
[][]byte{
[]byte("mvx token"),
},
[]*big.Int{
big.NewInt(1),
})
assert.Nil(t, err)
assert.True(t, tokenMintedBalancesCalled)
assert.True(t, accumulatedBurnedTokensCalled)
})
}
6 changes: 6 additions & 0 deletions bridges/ethMultiversX/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ var ErrNilSignaturesHolder = errors.New("nil signatures holder")

// ErrNilBatchValidator signals that a nil batch validator was provided
var ErrNilBatchValidator = errors.New("nil batch validator")

// ErrInvalidSetupMintBurnToken signals that an invalid setup mint burn token was provided
var ErrInvalidSetupMintBurnToken = errors.New("invalid setup mint burn token")

// ErrMintBurnBalance signals that the mint burn balances are not expected
var ErrMintBurnBalance = errors.New("mint burn balances are not expected")
10 changes: 8 additions & 2 deletions bridges/ethMultiversX/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/multiversx/mx-bridge-eth-go/clients"
"github.com/multiversx/mx-bridge-eth-go/clients/ethereum/contract"
"github.com/multiversx/mx-bridge-eth-go/core/batchProcessor"
)

// MultiversXClient defines the behavior of the MultiversX client able to communicate with the MultiversX chain
Expand All @@ -30,6 +31,8 @@ type MultiversXClient interface {
WasSigned(ctx context.Context, actionID uint64) (bool, error)
PerformAction(ctx context.Context, actionID uint64, batch *clients.TransferBatch) (string, error)
CheckClientAvailability(ctx context.Context) error
IsMintBurnAllowed(ctx context.Context, token []byte) (bool, error)
AccumulatedBurnedTokens(ctx context.Context, token []byte) (*big.Int, error)
Close() error
IsInterfaceNil() bool
}
Expand All @@ -38,16 +41,19 @@ type MultiversXClient interface {
type EthereumClient interface {
GetBatch(ctx context.Context, nonce uint64) (*clients.TransferBatch, error)
WasExecuted(ctx context.Context, batchID uint64) (bool, error)
GenerateMessageHash(batch *clients.TransferBatch) (common.Hash, error)
IsDepositSCCall(deposit *clients.DepositTransfer) bool
GenerateMessageHash(batch *batchProcessor.ArgListsBatch, batchId uint64) (common.Hash, error)

BroadcastSignatureForMessageHash(msgHash common.Hash)
ExecuteTransfer(ctx context.Context, msgHash common.Hash, batch *clients.TransferBatch, quorum int) (string, error)
ExecuteTransfer(ctx context.Context, msgHash common.Hash, batch *batchProcessor.ArgListsBatch, batchId uint64, quorum int) (string, error)
GetTransactionsStatuses(ctx context.Context, batchId uint64) ([]byte, error)
GetQuorumSize(ctx context.Context) (*big.Int, error)
IsQuorumReached(ctx context.Context, msgHash common.Hash) (bool, error)
GetBatchSCMetadata(ctx context.Context, nonce uint64) ([]*contract.SCExecProxyERC20SCDeposit, error)
CheckClientAvailability(ctx context.Context) error
CheckRequiredBalance(ctx context.Context, erc20Address common.Address, value *big.Int) error
TokenMintedBalances(ctx context.Context, token common.Address) (*big.Int, error)
WhitelistedTokensMintBurn(ctx context.Context, token common.Address) (bool, error)
IsInterfaceNil() bool
}

Expand Down
Loading