Skip to content

Commit

Permalink
Implement non-native tokens bridge e2e test (0xPolygon#1746)
Browse files Browse the repository at this point in the history
  • Loading branch information
begmaroman authored Jul 28, 2023
1 parent 19d140a commit af37011
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 82 deletions.
247 changes: 184 additions & 63 deletions e2e-polybft/e2e/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,20 @@ func TestE2E_Bridge_Transfers(t *testing.T) {
sprintSize = uint64(5)
)

receiversAddrs := make([]types.Address, transfersCount)
receivers := make([]string, transfersCount)
amounts := make([]string, transfersCount)
receiverKeys := make([]string, transfersCount)

for i := 0; i < transfersCount; i++ {
key, err := ethgow.GenerateKey()
require.NoError(t, err)

rawKey, err := key.MarshallPrivateKey()
require.NoError(t, err)

receiverKeys[i] = hex.EncodeToString(rawKey)
receiversAddrs[i] = types.Address(key.Address())
receivers[i] = types.Address(key.Address()).String()
amounts[i] = fmt.Sprintf("%d", amount)

Expand All @@ -73,18 +80,17 @@ func TestE2E_Bridge_Transfers(t *testing.T) {
// bridge some tokens for first validator to child chain
tokensToDeposit := ethgo.Ether(10)

require.NoError(
t, cluster.Bridge.Deposit(
common.ERC20,
polybftCfg.Bridge.RootNativeERC20Addr,
polybftCfg.Bridge.RootERC20PredicateAddr,
rootHelper.TestAccountPrivKey,
senderAccount.Address().String(),
tokensToDeposit.String(),
"",
cluster.Bridge.JSONRPCAddr(),
rootHelper.TestAccountPrivKey,
false),
require.NoError(t, cluster.Bridge.Deposit(
common.ERC20,
polybftCfg.Bridge.RootNativeERC20Addr,
polybftCfg.Bridge.RootERC20PredicateAddr,
rootHelper.TestAccountPrivKey,
senderAccount.Address().String(),
tokensToDeposit.String(),
"",
cluster.Bridge.JSONRPCAddr(),
rootHelper.TestAccountPrivKey,
false),
)

// wait for a couple of sprints
Expand All @@ -108,28 +114,24 @@ func TestE2E_Bridge_Transfers(t *testing.T) {
t.Run("bridge ERC 20 tokens", func(t *testing.T) {
// DEPOSIT ERC20 TOKENS
// send a few transactions to the bridge
require.NoError(
t,
cluster.Bridge.Deposit(
common.ERC20,
polybftCfg.Bridge.RootNativeERC20Addr,
polybftCfg.Bridge.RootERC20PredicateAddr,
rootHelper.TestAccountPrivKey,
strings.Join(receivers[:], ","),
strings.Join(amounts[:], ","),
"",
cluster.Bridge.JSONRPCAddr(),
rootHelper.TestAccountPrivKey,
false),
)
require.NoError(t, cluster.Bridge.Deposit(
common.ERC20,
polybftCfg.Bridge.RootNativeERC20Addr,
polybftCfg.Bridge.RootERC20PredicateAddr,
rootHelper.TestAccountPrivKey,
strings.Join(receivers[:], ","),
strings.Join(amounts[:], ","),
"",
cluster.Bridge.JSONRPCAddr(),
rootHelper.TestAccountPrivKey,
false,
))

finalBlockNum := 10 * sprintSize
// wait for a couple of sprints
require.NoError(t, cluster.WaitForBlock(finalBlockNum, 2*time.Minute))

// the transactions are processed and there should be a success events
var stateSyncedResult contractsapi.StateSyncResultEvent

logs, err := getFilteredLogs(stateSyncedResult.Sig(), 0, finalBlockNum, childEthEndpoint)
require.NoError(t, err)

Expand Down Expand Up @@ -228,19 +230,17 @@ func TestE2E_Bridge_Transfers(t *testing.T) {
require.NoError(t, cluster.WaitForBlock(initialBlockNum, 1*time.Minute))

// send two transactions to the bridge so that we have a minimal commitment
require.NoError(
t,
cluster.Bridge.Deposit(
common.ERC20,
polybftCfg.Bridge.RootNativeERC20Addr,
polybftCfg.Bridge.RootERC20PredicateAddr,
rootHelper.TestAccountPrivKey,
strings.Join(receivers[:depositsSubset], ","),
strings.Join(amounts[:depositsSubset], ","),
"",
cluster.Bridge.JSONRPCAddr(),
rootHelper.TestAccountPrivKey,
false),
require.NoError(t, cluster.Bridge.Deposit(
common.ERC20,
polybftCfg.Bridge.RootNativeERC20Addr,
polybftCfg.Bridge.RootERC20PredicateAddr,
rootHelper.TestAccountPrivKey,
strings.Join(receivers[:depositsSubset], ","),
strings.Join(amounts[:depositsSubset], ","),
"",
cluster.Bridge.JSONRPCAddr(),
rootHelper.TestAccountPrivKey,
false),
)

// wait for a few more sprints
Expand All @@ -257,19 +257,17 @@ func TestE2E_Bridge_Transfers(t *testing.T) {
require.Equal(t, initialCommittedID+depositsSubset, lastCommittedID)

// send some more transactions to the bridge to build another commitment in epoch
require.NoError(
t,
cluster.Bridge.Deposit(
common.ERC20,
polybftCfg.Bridge.RootNativeERC20Addr,
polybftCfg.Bridge.RootERC20PredicateAddr,
rootHelper.TestAccountPrivKey,
strings.Join(receivers[depositsSubset:], ","),
strings.Join(amounts[depositsSubset:], ","),
"",
cluster.Bridge.JSONRPCAddr(),
rootHelper.TestAccountPrivKey,
false),
require.NoError(t, cluster.Bridge.Deposit(
common.ERC20,
polybftCfg.Bridge.RootNativeERC20Addr,
polybftCfg.Bridge.RootERC20PredicateAddr,
rootHelper.TestAccountPrivKey,
strings.Join(receivers[depositsSubset:], ","),
strings.Join(amounts[depositsSubset:], ","),
"",
cluster.Bridge.JSONRPCAddr(),
rootHelper.TestAccountPrivKey,
false),
)

finalBlockNum := midBlockNumber + 5*sprintSize
Expand All @@ -287,14 +285,142 @@ func TestE2E_Bridge_Transfers(t *testing.T) {

// the transactions are mined and state syncs should be executed by the relayer
// and there should be a success events
var stateSyncedResult contractsapi.StateSyncResultEvent

logs, err := getFilteredLogs(stateSyncedResult.Sig(), initialBlockNum, finalBlockNum, childEthEndpoint)
require.NoError(t, err)

// assert that all state syncs are executed successfully
checkStateSyncResultLogs(t, logs, transfersCount)
})

t.Run("non native ERC20 deposit and withdraw", func(t *testing.T) {
rootchainTxRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(cluster.Bridge.JSONRPCAddr()))
require.NoError(t, err)

childchainTxRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(validatorSrv.JSONRPC()))
require.NoError(t, err)

// Deploy ERC20 contract to the rootchain
rootchainDeployer, err := rootHelper.DecodePrivateKey("")
require.NoError(t, err)

txn := &ethgo.Transaction{To: nil, Input: contractsapi.RootERC20.Bytecode}
receipt, err := rootchainTxRelayer.SendTransaction(txn, rootchainDeployer)
require.NoError(t, err)
require.NotNil(t, receipt)
require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status)

rootTokenAddr := receipt.ContractAddress

t.Log("Rootchain token address:", rootTokenAddr)

// wait for next sprint block as the starting point,
// in order to be able to make assertions against blocks offsetted by sprints
initialBlockNum, err := childEthEndpoint.BlockNumber()
require.NoError(t, err)

initialBlockNum = initialBlockNum + sprintSize - (initialBlockNum % sprintSize)
require.NoError(t, cluster.WaitForBlock(initialBlockNum, 1*time.Minute))

t.Log("Initial block number:", initialBlockNum)

// send a few transactions to the bridge
require.NoError(t, cluster.Bridge.Deposit(
common.ERC20,
types.Address(rootTokenAddr),
polybftCfg.Bridge.RootERC20PredicateAddr,
rootHelper.TestAccountPrivKey,
strings.Join(receivers[:], ","),
strings.Join(amounts[:], ","),
"",
cluster.Bridge.JSONRPCAddr(),
rootHelper.TestAccountPrivKey,
false,
))

// wait for a few more sprints
finalBlockNumber := initialBlockNum + 10*sprintSize
require.NoError(t, cluster.WaitForBlock(finalBlockNumber, 2*time.Minute))

t.Log("Final block number:", finalBlockNumber)

// the transactions are processed and there should be a success events
logs, err := getFilteredLogs(stateSyncedResult.Sig(), initialBlockNum, finalBlockNumber, childEthEndpoint)
require.NoError(t, err)

// assert that all deposits are executed successfully
// map token action is executed along with the first deposit transaction
checkStateSyncResultLogs(t, logs, transfersCount+1)

// retrieve child token address
childTokenAddr := getChildToken(
t,
contractsapi.ChildERC20Predicate.Abi,
contracts.ChildERC20PredicateContract,
types.Address(rootTokenAddr),
childchainTxRelayer,
)

t.Log("Childchain token address:", childTokenAddr)

// check receivers balances got increased by deposited amount
for _, receiver := range receiversAddrs {
balance := erc20BalanceOf(t, receiver, childTokenAddr, childchainTxRelayer)
require.Equal(t, big.NewInt(int64(amount)), balance)
}

t.Log("Deposits were successfully processed")

// get initial exit id
initialExitEventID := getLastExitEventID(t, childchainTxRelayer)

// WITHDRAW ERC20 TOKENS
for i, receiverKey := range receiverKeys {
t.Logf("Withdraw to: %s\n", receivers[i])

require.NoError(t, cluster.Bridge.Withdraw(
common.ERC20,
receiverKey,
receivers[i],
amounts[i],
"",
validatorSrv.JSONRPCAddr(),
contracts.ChildERC20PredicateContract,
childTokenAddr,
false,
))
}

currentBlock, err := childEthEndpoint.GetBlockByNumber(ethgo.Latest, false)
require.NoError(t, err)

currentExtra, err := polybft.GetIbftExtra(currentBlock.ExtraData)
require.NoError(t, err)

t.Logf("Latest block number: %d, epoch number: %d\n", currentBlock.Number, currentExtra.Checkpoint.EpochNumber)

require.NoError(t, waitForRootchainEpoch(
currentExtra.Checkpoint.EpochNumber+1,
3*time.Minute,
rootchainTxRelayer,
polybftCfg.Bridge.CheckpointManagerAddr,
))

exitHelper := polybftCfg.Bridge.ExitHelperAddr
childJSONRPC := validatorSrv.JSONRPCAddr()

initialExitEventID++
for i := initialExitEventID; i < initialExitEventID+transfersCount; i++ {
// send exit transaction to exit helper
err = cluster.Bridge.SendExitTransaction(exitHelper, i, childJSONRPC)
require.NoError(t, err)
}

// assert that receiver's balances on RootERC20 smart contract are expected
for _, receiver := range receivers {
balance := erc20BalanceOf(t, types.StringToAddress(receiver), types.Address(rootTokenAddr), rootchainTxRelayer)
require.Equal(t, big.NewInt(amount), balance)
}
})
}

func TestE2E_Bridge_ERC721Transfer(t *testing.T) {
Expand Down Expand Up @@ -819,13 +945,8 @@ func TestE2E_Bridge_ChildChainMintableTokensTransfer(t *testing.T) {
rootchainInitialBlock, err := rootchainTxRelayer.Client().Eth().BlockNumber()
require.NoError(t, err)

exitEventsCounterFn := contractsapi.L2StateSender.Abi.Methods["counter"]
input, err := exitEventsCounterFn.Encode([]interface{}{})
require.NoError(t, err)
initialExitEventIDRaw, err := childchainTxRelayer.Call(ethgo.ZeroAddress, ethgo.Address(contracts.L2StateSenderContract), input)
require.NoError(t, err)
initialExitEventID, err := types.ParseUint64orHex(&initialExitEventIDRaw)
require.NoError(t, err)
// get initial exit id
initialExitEventID := getLastExitEventID(t, childchainTxRelayer)

erc721DeployTxn := cluster.Deploy(t, admin, contractsapi.RootERC721.Bytecode)
require.NoError(t, erc721DeployTxn.Wait())
Expand Down
37 changes: 18 additions & 19 deletions e2e-polybft/e2e/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/umbracle/ethgo"
"github.com/umbracle/ethgo/abi"
"github.com/umbracle/ethgo/contract"
"github.com/umbracle/ethgo/jsonrpc"

"github.com/0xPolygon/polygon-edge/consensus/polybft"
Expand All @@ -31,23 +30,6 @@ import (

const nativeTokenMintableTestCfg = "Mintable Edge Coin:MEC:18:true:%s"

type e2eStateProvider struct {
txRelayer txrelayer.TxRelayer
}

func (s *e2eStateProvider) Call(contractAddr ethgo.Address, input []byte, opts *contract.CallOpts) ([]byte, error) {
response, err := s.txRelayer.Call(ethgo.Address(types.ZeroAddress), contractAddr, input)
if err != nil {
return nil, err
}

return hex.DecodeHex(response)
}

func (s *e2eStateProvider) Txn(ethgo.Address, ethgo.Key, []byte) (contract.Txn, error) {
return nil, errors.New("send txn is not supported")
}

// getCheckpointManagerValidators queries rootchain validator set on CheckpointManager contract
func getCheckpointManagerValidators(relayer txrelayer.TxRelayer, checkpointManagerAddr ethgo.Address) ([]*polybft.ValidatorInfo, error) {
validatorsCountRaw, err := ABICall(relayer, contractsapi.CheckpointManager,
Expand Down Expand Up @@ -217,7 +199,7 @@ func waitForRootchainEpoch(targetEpoch uint64, timeout time.Duration,
return err
}

rootchainEpoch, err := types.ParseUint64orHex(&rootchainEpochRaw)
rootchainEpoch, err := common.ParseUint64orHex(&rootchainEpochRaw)
if err != nil {
return err
}
Expand Down Expand Up @@ -348,3 +330,20 @@ func getChildToken(t *testing.T, predicateABI *abi.ABI, predicateAddr types.Addr

return types.StringToAddress(childTokenRaw)
}

func getLastExitEventID(t *testing.T, relayer txrelayer.TxRelayer) uint64 {
t.Helper()

exitEventsCounterFn := contractsapi.L2StateSender.Abi.Methods["counter"]

input, err := exitEventsCounterFn.Encode([]interface{}{})
require.NoError(t, err)

exitEventIDRaw, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(contracts.L2StateSenderContract), input)
require.NoError(t, err)

exitEventID, err := common.ParseUint64orHex(&exitEventIDRaw)
require.NoError(t, err)

return exitEventID
}

0 comments on commit af37011

Please sign in to comment.