Skip to content

Commit

Permalink
Merge pull request #266 from multiversx/mint-burn-tokens
Browse files Browse the repository at this point in the history
Mint burn tokens
  • Loading branch information
iulianpascalau authored Dec 22, 2023
2 parents 32065bf + a8c7e8c commit 0267f24
Show file tree
Hide file tree
Showing 26 changed files with 2,427 additions and 243 deletions.
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
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 {
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

0 comments on commit 0267f24

Please sign in to comment.