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(ton): adjacent TON tasks #3075

Merged
merged 13 commits into from
Nov 5, 2024
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
* [2894](https://github.com/zeta-chain/node/pull/2894) - increase gas limit for TSS vote tx
* [2932](https://github.com/zeta-chain/node/pull/2932) - add gateway upgrade as part of the upgrade test
* [2947](https://github.com/zeta-chain/node/pull/2947) - initialize simulation tests
* [3075](https://github.com/zeta-chain/node/pull/3075) - ton: withdraw concurrent, deposit & revert.
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
* [3033](https://github.com/zeta-chain/node/pull/3033) - initialize simulation tests for import and export

### Fixes
Expand Down
2 changes: 2 additions & 0 deletions cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,9 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
tonTests := []string{
e2etests.TestTONDepositName,
e2etests.TestTONDepositAndCallName,
e2etests.TestTONDepositRefundName,
e2etests.TestTONWithdrawName,
e2etests.TestTONWithdrawConcurrentName,
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
}

eg.Go(tonTestRoutine(conf, deployerRunner, verbose, tonTests...))
Expand Down
22 changes: 19 additions & 3 deletions e2e/e2etests/e2etests.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ const (
/**
* TON tests
*/
TestTONDepositName = "ton_deposit"
TestTONDepositAndCallName = "ton_deposit_and_call"
TestTONWithdrawName = "ton_withdraw"
TestTONDepositName = "ton_deposit"
TestTONDepositAndCallName = "ton_deposit_and_call"
TestTONDepositRefundName = "ton_deposit_refund"
TestTONWithdrawName = "ton_withdraw"
TestTONWithdrawConcurrentName = "ton_withdraw_concurrent"
swift1337 marked this conversation as resolved.
Show resolved Hide resolved

/*
Bitcoin tests
Expand Down Expand Up @@ -472,6 +474,14 @@ var AllE2ETests = []runner.E2ETest{
},
TestTONDepositAndCall,
),
runner.NewE2ETest(
TestTONDepositRefundName,
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
"deposit TON into ZEVM; expect refund",
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
[]runner.ArgDefinition{
{Description: "amount in nano tons", DefaultValue: "1000000000"}, // 1.0 TON
},
TestTONDepositAndCallRefund,
),
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
runner.NewE2ETest(
TestTONWithdrawName,
"withdraw TON from ZEVM",
Expand All @@ -480,6 +490,12 @@ var AllE2ETests = []runner.E2ETest{
},
TestTONWithdraw,
),
runner.NewE2ETest(
TestTONWithdrawConcurrentName,
"withdraw TON from ZEVM for several recipients simultaneously",
[]runner.ArgDefinition{},
TestTONWithdrawConcurrent,
),
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
/*
Bitcoin tests
*/
Expand Down
50 changes: 50 additions & 0 deletions e2e/e2etests/test_ton_deposit_refund.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package e2etests

import (
"github.com/stretchr/testify/require"

"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
testcontract "github.com/zeta-chain/node/testutil/contracts"
cctypes "github.com/zeta-chain/node/x/crosschain/types"
)

func TestTONDepositAndCallRefund(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

// Given amount and arbitrary call data
var (
amount = parseUint(r, args[0])
data = []byte("hello reverter")
)
swift1337 marked this conversation as resolved.
Show resolved Hide resolved

// Given deployer mock revert contract
// deploy a reverter contract in ZEVM
reverterAddr, _, _, err := testcontract.DeployReverter(r.ZEVMAuth, r.ZEVMClient)
require.NoError(r, err)
r.Logger.Info("Reverter contract deployed at: %s", reverterAddr.String())
swift1337 marked this conversation as resolved.
Show resolved Hide resolved

// ACT
// Send a deposit and call transaction from the deployer (faucet)
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
// to the reverter contract
cctx, err := r.TONDepositAndCall(
&r.TONDeployer.Wallet,
amount,
reverterAddr,
data,
runner.TONExpectStatus(cctypes.CctxStatus_Reverted),
)

// ASSERT
require.NoError(r, err)
r.Logger.CCTX(*cctx, "ton_deposit_and_refund")

// Check the error carries the revert executed.
// tolerate the error in both the ErrorMessage field and the StatusMessage field
if cctx.CctxStatus.ErrorMessage != "" {
require.Contains(r, cctx.CctxStatus.ErrorMessage, "revert executed")
return
}

require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevertFoo)
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
}
7 changes: 2 additions & 5 deletions e2e/e2etests/test_ton_withdrawal.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ import (
"github.com/zeta-chain/node/zetaclient/chains/ton/liteapi"
)

// TODO: Add "withdraw_many_concurrent" test
// https://github.com/zeta-chain/node/issues/3044

func TestTONWithdraw(r *runner.E2ERunner, args []string) {
// ARRANGE
require.Len(r, args, 1)
Expand All @@ -34,7 +31,7 @@ func TestTONWithdraw(r *runner.E2ERunner, args []string) {
tonRecipient, err := deployer.CreateWallet(r.Ctx, toncontracts.Coins(1))
require.NoError(r, err)

tonRecipientBalanceBefore, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress())
tonRecipientBalanceBefore, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress(), true)
require.NoError(r, err)

r.Logger.Info("Recipient's TON balance before withdrawal: %s", toncontracts.FormatCoins(tonRecipientBalanceBefore))
Expand All @@ -61,7 +58,7 @@ func TestTONWithdraw(r *runner.E2ERunner, args []string) {
)

// Make sure that recipient's TON balance has increased
tonRecipientBalanceAfter, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress())
tonRecipientBalanceAfter, err := deployer.GetBalanceOf(r.Ctx, tonRecipient.GetAddress(), true)
require.NoError(r, err)

r.Logger.Info("Recipient's balance after withdrawal: %s", toncontracts.FormatCoins(tonRecipientBalanceAfter))
Expand Down
80 changes: 80 additions & 0 deletions e2e/e2etests/test_ton_withdrawal_concurrent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package e2etests

import (
"math/rand"
"sync"

"cosmossdk.io/math"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
"github.com/tonkeeper/tongo/ton"

"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
"github.com/zeta-chain/node/testutil/sample"
cc "github.com/zeta-chain/node/x/crosschain/types"
)

func TestTONWithdrawConcurrent(r *runner.E2ERunner, _ []string) {
// ARRANGE
// Given a deployer
_, deployer := r.Ctx, r.TONDeployer

const recipientsCount = 10
type withdrawal struct {
recipient ton.AccountID
amount math.Uint
}

var (
testCases []withdrawal
wg sync.WaitGroup
)

// Given multiple recipients WITHOUT deployed wallet-contracts
// and sample withdrawal amounts between 1 and 5 TON
for i := 0; i < recipientsCount; i++ {
// #nosec G404: it's a test
amount := 1 + rand.Intn(5)
testCases = append(testCases, withdrawal{
// #nosec G115 test - always in range
amount: toncontracts.Coins(uint64(amount)),
recipient: sample.GenerateTONAccountID(),
})
}

// ACT
// Fire withdrawals. Note that zevm sender is r.ZEVMAuth
for i, tc := range testCases {
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
r.Logger.Info(
"Withdrawal #%d: sending %s to %s",
i+1,
toncontracts.FormatCoins(tc.amount),
tc.recipient.ToRaw(),
)

approvedAmount := tc.amount.Add(toncontracts.Coins(1))
tx := r.SendWithdrawTONZRC20(tc.recipient, tc.amount.BigInt(), approvedAmount.BigInt())
swift1337 marked this conversation as resolved.
Show resolved Hide resolved

wg.Add(1)

go func(number int, tx *ethtypes.Transaction) {
defer wg.Done()

// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)

// ASSERT
utils.RequireCCTXStatus(r, cctx, cc.CctxStatus_OutboundMined)
r.Logger.Info("Withdrawal #%d complete! cctx index: %s", number, cctx.Index)

// Check recipient's balance ON TON
balance, err := deployer.GetBalanceOf(r.Ctx, tc.recipient, false)
require.NoError(r, err, "failed to get balance of %s", tc.recipient.ToRaw())
require.Equal(r, tc.amount.Uint64(), balance.Uint64())
}(i+1, tx)
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
}

wg.Wait()
}
35 changes: 21 additions & 14 deletions e2e/runner/setup_ton.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/zeta-chain/node/pkg/chains"
"github.com/zeta-chain/node/pkg/constant"
toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
cctxtypes "github.com/zeta-chain/node/x/crosschain/types"
observertypes "github.com/zeta-chain/node/x/observer/types"
)

Expand Down Expand Up @@ -54,29 +55,35 @@ func (r *E2ERunner) SetupTON() error {
)

// 3. Check that the gateway indeed was deployed and has desired TON balance.
gwBalance, err := deployer.GetBalanceOf(ctx, gwAccount.ID)
if err != nil {
gwBalance, err := deployer.GetBalanceOf(ctx, gwAccount.ID, true)
switch {
case err != nil:
return errors.Wrap(err, "unable to get balance of TON gateway")
case gwBalance.IsZero():
return fmt.Errorf("TON gateway balance is zero")
}

if gwBalance.IsZero() {
return fmt.Errorf("TON gateway balance is zero")
// 4. Set chain params & chain nonce
if err := r.ensureTONChainParams(gwAccount); err != nil {
return errors.Wrap(err, "unable to ensure TON chain params")
}

// 4. Deposit 100 TON deployer to Zevm Auth
gw := toncontracts.NewGateway(gwAccount.ID)
veryFirstDeposit := toncontracts.Coins(1000)
r.TONDeployer = deployer
r.TONGateway = toncontracts.NewGateway(gwAccount.ID)

// 5. Deposit 10000 TON deployer to Zevm Auth
veryFirstDeposit := toncontracts.Coins(10000)
zevmRecipient := r.ZEVMAuth.From

err = gw.SendDeposit(ctx, &deployer.Wallet, veryFirstDeposit, zevmRecipient, 0)
if err != nil {
return errors.Wrap(err, "unable to send deposit to TON gateway")
gwDeposit, err := r.TONDeposit(&deployer.Wallet, veryFirstDeposit, zevmRecipient)
switch {
case err != nil:
return errors.Wrap(err, "unable to deposit TON to Zevm Auth")
case gwDeposit.CctxStatus.Status != cctxtypes.CctxStatus_OutboundMined:
return errors.New("gateway deposit CCTX is not mined")
}

r.TONDeployer = deployer
r.TONGateway = gw

return r.ensureTONChainParams(gwAccount)
return nil
}

func (r *E2ERunner) ensureTONChainParams(gw *ton.AccountInit) error {
Expand Down
47 changes: 42 additions & 5 deletions e2e/runner/ton.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package runner

import (
"encoding/hex"
"math/big"
"time"

"cosmossdk.io/math"
eth "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/tonkeeper/tongo/ton"
Expand All @@ -23,6 +25,20 @@ import (
// https://docs.ton.org/develop/smart-contracts/guidelines/message-modes-cookbook
const tonDepositSendCode = toncontracts.SendFlagSeparateFees + toncontracts.SendFlagIgnoreErrors

// currently implemented only for DepositAndCall,
// can be adopted for all TON ops
type tonOpts struct {
expectedStatus cctypes.CctxStatus
}

type TONOpt func(t *tonOpts)

func TONExpectStatus(status cctypes.CctxStatus) TONOpt {
return func(t *tonOpts) {
t.expectedStatus = status
}
}

// TONDeposit deposit TON to Gateway contract
func (r *E2ERunner) TONDeposit(
sender *wallet.Wallet,
Expand Down Expand Up @@ -56,7 +72,7 @@ func (r *E2ERunner) TONDeposit(
}

// Wait for cctx
cctx := r.WaitForSpecificCCTX(filter, time.Minute)
cctx := r.WaitForSpecificCCTX(filter, cctypes.CctxStatus_OutboundMined, time.Minute)

return cctx, nil
}
Expand All @@ -67,7 +83,13 @@ func (r *E2ERunner) TONDepositAndCall(
amount math.Uint,
zevmRecipient eth.Address,
callData []byte,
opts ...TONOpt,
) (*cctypes.CrossChainTx, error) {
cfg := &tonOpts{expectedStatus: cctypes.CctxStatus_OutboundMined}
for _, opt := range opts {
opt(cfg)
}

chain := chains.TONLocalnet

require.NotNil(r, r.TONGateway, "TON Gateway is not initialized")
Expand All @@ -91,18 +113,26 @@ func (r *E2ERunner) TONDepositAndCall(
}

filter := func(cctx *cctypes.CrossChainTx) bool {
memo := zevmRecipient.Bytes()
memo = append(memo, callData...)

return cctx.InboundParams.SenderChainId == chain.ChainId &&
cctx.InboundParams.Sender == sender.GetAddress().ToRaw()
cctx.InboundParams.Sender == sender.GetAddress().ToRaw() &&
cctx.RelayedMessage == hex.EncodeToString(memo)
}

// Wait for cctx
cctx := r.WaitForSpecificCCTX(filter, time.Minute)
cctx := r.WaitForSpecificCCTX(filter, cfg.expectedStatus, time.Minute)

return cctx, nil
}

// WithdrawTONZRC20 withdraws an amount of ZRC20 TON tokens
func (r *E2ERunner) WithdrawTONZRC20(to ton.AccountID, amount *big.Int, approveAmount *big.Int) *cctypes.CrossChainTx {
// SendWithdrawTONZRC20 sends withdraw tx of TON ZRC20 tokens
func (r *E2ERunner) SendWithdrawTONZRC20(
to ton.AccountID,
amount *big.Int,
approveAmount *big.Int,
) *ethtypes.Transaction {
// approve
tx, err := r.TONZRC20.Approve(r.ZEVMAuth, r.TONZRC20Addr, approveAmount)
require.NoError(r, err)
Expand All @@ -119,6 +149,13 @@ func (r *E2ERunner) WithdrawTONZRC20(to ton.AccountID, amount *big.Int, approveA
utils.RequireTxSuccessful(r, receipt, "withdraw")
r.Logger.Info("Receipt txhash %s status %d", receipt.TxHash, receipt.Status)

return tx
}

// WithdrawTONZRC20 withdraws an amount of ZRC20 TON tokens and waits for the cctx to be mined
func (r *E2ERunner) WithdrawTONZRC20(to ton.AccountID, amount *big.Int, approveAmount *big.Int) *cctypes.CrossChainTx {
tx := r.SendWithdrawTONZRC20(to, amount, approveAmount)

swift1337 marked this conversation as resolved.
Show resolved Hide resolved
// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, cctypes.CctxStatus_OutboundMined)
Expand Down
11 changes: 7 additions & 4 deletions e2e/runner/ton/deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,13 @@ func (d *Deployer) Seqno(ctx context.Context) (uint32, error) {
return d.blockchain.GetSeqno(ctx, d.GetAddress())
}

// GetBalanceOf returns the balance of the given account.
func (d *Deployer) GetBalanceOf(ctx context.Context, id ton.AccountID) (math.Uint, error) {
if err := d.waitForAccountActivation(ctx, id); err != nil {
return math.Uint{}, errors.Wrap(err, "failed to wait for account activation")
// GetBalanceOf returns the balance of a given account.
// wait=true waits for account activation.
func (d *Deployer) GetBalanceOf(ctx context.Context, id ton.AccountID, wait bool) (math.Uint, error) {
if wait {
if err := d.waitForAccountActivation(ctx, id); err != nil {
return math.Uint{}, errors.Wrap(err, "failed to wait for account activation")
}
}

state, err := d.blockchain.GetAccountState(ctx, id)
Expand Down
Loading
Loading