diff --git a/e2e-polybft/e2e/bridge_test.go b/e2e-polybft/e2e/bridge_test.go index 97354b2639..89c88be156 100644 --- a/e2e-polybft/e2e/bridge_test.go +++ b/e2e-polybft/e2e/bridge_test.go @@ -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) @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 := ðgo.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) { @@ -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()) diff --git a/e2e-polybft/e2e/helpers_test.go b/e2e-polybft/e2e/helpers_test.go index 2db66a2502..7914709170 100644 --- a/e2e-polybft/e2e/helpers_test.go +++ b/e2e-polybft/e2e/helpers_test.go @@ -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" @@ -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, @@ -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 } @@ -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 +}