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

Mint burn tokens #266

Merged
merged 11 commits into from
Dec 22, 2023
123 changes: 120 additions & 3 deletions bridges/ethMultiversX/bridgeExecutor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package ethmultiversx
import (
"context"
"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/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 @@ -178,7 +180,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 @@ -403,6 +405,7 @@ func (executor *bridgeExecutor) PerformActionOnMultiversX(ctx context.Context) e
return ErrNilBatch
}

// TODO: check mintBurn balances before performing the action
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO will be implemented in next PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

hash, err := executor.multiversXClient.PerformAction(ctx, executor.actionID, executor.batch)
if err != nil {
return err
Expand Down Expand Up @@ -467,7 +470,12 @@ func (executor *bridgeExecutor) SignTransferOnEthereum() error {
return ErrNilBatch
}

hash, err := executor.ethereumClient.GenerateMessageHash(executor.batch)
argLists, err := batchProcessor.ExtractList(executor.batch)
if err != nil {
return err
}

hash, err := executor.ethereumClient.GenerateMessageHash(argLists, executor.batch.ID)
if err != nil {
return err
}
Expand All @@ -493,7 +501,19 @@ 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, err := batchProcessor.ExtractList(executor.batch)
if err != nil {
return err
}

err = executor.checkAvailableTokensOnEthereum(ctx, argLists.Tokens, argLists.ConvertedTokenBytes, argLists.Amounts)
if err != nil {
return err
}

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 @@ -504,6 +524,103 @@ 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, token common.Address, convertedToken []byte) error {
mintedBalance, err := executor.ethereumClient.TokenMintedBalances(ctx, token)
if err != nil {
return err
}

burntBalance, err := executor.multiversXClient.AccumulatedBurnedTokens(ctx, convertedToken)
if err != nil {
return err
}
if mintedBalance.Cmp(burntBalance) != 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this ok? When will the burn be completed? At the moment of transfer in the mvx safe contract or when the transfer is completed on eth side and marked as finished on mvx?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the transfer is completed on eth side and marked as finished on mvx, thats why it should be equal at the moment of the batch execution

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, should work then

return fmt.Errorf("%w, minted: %s, burnt: %s for ERC20 token %s/ ESDT token %s",
ErrMintBurnBalance, mintedBalance.String(), burntBalance.String(), token.String(), convertedToken)
}
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
}

func (executor *bridgeExecutor) checkAvailableTokensOnEthereum(ctx context.Context, tokens []common.Address, convertedTokens [][]byte, amounts []*big.Int) error {
tokens, convertedTokens, amounts = executor.getCumulatedTransfers(tokens, convertedTokens, amounts)

return executor.checkCumulatedTransfers(ctx, tokens, convertedTokens, amounts)
}

func (executor *bridgeExecutor) getCumulatedTransfers(tokens []common.Address, convertedTokens [][]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 tokens {
existingValue, exists := cumulatedAmounts[token]
if exists {
existingValue.Add(existingValue, amounts[i])
continue
}

cumulatedAmounts[token] = amounts[i]
uniqueTokens = append(uniqueTokens, token)
uniqueConvertedTokens = append(uniqueConvertedTokens, convertedTokens[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
18 changes: 13 additions & 5 deletions bridges/ethMultiversX/bridgeExecutor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/multiversx/mx-bridge-eth-go/clients"
"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 @@ -1083,7 +1084,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 @@ -1100,7 +1101,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 @@ -1153,7 +1154,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 @@ -1176,9 +1177,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].TokenBytes, batch.Tokens[i].Bytes())
assert.Equal(t, providedBatch.Deposits[i].ConvertedTokenBytes, batch.ConvertedTokenBytes[i])
}
assert.True(t, providedQuorum == quorum)

wasCalledExecuteTransferCalled = true
Expand Down
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 @@ -6,6 +6,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/multiversx/mx-bridge-eth-go/clients"
"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 @@ -29,6 +30,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 @@ -37,14 +40,17 @@ 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)
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)
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