From 9495518353004e4dae2ebb3b8a4042016e1c424e Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Tue, 17 Sep 2024 12:40:45 +0300
Subject: [PATCH 01/36] Use chainParams from base observer
---
zetaclient/chains/base/observer.go | 11 +++-
zetaclient/chains/base/observer_test.go | 9 ---
zetaclient/chains/bitcoin/observer/inbound.go | 12 ++--
.../chains/bitcoin/observer/observer.go | 34 +++--------
.../chains/bitcoin/observer/outbound.go | 4 +-
.../chains/bitcoin/observer/rpc_status.go | 2 +-
zetaclient/chains/evm/observer/inbound.go | 14 ++---
zetaclient/chains/evm/observer/observer.go | 26 ++------
.../chains/evm/observer/observer_gas.go | 8 +--
zetaclient/chains/evm/observer/outbound.go | 4 +-
.../chains/evm/observer/outbound_test.go | 2 +-
zetaclient/chains/evm/observer/rpc_status.go | 2 +-
zetaclient/chains/interfaces/interfaces.go | 10 ++--
zetaclient/chains/solana/observer/inbound.go | 2 +-
.../chains/solana/observer/inbound_tracker.go | 4 +-
zetaclient/chains/solana/observer/observer.go | 16 -----
.../chains/solana/observer/observer_gas.go | 8 +--
zetaclient/chains/solana/observer/outbound.go | 4 +-
.../chains/solana/observer/rpc_status.go | 2 +-
zetaclient/chains/ton/observer/observer.go | 44 ++++++++++++++
.../chains/ton/observer/observer_test.go | 60 +------------------
zetaclient/orchestrator/orchestrator.go | 12 ++--
zetaclient/orchestrator/orchestrator_test.go | 6 +-
zetaclient/testutils/mocks/chain_clients.go | 30 +++++-----
24 files changed, 130 insertions(+), 196 deletions(-)
create mode 100644 zetaclient/chains/ton/observer/observer.go
diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go
index 020fd323f2..0a65ba2bc1 100644
--- a/zetaclient/chains/base/observer.go
+++ b/zetaclient/chains/base/observer.go
@@ -186,13 +186,18 @@ func (ob *Observer) WithChain(chain chains.Chain) *Observer {
// ChainParams returns the chain params for the observer.
func (ob *Observer) ChainParams() observertypes.ChainParams {
+ ob.mu.Lock()
+ defer ob.mu.Unlock()
+
return ob.chainParams
}
-// WithChainParams attaches a new chain params to the observer.
-func (ob *Observer) WithChainParams(params observertypes.ChainParams) *Observer {
+// SetChainParams attaches a new chain params to the observer.
+func (ob *Observer) SetChainParams(params observertypes.ChainParams) {
+ ob.mu.Lock()
+ defer ob.mu.Unlock()
+
ob.chainParams = params
- return ob
}
// ZetacoreClient returns the zetacore client for the observer.
diff --git a/zetaclient/chains/base/observer_test.go b/zetaclient/chains/base/observer_test.go
index 01713f6c4c..bbf24ccd92 100644
--- a/zetaclient/chains/base/observer_test.go
+++ b/zetaclient/chains/base/observer_test.go
@@ -168,15 +168,6 @@ func TestObserverGetterAndSetter(t *testing.T) {
require.Equal(t, newChain, ob.Chain())
})
- t.Run("should be able to update chain params", func(t *testing.T) {
- ob := createObserver(t, chain, defaultAlertLatency)
-
- // update chain params
- newChainParams := *sample.ChainParams(chains.BscMainnet.ChainId)
- ob = ob.WithChainParams(newChainParams)
- require.True(t, observertypes.ChainParamsEqual(newChainParams, ob.ChainParams()))
- })
-
t.Run("should be able to update zetacore client", func(t *testing.T) {
ob := createObserver(t, chain, defaultAlertLatency)
diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go
index 5985d6f574..01205026fd 100644
--- a/zetaclient/chains/bitcoin/observer/inbound.go
+++ b/zetaclient/chains/bitcoin/observer/inbound.go
@@ -35,7 +35,7 @@ func (ob *Observer) WatchInbound(ctx context.Context) error {
return err
}
- ticker, err := types.NewDynamicTicker("Bitcoin_WatchInbound", ob.GetChainParams().InboundTicker)
+ ticker, err := types.NewDynamicTicker("Bitcoin_WatchInbound", ob.ChainParams().InboundTicker)
if err != nil {
ob.logger.Inbound.Error().Err(err).Msg("error creating ticker")
return err
@@ -65,7 +65,7 @@ func (ob *Observer) WatchInbound(ctx context.Context) error {
ob.logger.Inbound.Debug().Err(err).Msg("WatchInbound: Bitcoin node is not enabled")
}
}
- ticker.UpdateInterval(ob.GetChainParams().InboundTicker, ob.logger.Inbound)
+ ticker.UpdateInterval(ob.ChainParams().InboundTicker, ob.logger.Inbound)
case <-ob.StopChannel():
ob.logger.Inbound.Info().Msgf("WatchInbound stopped for chain %d", ob.Chain().ChainId)
return nil
@@ -105,13 +105,13 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error {
ob.WithLastBlock(lastBlock)
// skip if current height is too low
- if lastBlock < ob.GetChainParams().ConfirmationCount {
+ if lastBlock < ob.ChainParams().ConfirmationCount {
return fmt.Errorf("observeInboundBTC: skipping observer, current block number %d is too low", currentBlock)
}
// skip if no new block is confirmed
lastScanned := ob.LastBlockScanned()
- if lastScanned >= lastBlock-ob.GetChainParams().ConfirmationCount {
+ if lastScanned >= lastBlock-ob.ChainParams().ConfirmationCount {
return nil
}
@@ -192,7 +192,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error {
return err
}
- ticker, err := types.NewDynamicTicker("Bitcoin_WatchInboundTracker", ob.GetChainParams().InboundTicker)
+ ticker, err := types.NewDynamicTicker("Bitcoin_WatchInboundTracker", ob.ChainParams().InboundTicker)
if err != nil {
ob.logger.Inbound.Err(err).Msg("error creating ticker")
return err
@@ -211,7 +211,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error {
Err(err).
Msgf("error observing inbound tracker for chain %d", ob.Chain().ChainId)
}
- ticker.UpdateInterval(ob.GetChainParams().InboundTicker, ob.logger.Inbound)
+ ticker.UpdateInterval(ob.ChainParams().InboundTicker, ob.logger.Inbound)
case <-ob.StopChannel():
ob.logger.Inbound.Info().Msgf("WatchInboundTracker stopped for chain %d", ob.Chain().ChainId)
return nil
diff --git a/zetaclient/chains/bitcoin/observer/observer.go b/zetaclient/chains/bitcoin/observer/observer.go
index 50de502b79..68e6f91022 100644
--- a/zetaclient/chains/bitcoin/observer/observer.go
+++ b/zetaclient/chains/bitcoin/observer/observer.go
@@ -181,22 +181,6 @@ func (ob *Observer) WithBtcClient(client interfaces.BTCRPCClient) {
ob.btcClient = client
}
-// SetChainParams sets the chain params for the observer
-// Note: chain params is accessed concurrently
-func (ob *Observer) SetChainParams(params observertypes.ChainParams) {
- ob.Mu().Lock()
- defer ob.Mu().Unlock()
- ob.WithChainParams(params)
-}
-
-// GetChainParams returns the chain params for the observer
-// Note: chain params is accessed concurrently
-func (ob *Observer) GetChainParams() observertypes.ChainParams {
- ob.Mu().Lock()
- defer ob.Mu().Unlock()
- return ob.ChainParams()
-}
-
// Start starts the Go routine processes to observe the Bitcoin chain
func (ob *Observer) Start(ctx context.Context) {
if noop := ob.Observer.Start(); noop {
@@ -238,12 +222,12 @@ func (ob *Observer) ConfirmationsThreshold(amount *big.Int) int64 {
if amount.Cmp(big.NewInt(BigValueSats)) >= 0 {
return BigValueConfirmationCount
}
- if BigValueConfirmationCount < ob.GetChainParams().ConfirmationCount {
+ if BigValueConfirmationCount < ob.ChainParams().ConfirmationCount {
return BigValueConfirmationCount
}
// #nosec G115 always in range
- return int64(ob.GetChainParams().ConfirmationCount)
+ return int64(ob.ChainParams().ConfirmationCount)
}
// WatchGasPrice watches Bitcoin chain for gas rate and post to zetacore
@@ -257,25 +241,25 @@ func (ob *Observer) WatchGasPrice(ctx context.Context) error {
}
// start gas price ticker
- ticker, err := clienttypes.NewDynamicTicker("Bitcoin_WatchGasPrice", ob.GetChainParams().GasPriceTicker)
+ ticker, err := clienttypes.NewDynamicTicker("Bitcoin_WatchGasPrice", ob.ChainParams().GasPriceTicker)
if err != nil {
return errors.Wrapf(err, "NewDynamicTicker error")
}
ob.logger.GasPrice.Info().Msgf("WatchGasPrice started for chain %d with interval %d",
- ob.Chain().ChainId, ob.GetChainParams().GasPriceTicker)
+ ob.Chain().ChainId, ob.ChainParams().GasPriceTicker)
defer ticker.Stop()
for {
select {
case <-ticker.C():
- if !ob.GetChainParams().IsSupported {
+ if !ob.ChainParams().IsSupported {
continue
}
err := ob.PostGasPrice(ctx)
if err != nil {
ob.logger.GasPrice.Error().Err(err).Msgf("PostGasPrice error for chain %d", ob.Chain().ChainId)
}
- ticker.UpdateInterval(ob.GetChainParams().GasPriceTicker, ob.logger.GasPrice)
+ ticker.UpdateInterval(ob.ChainParams().GasPriceTicker, ob.logger.GasPrice)
case <-ob.StopChannel():
ob.logger.GasPrice.Info().Msgf("WatchGasPrice stopped for chain %d", ob.Chain().ChainId)
return nil
@@ -378,7 +362,7 @@ func GetSenderAddressByVin(rpcClient interfaces.BTCRPCClient, vin btcjson.Vin, n
// WatchUTXOs watches bitcoin chain for UTXOs owned by the TSS address
// TODO(revamp): move ticker related functions to a specific file
func (ob *Observer) WatchUTXOs(ctx context.Context) error {
- ticker, err := clienttypes.NewDynamicTicker("Bitcoin_WatchUTXOs", ob.GetChainParams().WatchUtxoTicker)
+ ticker, err := clienttypes.NewDynamicTicker("Bitcoin_WatchUTXOs", ob.ChainParams().WatchUtxoTicker)
if err != nil {
ob.logger.UTXOs.Error().Err(err).Msg("error creating ticker")
return err
@@ -388,7 +372,7 @@ func (ob *Observer) WatchUTXOs(ctx context.Context) error {
for {
select {
case <-ticker.C():
- if !ob.GetChainParams().IsSupported {
+ if !ob.ChainParams().IsSupported {
continue
}
err := ob.FetchUTXOs(ctx)
@@ -403,7 +387,7 @@ func (ob *Observer) WatchUTXOs(ctx context.Context) error {
ob.logger.UTXOs.Debug().Err(err).Msg("No wallet is loaded")
}
}
- ticker.UpdateInterval(ob.GetChainParams().WatchUtxoTicker, ob.logger.UTXOs)
+ ticker.UpdateInterval(ob.ChainParams().WatchUtxoTicker, ob.logger.UTXOs)
case <-ob.StopChannel():
ob.logger.UTXOs.Info().Msgf("WatchUTXOs stopped for chain %d", ob.Chain().ChainId)
return nil
diff --git a/zetaclient/chains/bitcoin/observer/outbound.go b/zetaclient/chains/bitcoin/observer/outbound.go
index 01b1d16609..13c0386253 100644
--- a/zetaclient/chains/bitcoin/observer/outbound.go
+++ b/zetaclient/chains/bitcoin/observer/outbound.go
@@ -32,7 +32,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error {
return errors.Wrap(err, "unable to get app from context")
}
- ticker, err := types.NewDynamicTicker("Bitcoin_WatchOutbound", ob.GetChainParams().OutboundTicker)
+ ticker, err := types.NewDynamicTicker("Bitcoin_WatchOutbound", ob.ChainParams().OutboundTicker)
if err != nil {
return errors.Wrap(err, "unable to create dynamic ticker")
}
@@ -106,7 +106,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error {
ob.logger.Outbound.Error().Msgf("WatchOutbound: included multiple (%d) outbound for chain %d nonce %d", txCount, chainID, tracker.Nonce)
}
}
- ticker.UpdateInterval(ob.GetChainParams().OutboundTicker, ob.logger.Outbound)
+ ticker.UpdateInterval(ob.ChainParams().OutboundTicker, ob.logger.Outbound)
case <-ob.StopChannel():
ob.logger.Outbound.Info().Msgf("WatchOutbound stopped for chain %d", chainID)
return nil
diff --git a/zetaclient/chains/bitcoin/observer/rpc_status.go b/zetaclient/chains/bitcoin/observer/rpc_status.go
index 008d49aa59..58ccdfc558 100644
--- a/zetaclient/chains/bitcoin/observer/rpc_status.go
+++ b/zetaclient/chains/bitcoin/observer/rpc_status.go
@@ -16,7 +16,7 @@ func (ob *Observer) watchRPCStatus(_ context.Context) error {
for {
select {
case <-ticker.C:
- if !ob.GetChainParams().IsSupported {
+ if !ob.ChainParams().IsSupported {
continue
}
diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go
index 0e1cb6b84d..0ad5ba6b6c 100644
--- a/zetaclient/chains/evm/observer/inbound.go
+++ b/zetaclient/chains/evm/observer/inbound.go
@@ -39,7 +39,7 @@ import (
// TODO(revamp): move ticker function to a separate file
func (ob *Observer) WatchInbound(ctx context.Context) error {
sampledLogger := ob.Logger().Inbound.Sample(&zerolog.BasicSampler{N: 10})
- interval := ticker.SecondsFromUint64(ob.GetChainParams().InboundTicker)
+ interval := ticker.SecondsFromUint64(ob.ChainParams().InboundTicker)
task := func(ctx context.Context, t *ticker.Ticker) error {
return ob.watchInboundOnce(ctx, t, sampledLogger)
}
@@ -74,7 +74,7 @@ func (ob *Observer) watchInboundOnce(ctx context.Context, t *ticker.Ticker, samp
ob.Logger().Inbound.Err(err).Msg("WatchInbound: observeInbound error")
}
- newInterval := ticker.SecondsFromUint64(ob.GetChainParams().InboundTicker)
+ newInterval := ticker.SecondsFromUint64(ob.ChainParams().InboundTicker)
t.SetInterval(newInterval)
return nil
@@ -91,7 +91,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error {
ticker, err := clienttypes.NewDynamicTicker(
fmt.Sprintf("EVM_WatchInboundTracker_%d", ob.Chain().ChainId),
- ob.GetChainParams().InboundTicker,
+ ob.ChainParams().InboundTicker,
)
if err != nil {
ob.Logger().Inbound.Err(err).Msg("error creating ticker")
@@ -110,7 +110,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error {
if err != nil {
ob.Logger().Inbound.Err(err).Msg("ProcessInboundTrackers error")
}
- ticker.UpdateInterval(ob.GetChainParams().InboundTicker, ob.Logger().Inbound)
+ ticker.UpdateInterval(ob.ChainParams().InboundTicker, ob.Logger().Inbound)
case <-ob.StopChannel():
ob.Logger().Inbound.Info().Msgf("WatchInboundTracker stopped for chain %d", ob.Chain().ChainId)
return nil
@@ -191,10 +191,10 @@ func (ob *Observer) ObserveInbound(ctx context.Context, sampledLogger zerolog.Lo
metrics.GetBlockByNumberPerChain.WithLabelValues(ob.Chain().Name).Inc()
// skip if current height is too low
- if blockNumber < ob.GetChainParams().ConfirmationCount {
+ if blockNumber < ob.ChainParams().ConfirmationCount {
return fmt.Errorf("observeInbound: skipping observer, current block number %d is too low", blockNumber)
}
- confirmedBlockNum := blockNumber - ob.GetChainParams().ConfirmationCount
+ confirmedBlockNum := blockNumber - ob.ChainParams().ConfirmationCount
// skip if no new block is confirmed
lastScanned := ob.LastBlockScanned()
@@ -615,7 +615,7 @@ func (ob *Observer) CheckAndVoteInboundTokenGas(
// HasEnoughConfirmations checks if the given receipt has enough confirmations
func (ob *Observer) HasEnoughConfirmations(receipt *ethtypes.Receipt, lastHeight uint64) bool {
- confHeight := receipt.BlockNumber.Uint64() + ob.GetChainParams().ConfirmationCount
+ confHeight := receipt.BlockNumber.Uint64() + ob.ChainParams().ConfirmationCount
return lastHeight >= confHeight
}
diff --git a/zetaclient/chains/evm/observer/observer.go b/zetaclient/chains/evm/observer/observer.go
index 39de0eedea..68c0989672 100644
--- a/zetaclient/chains/evm/observer/observer.go
+++ b/zetaclient/chains/evm/observer/observer.go
@@ -116,39 +116,23 @@ func (ob *Observer) WithEvmJSONRPC(client interfaces.EVMJSONRPCClient) {
ob.evmJSONRPC = client
}
-// SetChainParams sets the chain params for the observer
-// Note: chain params is accessed concurrently
-func (ob *Observer) SetChainParams(params observertypes.ChainParams) {
- ob.Mu().Lock()
- defer ob.Mu().Unlock()
- ob.WithChainParams(params)
-}
-
-// GetChainParams returns the chain params for the observer
-// Note: chain params is accessed concurrently
-func (ob *Observer) GetChainParams() observertypes.ChainParams {
- ob.Mu().Lock()
- defer ob.Mu().Unlock()
- return ob.ChainParams()
-}
-
// GetConnectorContract returns the non-Eth connector address and binder
func (ob *Observer) GetConnectorContract() (ethcommon.Address, *zetaconnector.ZetaConnectorNonEth, error) {
- addr := ethcommon.HexToAddress(ob.GetChainParams().ConnectorContractAddress)
+ addr := ethcommon.HexToAddress(ob.ChainParams().ConnectorContractAddress)
contract, err := zetaconnector.NewZetaConnectorNonEth(addr, ob.evmClient)
return addr, contract, err
}
// GetConnectorContractEth returns the Eth connector address and binder
func (ob *Observer) GetConnectorContractEth() (ethcommon.Address, *zetaconnectoreth.ZetaConnectorEth, error) {
- addr := ethcommon.HexToAddress(ob.GetChainParams().ConnectorContractAddress)
+ addr := ethcommon.HexToAddress(ob.ChainParams().ConnectorContractAddress)
contract, err := FetchConnectorContractEth(addr, ob.evmClient)
return addr, contract, err
}
// GetERC20CustodyContract returns ERC20Custody contract address and binder
func (ob *Observer) GetERC20CustodyContract() (ethcommon.Address, *erc20custody.ERC20Custody, error) {
- addr := ethcommon.HexToAddress(ob.GetChainParams().Erc20CustodyContractAddress)
+ addr := ethcommon.HexToAddress(ob.ChainParams().Erc20CustodyContractAddress)
contract, err := erc20custody.NewERC20Custody(addr, ob.evmClient)
return addr, contract, err
}
@@ -158,14 +142,14 @@ func (ob *Observer) GetERC20CustodyContract() (ethcommon.Address, *erc20custody.
// this simplify the migration process v1 will be completely removed in the future
// currently the ABI for withdraw is identical, therefore both contract instances can be used
func (ob *Observer) GetERC20CustodyV2Contract() (ethcommon.Address, *erc20custodyv2.ERC20Custody, error) {
- addr := ethcommon.HexToAddress(ob.GetChainParams().Erc20CustodyContractAddress)
+ addr := ethcommon.HexToAddress(ob.ChainParams().Erc20CustodyContractAddress)
contract, err := erc20custodyv2.NewERC20Custody(addr, ob.evmClient)
return addr, contract, err
}
// GetGatewayContract returns the gateway contract address and binder
func (ob *Observer) GetGatewayContract() (ethcommon.Address, *gatewayevm.GatewayEVM, error) {
- addr := ethcommon.HexToAddress(ob.GetChainParams().GatewayAddress)
+ addr := ethcommon.HexToAddress(ob.ChainParams().GatewayAddress)
contract, err := gatewayevm.NewGatewayEVM(addr, ob.evmClient)
return addr, contract, err
}
diff --git a/zetaclient/chains/evm/observer/observer_gas.go b/zetaclient/chains/evm/observer/observer_gas.go
index 754f0349c5..3ebca1d3a9 100644
--- a/zetaclient/chains/evm/observer/observer_gas.go
+++ b/zetaclient/chains/evm/observer/observer_gas.go
@@ -22,27 +22,27 @@ func (ob *Observer) WatchGasPrice(ctx context.Context) error {
// start gas price ticker
ticker, err := clienttypes.NewDynamicTicker(
fmt.Sprintf("EVM_WatchGasPrice_%d", ob.Chain().ChainId),
- ob.GetChainParams().GasPriceTicker,
+ ob.ChainParams().GasPriceTicker,
)
if err != nil {
ob.Logger().GasPrice.Error().Err(err).Msg("NewDynamicTicker error")
return err
}
ob.Logger().GasPrice.Info().Msgf("WatchGasPrice started for chain %d with interval %d",
- ob.Chain().ChainId, ob.GetChainParams().GasPriceTicker)
+ ob.Chain().ChainId, ob.ChainParams().GasPriceTicker)
defer ticker.Stop()
for {
select {
case <-ticker.C():
- if !ob.GetChainParams().IsSupported {
+ if !ob.ChainParams().IsSupported {
continue
}
err = ob.PostGasPrice(ctx)
if err != nil {
ob.Logger().GasPrice.Error().Err(err).Msgf("PostGasPrice error for chain %d", ob.Chain().ChainId)
}
- ticker.UpdateInterval(ob.GetChainParams().GasPriceTicker, ob.Logger().GasPrice)
+ ticker.UpdateInterval(ob.ChainParams().GasPriceTicker, ob.Logger().GasPrice)
case <-ob.StopChannel():
ob.Logger().GasPrice.Info().Msg("WatchGasPrice stopped")
return nil
diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go
index 2534c47aab..0bab913592 100644
--- a/zetaclient/chains/evm/observer/outbound.go
+++ b/zetaclient/chains/evm/observer/outbound.go
@@ -44,7 +44,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error {
chainID := ob.Chain().ChainId
ticker, err := clienttypes.NewDynamicTicker(
fmt.Sprintf("EVM_WatchOutbound_%d", ob.Chain().ChainId),
- ob.GetChainParams().OutboundTicker,
+ ob.ChainParams().OutboundTicker,
)
if err != nil {
ob.Logger().Outbound.Error().Err(err).Msg("error creating ticker")
@@ -72,7 +72,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error {
Msgf("WatchOutbound: error ProcessOutboundTrackers for chain %d", chainID)
}
- ticker.UpdateInterval(ob.GetChainParams().OutboundTicker, ob.Logger().Outbound)
+ ticker.UpdateInterval(ob.ChainParams().OutboundTicker, ob.Logger().Outbound)
case <-ob.StopChannel():
ob.Logger().Outbound.Info().Msg("WatchOutbound: stopped")
return nil
diff --git a/zetaclient/chains/evm/observer/outbound_test.go b/zetaclient/chains/evm/observer/outbound_test.go
index 7b8e47f40f..5011e5660a 100644
--- a/zetaclient/chains/evm/observer/outbound_test.go
+++ b/zetaclient/chains/evm/observer/outbound_test.go
@@ -105,7 +105,7 @@ func Test_IsOutboundProcessed(t *testing.T) {
ob.SetTxNReceipt(nonce, receipt, outbound)
// set connector contract address to an arbitrary address to make event parsing fail
- chainParamsNew := ob.GetChainParams()
+ chainParamsNew := ob.ChainParams()
chainParamsNew.ConnectorContractAddress = sample.EthAddress().Hex()
ob.SetChainParams(chainParamsNew)
continueKeysign, err := ob.VoteOutboundIfConfirmed(ctx, cctx)
diff --git a/zetaclient/chains/evm/observer/rpc_status.go b/zetaclient/chains/evm/observer/rpc_status.go
index c63e9e775a..68c7629523 100644
--- a/zetaclient/chains/evm/observer/rpc_status.go
+++ b/zetaclient/chains/evm/observer/rpc_status.go
@@ -17,7 +17,7 @@ func (ob *Observer) watchRPCStatus(ctx context.Context) error {
for {
select {
case <-ticker.C:
- if !ob.GetChainParams().IsSupported {
+ if !ob.ChainParams().IsSupported {
continue
}
diff --git a/zetaclient/chains/interfaces/interfaces.go b/zetaclient/chains/interfaces/interfaces.go
index 36f3fcad5b..9c9cb3fbbb 100644
--- a/zetaclient/chains/interfaces/interfaces.go
+++ b/zetaclient/chains/interfaces/interfaces.go
@@ -41,13 +41,11 @@ const (
type ChainObserver interface {
Start(ctx context.Context)
Stop()
- VoteOutboundIfConfirmed(
- ctx context.Context,
- cctx *crosschaintypes.CrossChainTx,
- ) (bool, error)
+
+ ChainParams() observertypes.ChainParams
SetChainParams(observertypes.ChainParams)
- GetChainParams() observertypes.ChainParams
- WatchInboundTracker(ctx context.Context) error
+
+ VoteOutboundIfConfirmed(ctx context.Context, cctx *crosschaintypes.CrossChainTx) (bool, error)
}
// ChainSigner is the interface to sign transactions for a chain
diff --git a/zetaclient/chains/solana/observer/inbound.go b/zetaclient/chains/solana/observer/inbound.go
index d9819bd53c..1441150ada 100644
--- a/zetaclient/chains/solana/observer/inbound.go
+++ b/zetaclient/chains/solana/observer/inbound.go
@@ -38,7 +38,7 @@ func (ob *Observer) WatchInbound(ctx context.Context) error {
ticker, err := clienttypes.NewDynamicTicker(
fmt.Sprintf("Solana_WatchInbound_%d", ob.Chain().ChainId),
- ob.GetChainParams().InboundTicker,
+ ob.ChainParams().InboundTicker,
)
if err != nil {
ob.Logger().Inbound.Error().Err(err).Msg("error creating ticker")
diff --git a/zetaclient/chains/solana/observer/inbound_tracker.go b/zetaclient/chains/solana/observer/inbound_tracker.go
index 70b6e9702f..19f8d26d04 100644
--- a/zetaclient/chains/solana/observer/inbound_tracker.go
+++ b/zetaclient/chains/solana/observer/inbound_tracker.go
@@ -21,7 +21,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error {
ticker, err := clienttypes.NewDynamicTicker(
fmt.Sprintf("Solana_WatchInboundTracker_%d", ob.Chain().ChainId),
- ob.GetChainParams().InboundTicker,
+ ob.ChainParams().InboundTicker,
)
if err != nil {
ob.Logger().Inbound.Err(err).Msg("error creating ticker")
@@ -42,7 +42,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error {
Err(err).
Msgf("WatchInboundTracker: error ProcessInboundTrackers for chain %d", ob.Chain().ChainId)
}
- ticker.UpdateInterval(ob.GetChainParams().InboundTicker, ob.Logger().Inbound)
+ ticker.UpdateInterval(ob.ChainParams().InboundTicker, ob.Logger().Inbound)
case <-ob.StopChannel():
ob.Logger().Inbound.Info().Msgf("WatchInboundTracker stopped for chain %d", ob.Chain().ChainId)
return nil
diff --git a/zetaclient/chains/solana/observer/observer.go b/zetaclient/chains/solana/observer/observer.go
index 634bdfd635..55a5938316 100644
--- a/zetaclient/chains/solana/observer/observer.go
+++ b/zetaclient/chains/solana/observer/observer.go
@@ -96,22 +96,6 @@ func (ob *Observer) WithSolClient(client interfaces.SolanaRPCClient) {
ob.solClient = client
}
-// SetChainParams sets the chain params for the observer
-// Note: chain params is accessed concurrently
-func (ob *Observer) SetChainParams(params observertypes.ChainParams) {
- ob.Mu().Lock()
- defer ob.Mu().Unlock()
- ob.WithChainParams(params)
-}
-
-// GetChainParams returns the chain params for the observer
-// Note: chain params is accessed concurrently
-func (ob *Observer) GetChainParams() observertypes.ChainParams {
- ob.Mu().Lock()
- defer ob.Mu().Unlock()
- return ob.ChainParams()
-}
-
// Start starts the Go routine processes to observe the Solana chain
func (ob *Observer) Start(ctx context.Context) {
if noop := ob.Observer.Start(); noop {
diff --git a/zetaclient/chains/solana/observer/observer_gas.go b/zetaclient/chains/solana/observer/observer_gas.go
index 80747e2efb..03291d6a54 100644
--- a/zetaclient/chains/solana/observer/observer_gas.go
+++ b/zetaclient/chains/solana/observer/observer_gas.go
@@ -42,26 +42,26 @@ func (ob *Observer) WatchGasPrice(ctx context.Context) error {
// start gas price ticker
ticker, err := clienttypes.NewDynamicTicker(
fmt.Sprintf("Solana_WatchGasPrice_%d", ob.Chain().ChainId),
- ob.GetChainParams().GasPriceTicker,
+ ob.ChainParams().GasPriceTicker,
)
if err != nil {
return errors.Wrapf(err, "NewDynamicTicker error")
}
ob.Logger().GasPrice.Info().Msgf("WatchGasPrice started for chain %d with interval %d",
- ob.Chain().ChainId, ob.GetChainParams().GasPriceTicker)
+ ob.Chain().ChainId, ob.ChainParams().GasPriceTicker)
defer ticker.Stop()
for {
select {
case <-ticker.C():
- if !ob.GetChainParams().IsSupported {
+ if !ob.ChainParams().IsSupported {
continue
}
err = ob.PostGasPrice(ctx)
if err != nil {
ob.Logger().GasPrice.Error().Err(err).Msgf("PostGasPrice error for chain %d", ob.Chain().ChainId)
}
- ticker.UpdateInterval(ob.GetChainParams().GasPriceTicker, ob.Logger().GasPrice)
+ ticker.UpdateInterval(ob.ChainParams().GasPriceTicker, ob.Logger().GasPrice)
case <-ob.StopChannel():
ob.Logger().GasPrice.Info().Msgf("WatchGasPrice stopped for chain %d", ob.Chain().ChainId)
return nil
diff --git a/zetaclient/chains/solana/observer/outbound.go b/zetaclient/chains/solana/observer/outbound.go
index 7ff968ea93..e185b1a27d 100644
--- a/zetaclient/chains/solana/observer/outbound.go
+++ b/zetaclient/chains/solana/observer/outbound.go
@@ -36,7 +36,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error {
chainID := ob.Chain().ChainId
ticker, err := clienttypes.NewDynamicTicker(
fmt.Sprintf("Solana_WatchOutbound_%d", chainID),
- ob.GetChainParams().OutboundTicker,
+ ob.ChainParams().OutboundTicker,
)
if err != nil {
ob.Logger().Outbound.Error().Err(err).Msg("error creating ticker")
@@ -63,7 +63,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error {
Msgf("WatchOutbound: error ProcessOutboundTrackers for chain %d", chainID)
}
- ticker.UpdateInterval(ob.GetChainParams().OutboundTicker, ob.Logger().Outbound)
+ ticker.UpdateInterval(ob.ChainParams().OutboundTicker, ob.Logger().Outbound)
case <-ob.StopChannel():
ob.Logger().Outbound.Info().Msgf("WatchOutbound: watcher stopped for chain %d", chainID)
return nil
diff --git a/zetaclient/chains/solana/observer/rpc_status.go b/zetaclient/chains/solana/observer/rpc_status.go
index ff3d02f679..1b16492076 100644
--- a/zetaclient/chains/solana/observer/rpc_status.go
+++ b/zetaclient/chains/solana/observer/rpc_status.go
@@ -17,7 +17,7 @@ func (ob *Observer) watchRPCStatus(ctx context.Context) error {
for {
select {
case <-ticker.C:
- if !ob.GetChainParams().IsSupported {
+ if !ob.ChainParams().IsSupported {
continue
}
diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go
new file mode 100644
index 0000000000..d21312c31c
--- /dev/null
+++ b/zetaclient/chains/ton/observer/observer.go
@@ -0,0 +1,44 @@
+package observer
+
+import (
+ "context"
+
+ "github.com/tonkeeper/tongo/liteapi"
+ "github.com/tonkeeper/tongo/ton"
+
+ "github.com/zeta-chain/node/x/crosschain/types"
+ "github.com/zeta-chain/node/zetaclient/chains/base"
+ "github.com/zeta-chain/node/zetaclient/chains/interfaces"
+)
+
+type Observer struct {
+ base.Observer
+
+ client *liteapi.Client
+ gatewayID ton.AccountID
+}
+
+var _ interfaces.ChainObserver = (*Observer)(nil)
+
+func New(bo *base.Observer, client *liteapi.Client, gatewayID ton.AccountID) (*Observer, error) {
+ bo.LoadLastTxScanned()
+
+ return &Observer{
+ Observer: *bo,
+ gatewayID: gatewayID,
+ client: client,
+ }, nil
+}
+
+func (ob *Observer) Start(ctx context.Context) {
+ // todo
+}
+
+func (ob *Observer) Stop() {
+ // todo
+}
+
+func (ob *Observer) VoteOutboundIfConfirmed(ctx context.Context, cctx *types.CrossChainTx) (bool, error) {
+ // todo
+ return false, nil
+}
diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go
index e978a589b9..e4d0ce4e35 100644
--- a/zetaclient/chains/ton/observer/observer_test.go
+++ b/zetaclient/chains/ton/observer/observer_test.go
@@ -1,63 +1,7 @@
package observer
-import (
- "context"
- "encoding/json"
- "strings"
- "testing"
-
- "github.com/stretchr/testify/require"
- "github.com/tonkeeper/tongo/config"
- "github.com/tonkeeper/tongo/liteapi"
-)
-
-// todo tmp (will be resolved automatically)
-// taken from ton:8000/lite-client.json
-const configRaw = `{"@type":"config.global","dht":{"@type":"dht.config.global","k":3,"a":3,"static_nodes":
-{"@type":"dht.nodes","nodes":[]}},"liteservers":[{"id":{"key":"+DjLFqH/N5jO1ZO8PYVYU6a6e7EnnsF0GWFsteE+qy8=","@type":
-"pub.ed25519"},"port":4443,"ip":2130706433}],"validator":{"@type":"validator.config.global","zero_state":
-{"workchain":-1,"shard":-9223372036854775808,"seqno":0,"root_hash":"rR8EFZNlyj3rfYlMyQC8gT0A6ghDrbKe4aMmodiNw6I=",
-"file_hash":"fT2hXGv1OF7XDhraoAELrYz6wX3ue16QpSoWTiPrUAE="},"init_block":{"workchain":-1,"shard":-9223372036854775808,
-"seqno":0,"root_hash":"rR8EFZNlyj3rfYlMyQC8gT0A6ghDrbKe4aMmodiNw6I=",
-"file_hash":"fT2hXGv1OF7XDhraoAELrYz6wX3ue16QpSoWTiPrUAE="}}}`
+import "testing"
func TestObserver(t *testing.T) {
- t.Skip("skip test")
-
- ctx := context.Background()
-
- cfg, err := config.ParseConfig(strings.NewReader(configRaw))
- require.NoError(t, err)
-
- client, err := liteapi.NewClient(liteapi.WithConfigurationFile(*cfg))
- require.NoError(t, err)
-
- res, err := client.GetMasterchainInfo(ctx)
- require.NoError(t, err)
-
- // Outputs:
- // {
- // "Last": {
- // "Workchain": 4294967295,
- // "Shard": 9223372036854775808,
- // "Seqno": 915,
- // "RootHash": "2e9e312c5bd3b7b96d23ce1342ac76e5486012c9aac44781c2c25dbc55f5c8ad",
- // "FileHash": "d3745319bfaeebb168d9db6bb5b4752b6b28ab9041735c81d4a02fc820040851"
- // },
- // "StateRootHash": "02538fb9dc802004012285a90a7af9ba279706e2deea9ca635decd80e94a7045",
- // "Init": {
- // "Workchain": 4294967295,
- // "RootHash": "ad1f04159365ca3deb7d894cc900bc813d00ea0843adb29ee1a326a1d88dc3a2",
- // "FileHash": "7d3da15c6bf5385ed70e1adaa0010bad8cfac17dee7b5e90a52a164e23eb5001"
- // }
- // }
- t.Logf("Masterchain info")
- logJSON(t, res)
-}
-
-func logJSON(t *testing.T, v any) {
- b, err := json.MarshalIndent(v, "", " ")
- require.NoError(t, err)
-
- t.Log(string(b))
+ // todo
}
diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go
index 6b2b673f4e..7594ffd763 100644
--- a/zetaclient/orchestrator/orchestrator.go
+++ b/zetaclient/orchestrator/orchestrator.go
@@ -228,7 +228,7 @@ func (oc *Orchestrator) resolveObserver(app *zctx.AppContext, chainID int64) (in
// update chain observer chain parameters
var (
- curParams = observer.GetChainParams()
+ curParams = observer.ChainParams()
freshParams = chain.Params()
)
@@ -447,11 +447,11 @@ func (oc *Orchestrator) ScheduleCctxEVM(
for _, v := range res {
trackerMap[v.Nonce] = true
}
- outboundScheduleLookahead := observer.GetChainParams().OutboundScheduleLookahead
+ outboundScheduleLookahead := observer.ChainParams().OutboundScheduleLookahead
// #nosec G115 always in range
outboundScheduleLookback := uint64(float64(outboundScheduleLookahead) * evmOutboundLookbackFactor)
// #nosec G115 positive
- outboundScheduleInterval := uint64(observer.GetChainParams().OutboundScheduleInterval)
+ outboundScheduleInterval := uint64(observer.ChainParams().OutboundScheduleInterval)
criticalInterval := uint64(10) // for critical pending outbound we reduce re-try interval
nonCriticalInterval := outboundScheduleInterval * 2 // for non-critical pending outbound we increase re-try interval
@@ -546,8 +546,8 @@ func (oc *Orchestrator) ScheduleCctxBTC(
return
}
// #nosec G115 positive
- interval := uint64(observer.GetChainParams().OutboundScheduleInterval)
- lookahead := observer.GetChainParams().OutboundScheduleLookahead
+ interval := uint64(observer.ChainParams().OutboundScheduleInterval)
+ lookahead := observer.ChainParams().OutboundScheduleLookahead
// schedule at most one keysign per ticker
for idx, cctx := range cctxList {
@@ -618,7 +618,7 @@ func (oc *Orchestrator) ScheduleCctxSolana(
return
}
// #nosec G701 positive
- interval := uint64(observer.GetChainParams().OutboundScheduleInterval)
+ interval := uint64(observer.ChainParams().OutboundScheduleInterval)
// schedule keysign for each pending cctx
for _, cctx := range cctxList {
diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go
index b15b93bb54..739c299e00 100644
--- a/zetaclient/orchestrator/orchestrator_test.go
+++ b/zetaclient/orchestrator/orchestrator_test.go
@@ -196,7 +196,7 @@ func Test_GetUpdatedChainObserver(t *testing.T) {
chainOb, err := orchestrator.resolveObserver(appContext, evmChain.ChainId)
require.NoError(t, err)
require.NotNil(t, chainOb)
- require.True(t, observertypes.ChainParamsEqual(*evmChainParamsNew, chainOb.GetChainParams()))
+ require.True(t, observertypes.ChainParamsEqual(*evmChainParamsNew, chainOb.ChainParams()))
})
t.Run("btc chain observer should not be found", func(t *testing.T) {
@@ -244,7 +244,7 @@ func Test_GetUpdatedChainObserver(t *testing.T) {
chainOb, err := orchestrator.resolveObserver(appContext, btcChain.ChainId)
require.NoError(t, err)
require.NotNil(t, chainOb)
- require.True(t, observertypes.ChainParamsEqual(*btcChainParamsNew, chainOb.GetChainParams()))
+ require.True(t, observertypes.ChainParamsEqual(*btcChainParamsNew, chainOb.ChainParams()))
})
t.Run("solana chain observer should not be found", func(t *testing.T) {
orchestrator := mockOrchestrator(
@@ -282,7 +282,7 @@ func Test_GetUpdatedChainObserver(t *testing.T) {
chainOb, err := orchestrator.resolveObserver(appContext, solChain.ChainId)
require.NoError(t, err)
require.NotNil(t, chainOb)
- require.True(t, observertypes.ChainParamsEqual(*solChainParamsNew, chainOb.GetChainParams()))
+ require.True(t, observertypes.ChainParamsEqual(*solChainParamsNew, chainOb.ChainParams()))
})
}
diff --git a/zetaclient/testutils/mocks/chain_clients.go b/zetaclient/testutils/mocks/chain_clients.go
index 94f636bf4e..aa5e36889b 100644
--- a/zetaclient/testutils/mocks/chain_clients.go
+++ b/zetaclient/testutils/mocks/chain_clients.go
@@ -15,12 +15,12 @@ var _ interfaces.ChainObserver = (*EVMObserver)(nil)
// EVMObserver is a mock of evm chain observer for testing
type EVMObserver struct {
- ChainParams observertypes.ChainParams
+ chainParams observertypes.ChainParams
}
func NewEVMObserver(chainParams *observertypes.ChainParams) *EVMObserver {
return &EVMObserver{
- ChainParams: *chainParams,
+ chainParams: *chainParams,
}
}
@@ -35,11 +35,11 @@ func (ob *EVMObserver) VoteOutboundIfConfirmed(
}
func (ob *EVMObserver) SetChainParams(chainParams observertypes.ChainParams) {
- ob.ChainParams = chainParams
+ ob.chainParams = chainParams
}
-func (ob *EVMObserver) GetChainParams() observertypes.ChainParams {
- return ob.ChainParams
+func (ob *EVMObserver) ChainParams() observertypes.ChainParams {
+ return ob.chainParams
}
func (ob *EVMObserver) GetTxID(_ uint64) string {
@@ -57,12 +57,12 @@ var _ interfaces.ChainObserver = (*BTCObserver)(nil)
// BTCObserver is a mock of btc chain observer for testing
type BTCObserver struct {
- ChainParams observertypes.ChainParams
+ chainParams observertypes.ChainParams
}
func NewBTCObserver(chainParams *observertypes.ChainParams) *BTCObserver {
return &BTCObserver{
- ChainParams: *chainParams,
+ chainParams: *chainParams,
}
}
@@ -78,11 +78,11 @@ func (ob *BTCObserver) VoteOutboundIfConfirmed(
}
func (ob *BTCObserver) SetChainParams(chainParams observertypes.ChainParams) {
- ob.ChainParams = chainParams
+ ob.chainParams = chainParams
}
-func (ob *BTCObserver) GetChainParams() observertypes.ChainParams {
- return ob.ChainParams
+func (ob *BTCObserver) ChainParams() observertypes.ChainParams {
+ return ob.chainParams
}
func (ob *BTCObserver) GetTxID(_ uint64) string {
@@ -98,12 +98,12 @@ var _ interfaces.ChainObserver = (*SolanaObserver)(nil)
// SolanaObserver is a mock of solana chain observer for testing
type SolanaObserver struct {
- ChainParams observertypes.ChainParams
+ chainParams observertypes.ChainParams
}
func NewSolanaObserver(chainParams *observertypes.ChainParams) *SolanaObserver {
return &SolanaObserver{
- ChainParams: *chainParams,
+ chainParams: *chainParams,
}
}
@@ -119,11 +119,11 @@ func (ob *SolanaObserver) VoteOutboundIfConfirmed(
}
func (ob *SolanaObserver) SetChainParams(chainParams observertypes.ChainParams) {
- ob.ChainParams = chainParams
+ ob.chainParams = chainParams
}
-func (ob *SolanaObserver) GetChainParams() observertypes.ChainParams {
- return ob.ChainParams
+func (ob *SolanaObserver) ChainParams() observertypes.ChainParams {
+ return ob.chainParams
}
func (ob *SolanaObserver) GetTxID(_ uint64) string {
From 2824c27e828afae588a6c84d3b3802caaf349b55 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Tue, 17 Sep 2024 12:54:58 +0300
Subject: [PATCH 02/36] Improve base observer Start() semantics
---
zetaclient/chains/base/observer.go | 6 +++---
zetaclient/chains/bitcoin/observer/observer.go | 2 +-
zetaclient/chains/evm/observer/observer.go | 2 +-
zetaclient/chains/solana/observer/observer.go | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go
index 0a65ba2bc1..e9e8e05ba2 100644
--- a/zetaclient/chains/base/observer.go
+++ b/zetaclient/chains/base/observer.go
@@ -135,19 +135,19 @@ func NewObserver(
return &ob, nil
}
-// Start starts the observer. Returns true if the observer was already started (noop).
+// Start starts the observer. Returns false started (noop).
func (ob *Observer) Start() bool {
ob.mu.Lock()
defer ob.Mu().Unlock()
// noop
if ob.started {
- return true
+ return false
}
ob.started = true
- return false
+ return true
}
// Stop notifies all goroutines to stop and closes the database.
diff --git a/zetaclient/chains/bitcoin/observer/observer.go b/zetaclient/chains/bitcoin/observer/observer.go
index 68e6f91022..6bcdc7fcf6 100644
--- a/zetaclient/chains/bitcoin/observer/observer.go
+++ b/zetaclient/chains/bitcoin/observer/observer.go
@@ -183,7 +183,7 @@ func (ob *Observer) WithBtcClient(client interfaces.BTCRPCClient) {
// Start starts the Go routine processes to observe the Bitcoin chain
func (ob *Observer) Start(ctx context.Context) {
- if noop := ob.Observer.Start(); noop {
+ if ok := ob.Observer.Start(); !ok {
ob.Logger().Chain.Info().Msgf("observer is already started for chain %d", ob.Chain().ChainId)
return
}
diff --git a/zetaclient/chains/evm/observer/observer.go b/zetaclient/chains/evm/observer/observer.go
index 68c0989672..8823b13c47 100644
--- a/zetaclient/chains/evm/observer/observer.go
+++ b/zetaclient/chains/evm/observer/observer.go
@@ -174,7 +174,7 @@ func FetchZetaTokenContract(
// Start all observation routines for the evm chain
func (ob *Observer) Start(ctx context.Context) {
- if noop := ob.Observer.Start(); noop {
+ if ok := ob.Observer.Start(); !ok {
ob.Logger().Chain.Info().Msgf("observer is already started for chain %d", ob.Chain().ChainId)
return
}
diff --git a/zetaclient/chains/solana/observer/observer.go b/zetaclient/chains/solana/observer/observer.go
index 55a5938316..0548fcd6d3 100644
--- a/zetaclient/chains/solana/observer/observer.go
+++ b/zetaclient/chains/solana/observer/observer.go
@@ -98,7 +98,7 @@ func (ob *Observer) WithSolClient(client interfaces.SolanaRPCClient) {
// Start starts the Go routine processes to observe the Solana chain
func (ob *Observer) Start(ctx context.Context) {
- if noop := ob.Observer.Start(); noop {
+ if ok := ob.Observer.Start(); !ok {
ob.Logger().Chain.Info().Msgf("observer is already started for chain %d", ob.Chain().ChainId)
return
}
From 3e3e386d9addb247c335a1547a8bb63a412ea6a5 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Tue, 17 Sep 2024 14:25:38 +0300
Subject: [PATCH 03/36] Add pkg/ticker options
---
pkg/ticker/ticker.go | 79 ++++++++++++++++-------
pkg/ticker/ticker_test.go | 50 ++++++++++++++
zetaclient/chains/evm/observer/inbound.go | 18 ++----
3 files changed, 114 insertions(+), 33 deletions(-)
diff --git a/pkg/ticker/ticker.go b/pkg/ticker/ticker.go
index 566fc03f8b..0e12db2009 100644
--- a/pkg/ticker/ticker.go
+++ b/pkg/ticker/ticker.go
@@ -32,16 +32,19 @@ import (
"sync"
"time"
- "cosmossdk.io/errors"
+ "github.com/rs/zerolog"
)
+// Task is a function that will be called by the Ticker
+type Task func(ctx context.Context, t *Ticker) error
+
// Ticker represents a ticker that will run a function periodically.
// It also invokes BEFORE ticker starts.
type Ticker struct {
- interval time.Duration
- ticker *time.Ticker
- task Task
- signalChan chan struct{}
+ interval time.Duration
+ ticker *time.Ticker
+ task Task
+ internalStopChan chan struct{}
// runnerMu is a mutex to prevent double run
runnerMu sync.Mutex
@@ -50,24 +53,45 @@ type Ticker struct {
stateMu sync.Mutex
stopped bool
+
+ externalStopChan <-chan struct{}
+ logger zerolog.Logger
}
-// Task is a function that will be called by the Ticker
-type Task func(ctx context.Context, t *Ticker) error
+// Opt is a configuration option for the Ticker.
+type Opt func(*Ticker)
-// New creates a new Ticker.
-func New(interval time.Duration, runner Task) *Ticker {
- return &Ticker{interval: interval, task: runner}
+// WithLogger sets the logger for the Ticker.
+func WithLogger(log zerolog.Logger, name string) Opt {
+ return func(t *Ticker) {
+ t.logger = log.With().Str("ticker_name", name).Logger()
+ }
}
-// Run creates and runs a new Ticker.
-func Run(ctx context.Context, interval time.Duration, task Task) error {
- return New(interval, task).Run(ctx)
+// WithStopChan sets the stop channel for the Ticker.
+// Please note that stopChan is NOT signalChan.
+// Stop channel is a trigger for invoking ticker.Stop();
+func WithStopChan(stopChan <-chan struct{}) Opt {
+ return func(cfg *Ticker) { cfg.externalStopChan = stopChan }
}
-// SecondsFromUint64 converts uint64 to time.Duration in seconds.
-func SecondsFromUint64(d uint64) time.Duration {
- return time.Duration(d) * time.Second
+// New creates a new Ticker.
+func New(interval time.Duration, task Task, opts ...Opt) *Ticker {
+ t := &Ticker{
+ interval: interval,
+ task: task,
+ }
+
+ for _, opt := range opts {
+ opt(t)
+ }
+
+ return t
+}
+
+// Run creates and runs a new Ticker.
+func Run(ctx context.Context, interval time.Duration, task Task, opts ...Opt) error {
+ return New(interval, task, opts...).Run(ctx)
}
// Run runs the ticker by blocking current goroutine. It also invokes BEFORE ticker starts.
@@ -88,12 +112,13 @@ func (t *Ticker) Run(ctx context.Context) (err error) {
// setup
t.ticker = time.NewTicker(t.interval)
- t.signalChan = make(chan struct{})
+ t.internalStopChan = make(chan struct{})
t.stopped = false
// initial run
if err := t.task(ctx, t); err != nil {
- return errors.Wrap(err, "ticker task failed")
+ t.Stop()
+ return fmt.Errorf("ticker task failed (initial run): %w", err)
}
for {
@@ -102,9 +127,12 @@ func (t *Ticker) Run(ctx context.Context) (err error) {
return ctx.Err()
case <-t.ticker.C:
if err := t.task(ctx, t); err != nil {
- return errors.Wrap(err, "ticker task failed")
+ return fmt.Errorf("ticker task failed: %w", err)
}
- case <-t.signalChan:
+ case <-t.externalStopChan:
+ t.Stop()
+ return nil
+ case <-t.internalStopChan:
return nil
}
}
@@ -130,11 +158,18 @@ func (t *Ticker) Stop() {
defer t.stateMu.Unlock()
// noop
- if t.stopped || t.signalChan == nil {
+ if t.stopped || t.internalStopChan == nil {
return
}
- close(t.signalChan)
+ close(t.internalStopChan)
t.stopped = true
t.ticker.Stop()
+
+ t.logger.Info().Msgf("Ticker stopped")
+}
+
+// SecondsFromUint64 converts uint64 to time.Duration in seconds.
+func SecondsFromUint64(d uint64) time.Duration {
+ return time.Duration(d) * time.Second
}
diff --git a/pkg/ticker/ticker_test.go b/pkg/ticker/ticker_test.go
index 671091c71f..a86b0eae83 100644
--- a/pkg/ticker/ticker_test.go
+++ b/pkg/ticker/ticker_test.go
@@ -1,12 +1,15 @@
package ticker
import (
+ "bytes"
"context"
"fmt"
"testing"
"time"
+ "github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestTicker(t *testing.T) {
@@ -170,4 +173,51 @@ func TestTicker(t *testing.T) {
assert.ErrorIs(t, err, context.DeadlineExceeded)
assert.Equal(t, 2, counter)
})
+
+ t.Run("With stop channel", func(t *testing.T) {
+ // ARRANGE
+ var (
+ tickerInterval = 100 * time.Millisecond
+ counter = 0
+
+ stopChan = make(chan struct{})
+ sleepBeforeStop = 5*tickerInterval + (10 * time.Millisecond)
+ )
+
+ task := func(ctx context.Context, _ *Ticker) error {
+ t.Logf("Tick %d", counter)
+ counter++
+
+ return nil
+ }
+
+ // ACT
+ go func() {
+ time.Sleep(sleepBeforeStop)
+ close(stopChan)
+ }()
+
+ err := Run(context.Background(), tickerInterval, task, WithStopChan(stopChan))
+
+ // ASSERT
+ require.NoError(t, err)
+ require.Equal(t, 6, counter) // initial tick + 5 more ticks
+ })
+
+ t.Run("With logger", func(t *testing.T) {
+ // ARRANGE
+ out := &bytes.Buffer{}
+ logger := zerolog.New(out)
+
+ // ACT
+ task := func(ctx context.Context, _ *Ticker) error {
+ return fmt.Errorf("hey")
+ }
+
+ err := Run(context.Background(), time.Second, task, WithLogger(logger, "my-task"))
+
+ // ARRANGE
+ require.ErrorContains(t, err, "hey")
+ require.Contains(t, out.String(), `{"level":"info","ticker_name":"my-task","message":"Ticker stopped"}`)
+ })
}
diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go
index 0ad5ba6b6c..c662fc2d60 100644
--- a/zetaclient/chains/evm/observer/inbound.go
+++ b/zetaclient/chains/evm/observer/inbound.go
@@ -20,7 +20,6 @@ import (
"github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol"
"github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.non-eth.sol"
- "github.com/zeta-chain/node/pkg/bg"
"github.com/zeta-chain/node/pkg/chains"
"github.com/zeta-chain/node/pkg/coin"
"github.com/zeta-chain/node/pkg/constant"
@@ -44,18 +43,15 @@ func (ob *Observer) WatchInbound(ctx context.Context) error {
return ob.watchInboundOnce(ctx, t, sampledLogger)
}
- t := ticker.New(interval, task)
-
- bg.Work(ctx, func(_ context.Context) error {
- <-ob.StopChannel()
- t.Stop()
- ob.Logger().Inbound.Info().Msg("WatchInbound stopped")
- return nil
- })
-
ob.Logger().Inbound.Info().Msgf("WatchInbound started")
- return t.Run(ctx)
+ return ticker.Run(
+ ctx,
+ interval,
+ task,
+ ticker.WithStopChan(ob.StopChannel()),
+ ticker.WithLogger(ob.Logger().Inbound, "WatchInbound"),
+ )
}
func (ob *Observer) watchInboundOnce(ctx context.Context, t *ticker.Ticker, sampledLogger zerolog.Logger) error {
From 555d56084bb7e452cf040263e7b2b3384ef538d9 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Tue, 17 Sep 2024 16:41:12 +0300
Subject: [PATCH 04/36] Add TON to pkg/chains & protos
---
docs/openapi/openapi.swagger.yaml | 4 +
pkg/chains/chain_test.go | 6 +-
pkg/chains/chains.go | 48 +++++--
pkg/chains/chains.pb.go | 130 ++++++++++--------
pkg/chains/chains_test.go | 17 ++-
.../zetacore/pkg/chains/chains.proto | 3 +
.../zetacore/pkg/chains/chains_pb.d.ts | 17 +++
7 files changed, 150 insertions(+), 75 deletions(-)
diff --git a/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml
index 819122175f..182f2138e8 100644
--- a/docs/openapi/openapi.swagger.yaml
+++ b/docs/openapi/openapi.swagger.yaml
@@ -56995,7 +56995,9 @@ definitions:
- bitcoin
- op_stack
- solana_consensus
+ - catchain_consensus
default: ethereum
+ description: '- catchain_consensus: ton'
title: |-
Consensus represents the consensus algorithm used by the chain
this can represent the consensus of a L1
@@ -57011,6 +57013,7 @@ definitions:
- optimism
- base
- solana
+ - ton
default: eth
title: |-
Network represents the network of the chain
@@ -57045,6 +57048,7 @@ definitions:
- no_vm
- evm
- svm
+ - tvm
default: no_vm
title: |-
Vm represents the virtual machine type of the chain to support smart
diff --git a/pkg/chains/chain_test.go b/pkg/chains/chain_test.go
index d664e9c7fd..2b2dfd7e77 100644
--- a/pkg/chains/chain_test.go
+++ b/pkg/chains/chain_test.go
@@ -73,7 +73,7 @@ func TestChain_Validate(t *testing.T) {
chain: chains.Chain{
ChainId: 42,
Name: "foo",
- Network: chains.Network_solana + 1,
+ Network: chains.Network_ton + 1,
NetworkType: chains.NetworkType_testnet,
Vm: chains.Vm_evm,
Consensus: chains.Consensus_op_stack,
@@ -101,7 +101,7 @@ func TestChain_Validate(t *testing.T) {
Name: "foo",
Network: chains.Network_base,
NetworkType: chains.NetworkType_devnet,
- Vm: chains.Vm_svm + 1,
+ Vm: chains.Vm_tvm + 1,
Consensus: chains.Consensus_op_stack,
IsExternal: true,
},
@@ -115,7 +115,7 @@ func TestChain_Validate(t *testing.T) {
Network: chains.Network_base,
NetworkType: chains.NetworkType_devnet,
Vm: chains.Vm_evm,
- Consensus: chains.Consensus_solana_consensus + 1,
+ Consensus: chains.Consensus_catchain_consensus + 1,
IsExternal: true,
},
errStr: "invalid consensus",
diff --git a/pkg/chains/chains.go b/pkg/chains/chains.go
index da449af748..03b993f37e 100644
--- a/pkg/chains/chains.go
+++ b/pkg/chains/chains.go
@@ -113,6 +113,18 @@ var (
Name: "solana_mainnet",
}
+ TONMainnet = Chain{
+ // T[20] O[15] N[14] mainnet[0] :)
+ ChainId: 2015140,
+ Network: Network_ton,
+ NetworkType: NetworkType_mainnet,
+ Vm: Vm_tvm,
+ Consensus: Consensus_catchain_consensus,
+ IsExternal: true,
+ CctxGateway: CCTXGateway_observers,
+ Name: "ton_mainnet",
+ }
+
/**
* Testnet chains
*/
@@ -225,6 +237,17 @@ var (
Name: "solana_devnet",
}
+ TONTestnet = Chain{
+ ChainId: 2015141,
+ Network: Network_ton,
+ NetworkType: NetworkType_testnet,
+ Vm: Vm_tvm,
+ Consensus: Consensus_catchain_consensus,
+ IsExternal: true,
+ CctxGateway: CCTXGateway_observers,
+ Name: "ton_testnet",
+ }
+
/**
* Devnet chains
*/
@@ -301,6 +324,17 @@ var (
Name: "solana_localnet",
}
+ TONLocalnet = Chain{
+ ChainId: 2015142,
+ Network: Network_ton,
+ NetworkType: NetworkType_privnet,
+ Vm: Vm_tvm,
+ Consensus: Consensus_catchain_consensus,
+ IsExternal: true,
+ CctxGateway: CCTXGateway_observers,
+ Name: "ton_localnet",
+ }
+
/**
* Deprecated chains
*/
@@ -366,6 +400,9 @@ func DefaultChainsList() []Chain {
SolanaMainnet,
SolanaDevnet,
SolanaLocalnet,
+ TONMainnet,
+ TONTestnet,
+ TONLocalnet,
}
}
@@ -423,17 +460,6 @@ func ChainListByGateway(gateway CCTXGateway, additionalChains []Chain) []Chain {
return chainList
}
-// ChainListForHeaderSupport returns a list of chains that support headers
-func ChainListForHeaderSupport(additionalChains []Chain) []Chain {
- var chainList []Chain
- for _, chain := range CombineDefaultChainsList(additionalChains) {
- if chain.Consensus == Consensus_ethereum || chain.Consensus == Consensus_bitcoin {
- chainList = append(chainList, chain)
- }
- }
- return chainList
-}
-
// ZetaChainFromCosmosChainID returns a ZetaChain chain object from a Cosmos chain ID
func ZetaChainFromCosmosChainID(chainID string) (Chain, error) {
ethChainID, err := CosmosToEthChainID(chainID)
diff --git a/pkg/chains/chains.pb.go b/pkg/chains/chains.pb.go
index 217ad55328..ad5ec2f77a 100644
--- a/pkg/chains/chains.pb.go
+++ b/pkg/chains/chains.pb.go
@@ -156,6 +156,7 @@ const (
Network_optimism Network = 5
Network_base Network = 6
Network_solana Network = 7
+ Network_ton Network = 8
)
var Network_name = map[int32]string{
@@ -167,6 +168,7 @@ var Network_name = map[int32]string{
5: "optimism",
6: "base",
7: "solana",
+ 8: "ton",
}
var Network_value = map[string]int32{
@@ -178,6 +180,7 @@ var Network_value = map[string]int32{
"optimism": 5,
"base": 6,
"solana": 7,
+ "ton": 8,
}
func (x Network) String() string {
@@ -229,18 +232,21 @@ const (
Vm_no_vm Vm = 0
Vm_evm Vm = 1
Vm_svm Vm = 2
+ Vm_tvm Vm = 3
)
var Vm_name = map[int32]string{
0: "no_vm",
1: "evm",
2: "svm",
+ 3: "tvm",
}
var Vm_value = map[string]int32{
"no_vm": 0,
"evm": 1,
"svm": 2,
+ "tvm": 3,
}
func (x Vm) String() string {
@@ -257,11 +263,12 @@ func (Vm) EnumDescriptor() ([]byte, []int) {
type Consensus int32
const (
- Consensus_ethereum Consensus = 0
- Consensus_tendermint Consensus = 1
- Consensus_bitcoin Consensus = 2
- Consensus_op_stack Consensus = 3
- Consensus_solana_consensus Consensus = 4
+ Consensus_ethereum Consensus = 0
+ Consensus_tendermint Consensus = 1
+ Consensus_bitcoin Consensus = 2
+ Consensus_op_stack Consensus = 3
+ Consensus_solana_consensus Consensus = 4
+ Consensus_catchain_consensus Consensus = 5
)
var Consensus_name = map[int32]string{
@@ -270,14 +277,16 @@ var Consensus_name = map[int32]string{
2: "bitcoin",
3: "op_stack",
4: "solana_consensus",
+ 5: "catchain_consensus",
}
var Consensus_value = map[string]int32{
- "ethereum": 0,
- "tendermint": 1,
- "bitcoin": 2,
- "op_stack": 3,
- "solana_consensus": 4,
+ "ethereum": 0,
+ "tendermint": 1,
+ "bitcoin": 2,
+ "op_stack": 3,
+ "solana_consensus": 4,
+ "catchain_consensus": 5,
}
func (x Consensus) String() string {
@@ -455,56 +464,57 @@ func init() {
}
var fileDescriptor_236b85e7bff6130d = []byte{
- // 770 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0xcd, 0x8e, 0xe4, 0x34,
- 0x10, 0xee, 0x24, 0xfd, 0x5b, 0x3d, 0x3f, 0x5e, 0xef, 0x00, 0x61, 0x25, 0x9a, 0x01, 0x09, 0x68,
- 0x8d, 0xa0, 0x47, 0xc0, 0x91, 0x03, 0x68, 0x47, 0x2c, 0x42, 0x88, 0x3d, 0x84, 0xd5, 0x0a, 0x71,
- 0x69, 0xdc, 0xee, 0x22, 0x6d, 0x75, 0x6c, 0x47, 0xb1, 0x3b, 0xbb, 0xcd, 0x53, 0xf0, 0x10, 0x1c,
- 0x90, 0x78, 0x11, 0x8e, 0x7b, 0xe4, 0x88, 0x66, 0x1e, 0x04, 0x64, 0xc7, 0x49, 0x0f, 0x97, 0x9d,
- 0x39, 0xc5, 0xfe, 0xf2, 0x7d, 0x55, 0x5f, 0x55, 0xd9, 0x86, 0x8b, 0x5f, 0xd1, 0x32, 0xbe, 0x61,
- 0x42, 0x5d, 0xfa, 0x95, 0xae, 0xf0, 0xb2, 0xdc, 0xe6, 0x97, 0x1e, 0x32, 0xe1, 0xb3, 0x28, 0x2b,
- 0x6d, 0x35, 0x7d, 0xa7, 0xe3, 0x2e, 0x5a, 0xee, 0xa2, 0xdc, 0xe6, 0x8b, 0x86, 0xf4, 0xe8, 0x2c,
- 0xd7, 0xb9, 0xf6, 0xcc, 0x4b, 0xb7, 0x6a, 0x44, 0xef, 0xff, 0x9b, 0xc0, 0xe0, 0xca, 0x11, 0xe8,
- 0xdb, 0x30, 0xf6, 0xcc, 0xa5, 0x58, 0xa7, 0xf1, 0x79, 0x34, 0x4f, 0xb2, 0x91, 0xdf, 0x7f, 0xbb,
- 0xa6, 0xdf, 0x01, 0x34, 0xbf, 0x14, 0x93, 0x98, 0x46, 0xe7, 0xd1, 0xfc, 0xe4, 0xb3, 0xf9, 0xe2,
- 0xb5, 0xe9, 0x16, 0x3e, 0xe8, 0x53, 0x26, 0xf1, 0x71, 0x9c, 0x46, 0xd9, 0x84, 0xb7, 0x5b, 0xfa,
- 0x15, 0x8c, 0x14, 0xda, 0x17, 0xba, 0xda, 0xa6, 0x89, 0x8f, 0xf4, 0xe1, 0x1d, 0x91, 0x9e, 0x36,
- 0xec, 0xac, 0x95, 0xd1, 0xef, 0xe1, 0x28, 0x2c, 0x97, 0x76, 0x5f, 0x62, 0xda, 0xf7, 0x61, 0x2e,
- 0xee, 0x17, 0xe6, 0xd9, 0xbe, 0xc4, 0x6c, 0xaa, 0x0e, 0x1b, 0xfa, 0x29, 0xc4, 0xb5, 0x4c, 0x07,
- 0x3e, 0xc8, 0x7b, 0x77, 0x04, 0x79, 0x2e, 0xb3, 0xb8, 0x96, 0xf4, 0x09, 0x4c, 0xb8, 0x56, 0x06,
- 0x95, 0xd9, 0x99, 0x74, 0x78, 0xbf, 0x7e, 0xb4, 0xfc, 0xec, 0x20, 0xa5, 0xef, 0xc2, 0x54, 0x98,
- 0x25, 0xbe, 0xb4, 0x58, 0x29, 0x56, 0xa4, 0xa3, 0xf3, 0x68, 0x3e, 0xce, 0x40, 0x98, 0xaf, 0x03,
- 0xe2, 0x4a, 0xe5, 0xdc, 0xbe, 0x5c, 0xe6, 0xcc, 0xe2, 0x0b, 0xb6, 0x4f, 0xc7, 0xf7, 0x2a, 0xf5,
- 0xea, 0xea, 0xd9, 0x8f, 0xdf, 0x34, 0x8a, 0x6c, 0xea, 0xf4, 0x61, 0x43, 0x29, 0xf4, 0xfd, 0x08,
- 0x27, 0xe7, 0xd1, 0x7c, 0x92, 0xf9, 0xf5, 0xc5, 0x17, 0x70, 0x9c, 0x21, 0x47, 0x51, 0xe3, 0x0f,
- 0x96, 0xd9, 0x9d, 0xa1, 0x53, 0x18, 0xf1, 0x0a, 0x99, 0xc5, 0x35, 0xe9, 0xb9, 0x8d, 0xd9, 0x71,
- 0x8e, 0xc6, 0x90, 0x88, 0x02, 0x0c, 0x7f, 0x61, 0xa2, 0xc0, 0x35, 0x89, 0x1f, 0xf5, 0xff, 0xf8,
- 0x7d, 0x16, 0x5d, 0xfc, 0x99, 0xc0, 0xa4, 0x9b, 0x34, 0x9d, 0xc0, 0x00, 0x65, 0x69, 0xf7, 0xa4,
- 0x47, 0x4f, 0x61, 0x8a, 0x76, 0xb3, 0x94, 0x4c, 0x28, 0x85, 0x96, 0x44, 0x94, 0xc0, 0x91, 0xb3,
- 0xda, 0x21, 0xb1, 0xa3, 0xac, 0x2c, 0xef, 0x80, 0x84, 0x3e, 0x84, 0xd3, 0x52, 0x17, 0xfb, 0x5c,
- 0xab, 0x0e, 0xec, 0x7b, 0x96, 0x39, 0xb0, 0x06, 0x94, 0xc2, 0x49, 0xae, 0xb1, 0x2a, 0xc4, 0xd2,
- 0xa2, 0xb1, 0x0e, 0x1b, 0x3a, 0x4c, 0xee, 0xe4, 0x8a, 0x1d, 0xb0, 0x51, 0x2b, 0x6c, 0x01, 0xe8,
- 0x1c, 0xb4, 0xc8, 0xb4, 0x75, 0xd0, 0x02, 0x47, 0xce, 0x81, 0xc1, 0x52, 0x17, 0xe2, 0xc0, 0x3a,
- 0x76, 0x60, 0x48, 0x58, 0x68, 0xce, 0x0a, 0x07, 0x9e, 0xb4, 0xd2, 0x0a, 0x73, 0x47, 0x24, 0xa7,
- 0x2e, 0x3a, 0x93, 0x7a, 0xdf, 0xe9, 0x08, 0x3d, 0x03, 0xa2, 0x4b, 0x2b, 0xa4, 0x30, 0xb2, 0xb3,
- 0xff, 0xe0, 0x7f, 0x68, 0xc8, 0x45, 0xa8, 0x53, 0xaf, 0x98, 0xc1, 0x8e, 0xf7, 0xb0, 0x43, 0x5a,
- 0xce, 0x99, 0x2b, 0xd2, 0xe8, 0x82, 0xa9, 0x43, 0x0f, 0xdf, 0xa0, 0x0f, 0xe0, 0x38, 0x60, 0x6b,
- 0xac, 0x1d, 0xf4, 0xa6, 0xaf, 0xa1, 0x81, 0x3a, 0xbb, 0x6f, 0x85, 0x69, 0x21, 0x8c, 0xc2, 0x2d,
- 0xa0, 0x23, 0x48, 0xd0, 0x6e, 0x48, 0x8f, 0x8e, 0xa1, 0xef, 0xba, 0x42, 0x22, 0x07, 0xad, 0x2c,
- 0x27, 0xb1, 0x9b, 0x79, 0x98, 0x03, 0x49, 0x3c, 0x6a, 0x38, 0xe9, 0xd3, 0x23, 0x18, 0xb7, 0xc6,
- 0xc9, 0xc0, 0xc9, 0x9c, 0x3d, 0x32, 0x74, 0x87, 0xa2, 0xc9, 0x47, 0x46, 0x21, 0xcd, 0x13, 0x98,
- 0xde, 0xba, 0x6c, 0x2e, 0x5c, 0x6b, 0xd8, 0x9f, 0xa7, 0xb6, 0x43, 0x91, 0x4f, 0x54, 0x89, 0xba,
- 0x39, 0x0e, 0x00, 0xc3, 0x50, 0x43, 0x12, 0xe2, 0x7c, 0x04, 0xf1, 0x73, 0xe9, 0x0e, 0x95, 0xd2,
- 0xcb, 0x5a, 0x92, 0x9e, 0x37, 0x5d, 0xcb, 0xc6, 0xaa, 0xa9, 0x65, 0x77, 0x0a, 0x7f, 0x86, 0x49,
- 0x77, 0xbd, 0x9c, 0x4f, 0xb4, 0x1b, 0xac, 0x70, 0xe7, 0x24, 0x27, 0x00, 0x16, 0xd5, 0x1a, 0x2b,
- 0x29, 0x54, 0x48, 0xb9, 0x12, 0x96, 0x6b, 0xa1, 0x48, 0xdc, 0x94, 0xb4, 0x34, 0x96, 0xf1, 0x2d,
- 0x49, 0xdc, 0x64, 0x42, 0xe3, 0xba, 0x0b, 0x4a, 0xfa, 0x21, 0xc3, 0xc7, 0x30, 0xbd, 0x75, 0xa9,
- 0x9a, 0xa6, 0x79, 0x4b, 0xc7, 0x30, 0xd1, 0x2b, 0x83, 0x55, 0x8d, 0x95, 0x21, 0x51, 0xc3, 0x7e,
- 0xfc, 0xe5, 0x5f, 0xd7, 0xb3, 0xe8, 0xd5, 0xf5, 0x2c, 0xfa, 0xe7, 0x7a, 0x16, 0xfd, 0x76, 0x33,
- 0xeb, 0xbd, 0xba, 0x99, 0xf5, 0xfe, 0xbe, 0x99, 0xf5, 0x7e, 0xfa, 0x20, 0x17, 0x76, 0xb3, 0x5b,
- 0x2d, 0xb8, 0x96, 0xfe, 0x41, 0xff, 0xa4, 0x79, 0xdb, 0x95, 0x5e, 0xdf, 0x7e, 0xd7, 0x57, 0x43,
- 0xff, 0x38, 0x7f, 0xfe, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x86, 0x0b, 0xc3, 0x03, 0xff, 0x05,
- 0x00, 0x00,
+ // 789 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0xcd, 0x8e, 0x23, 0x35,
+ 0x10, 0x4e, 0x77, 0xe7, 0xb7, 0x32, 0x3f, 0x5e, 0xef, 0xb0, 0x34, 0x2b, 0x11, 0x06, 0x24, 0x50,
+ 0x34, 0x82, 0x0c, 0x3f, 0x47, 0x0e, 0xa0, 0x1d, 0xb1, 0x08, 0x21, 0xf6, 0xd0, 0xac, 0x56, 0x88,
+ 0x4b, 0xe4, 0x38, 0x45, 0xc7, 0x4a, 0xdb, 0x6e, 0xb5, 0x9d, 0xde, 0x09, 0x4f, 0xc1, 0x43, 0x70,
+ 0x40, 0xe2, 0x45, 0x38, 0xee, 0x91, 0x23, 0x9a, 0x79, 0x10, 0x90, 0xdd, 0xee, 0xce, 0x70, 0xd9,
+ 0x9d, 0x53, 0xec, 0x2f, 0xdf, 0x57, 0xf5, 0x55, 0x95, 0xed, 0x86, 0x8b, 0x5f, 0xd1, 0x32, 0xbe,
+ 0x61, 0x42, 0x5d, 0xfa, 0x95, 0xae, 0xf0, 0xb2, 0xdc, 0xe6, 0x97, 0x1e, 0x32, 0xe1, 0x67, 0x51,
+ 0x56, 0xda, 0x6a, 0xfa, 0x6e, 0xc7, 0x5d, 0xb4, 0xdc, 0x45, 0xb9, 0xcd, 0x17, 0x0d, 0xe9, 0xf1,
+ 0x59, 0xae, 0x73, 0xed, 0x99, 0x97, 0x6e, 0xd5, 0x88, 0x3e, 0xf8, 0x37, 0x81, 0xc1, 0x95, 0x23,
+ 0xd0, 0x77, 0x60, 0xec, 0x99, 0x4b, 0xb1, 0x4e, 0xe3, 0xf3, 0x68, 0x9e, 0x64, 0x23, 0xbf, 0xff,
+ 0x6e, 0x4d, 0xbf, 0x07, 0x68, 0xfe, 0x52, 0x4c, 0x62, 0x1a, 0x9d, 0x47, 0xf3, 0x93, 0xcf, 0xe7,
+ 0x8b, 0xd7, 0xa6, 0x5b, 0xf8, 0xa0, 0xcf, 0x98, 0xc4, 0x27, 0x71, 0x1a, 0x65, 0x13, 0xde, 0x6e,
+ 0xe9, 0xd7, 0x30, 0x52, 0x68, 0x5f, 0xea, 0x6a, 0x9b, 0x26, 0x3e, 0xd2, 0x47, 0x6f, 0x88, 0xf4,
+ 0xac, 0x61, 0x67, 0xad, 0x8c, 0xfe, 0x00, 0x47, 0x61, 0xb9, 0xb4, 0xfb, 0x12, 0xd3, 0xbe, 0x0f,
+ 0x73, 0x71, 0xbf, 0x30, 0xcf, 0xf7, 0x25, 0x66, 0x53, 0x75, 0xd8, 0xd0, 0xcf, 0x20, 0xae, 0x65,
+ 0x3a, 0xf0, 0x41, 0xde, 0x7f, 0x43, 0x90, 0x17, 0x32, 0x8b, 0x6b, 0x49, 0x9f, 0xc2, 0x84, 0x6b,
+ 0x65, 0x50, 0x99, 0x9d, 0x49, 0x87, 0xf7, 0xeb, 0x47, 0xcb, 0xcf, 0x0e, 0x52, 0xfa, 0x1e, 0x4c,
+ 0x85, 0x59, 0xe2, 0xb5, 0xc5, 0x4a, 0xb1, 0x22, 0x1d, 0x9d, 0x47, 0xf3, 0x71, 0x06, 0xc2, 0x7c,
+ 0x13, 0x10, 0x57, 0x2a, 0xe7, 0xf6, 0x7a, 0x99, 0x33, 0x8b, 0x2f, 0xd9, 0x3e, 0x1d, 0xdf, 0xab,
+ 0xd4, 0xab, 0xab, 0xe7, 0x3f, 0x7d, 0xdb, 0x28, 0xb2, 0xa9, 0xd3, 0x87, 0x0d, 0xa5, 0xd0, 0xf7,
+ 0x23, 0x9c, 0x9c, 0x47, 0xf3, 0x49, 0xe6, 0xd7, 0x17, 0x5f, 0xc2, 0x71, 0x86, 0x1c, 0x45, 0x8d,
+ 0x3f, 0x5a, 0x66, 0x77, 0x86, 0x4e, 0x61, 0xc4, 0x2b, 0x64, 0x16, 0xd7, 0xa4, 0xe7, 0x36, 0x66,
+ 0xc7, 0x39, 0x1a, 0x43, 0x22, 0x0a, 0x30, 0xfc, 0x85, 0x89, 0x02, 0xd7, 0x24, 0x7e, 0xdc, 0xff,
+ 0xe3, 0xf7, 0x59, 0x74, 0xf1, 0x67, 0x02, 0x93, 0x6e, 0xd2, 0x74, 0x02, 0x03, 0x94, 0xa5, 0xdd,
+ 0x93, 0x1e, 0x3d, 0x85, 0x29, 0xda, 0xcd, 0x52, 0x32, 0xa1, 0x14, 0x5a, 0x12, 0x51, 0x02, 0x47,
+ 0xce, 0x6a, 0x87, 0xc4, 0x8e, 0xb2, 0xb2, 0xbc, 0x03, 0x12, 0xfa, 0x10, 0x4e, 0x4b, 0x5d, 0xec,
+ 0x73, 0xad, 0x3a, 0xb0, 0xef, 0x59, 0xe6, 0xc0, 0x1a, 0x50, 0x0a, 0x27, 0xb9, 0xc6, 0xaa, 0x10,
+ 0x4b, 0x8b, 0xc6, 0x3a, 0x6c, 0xe8, 0x30, 0xb9, 0x93, 0x2b, 0x76, 0xc0, 0x46, 0xad, 0xb0, 0x05,
+ 0xa0, 0x73, 0xd0, 0x22, 0xd3, 0xd6, 0x41, 0x0b, 0x1c, 0x39, 0x07, 0x06, 0x4b, 0x5d, 0x88, 0x03,
+ 0xeb, 0xd8, 0x81, 0x21, 0x61, 0xa1, 0x39, 0x2b, 0x1c, 0x78, 0xd2, 0x4a, 0x2b, 0xcc, 0x1d, 0x91,
+ 0x9c, 0xba, 0xe8, 0x4c, 0xea, 0x7d, 0xa7, 0x23, 0xf4, 0x0c, 0x88, 0x2e, 0xad, 0x90, 0xc2, 0xc8,
+ 0xce, 0xfe, 0x83, 0xff, 0xa1, 0x21, 0x17, 0xa1, 0x4e, 0xbd, 0x62, 0x06, 0x3b, 0xde, 0xc3, 0x0e,
+ 0x69, 0x39, 0x67, 0xae, 0x48, 0xa3, 0x0b, 0xa6, 0x0e, 0x3d, 0x7c, 0x8b, 0x3e, 0x80, 0xe3, 0x80,
+ 0xad, 0xb1, 0x76, 0xd0, 0x23, 0x5f, 0x43, 0x03, 0x75, 0x76, 0xdf, 0x0e, 0xd3, 0x52, 0x30, 0x0a,
+ 0xb7, 0x80, 0x8e, 0x20, 0x41, 0xbb, 0x21, 0x3d, 0x3a, 0x86, 0xbe, 0xeb, 0x0a, 0x89, 0x1c, 0xb4,
+ 0xb2, 0x9c, 0xc4, 0x6e, 0xe6, 0x61, 0x0e, 0x24, 0xf1, 0xa8, 0xe1, 0xa4, 0x4f, 0x8f, 0x60, 0xdc,
+ 0x1a, 0x27, 0x03, 0x27, 0x73, 0xf6, 0xc8, 0xd0, 0x1d, 0x8a, 0x26, 0x1f, 0x19, 0x39, 0xb2, 0xd5,
+ 0x8a, 0x8c, 0x43, 0xbe, 0xa7, 0x30, 0xbd, 0x73, 0xeb, 0x5c, 0xdc, 0xd6, 0xb9, 0x3f, 0x58, 0x6d,
+ 0xab, 0x22, 0x9f, 0xb1, 0x12, 0x75, 0x73, 0x2e, 0x00, 0x86, 0xa1, 0x98, 0x24, 0xc4, 0xf9, 0x14,
+ 0xe2, 0x17, 0xd2, 0x9d, 0x2e, 0xa5, 0x97, 0xb5, 0x24, 0x3d, 0xef, 0xbe, 0x96, 0x8d, 0x67, 0x53,
+ 0x4b, 0x12, 0xfb, 0xcc, 0xb5, 0xec, 0x14, 0xd7, 0x30, 0xe9, 0x2e, 0x9c, 0x73, 0x8e, 0x76, 0x83,
+ 0x15, 0xee, 0x9c, 0xf6, 0x04, 0xc0, 0xa2, 0x5a, 0x63, 0x25, 0x85, 0x0a, 0xb9, 0x57, 0xc2, 0x72,
+ 0x2d, 0x14, 0x89, 0x9b, 0x22, 0x97, 0xc6, 0x32, 0xbe, 0x25, 0x89, 0x9b, 0x55, 0x68, 0x65, 0x77,
+ 0x65, 0x49, 0x9f, 0x3e, 0x02, 0xca, 0x99, 0x6d, 0x1e, 0xc4, 0x03, 0x3e, 0x08, 0x99, 0x3f, 0x86,
+ 0xe9, 0x9d, 0xeb, 0xd7, 0xb4, 0xd7, 0x7b, 0x3e, 0x86, 0x89, 0x5e, 0x19, 0xac, 0x6a, 0xac, 0x0c,
+ 0x89, 0x1a, 0xf6, 0x93, 0xaf, 0xfe, 0xba, 0x99, 0x45, 0xaf, 0x6e, 0x66, 0xd1, 0x3f, 0x37, 0xb3,
+ 0xe8, 0xb7, 0xdb, 0x59, 0xef, 0xd5, 0xed, 0xac, 0xf7, 0xf7, 0xed, 0xac, 0xf7, 0xf3, 0x87, 0xb9,
+ 0xb0, 0x9b, 0xdd, 0x6a, 0xc1, 0xb5, 0xf4, 0x4f, 0xff, 0x27, 0xcd, 0x57, 0x40, 0xe9, 0xf5, 0xdd,
+ 0x2f, 0xc0, 0x6a, 0xe8, 0x9f, 0xf1, 0x2f, 0xfe, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xb9, 0xaa, 0xd0,
+ 0x56, 0x29, 0x06, 0x00, 0x00,
}
func (m *Chain) Marshal() (dAtA []byte, err error) {
diff --git a/pkg/chains/chains_test.go b/pkg/chains/chains_test.go
index d6e1456434..aca3c6ff6d 100644
--- a/pkg/chains/chains_test.go
+++ b/pkg/chains/chains_test.go
@@ -11,7 +11,10 @@ import (
func TestChain_Name(t *testing.T) {
t.Run("new Name field is compatible with ChainName enum", func(t *testing.T) {
for _, chain := range chains.DefaultChainsList() {
- require.EqualValues(t, chain.Name, chain.ChainName.String())
+ chainName := chain.ChainName.String()
+ if chainName != "empty" {
+ require.EqualValues(t, chain.Name, chainName)
+ }
}
})
}
@@ -34,6 +37,7 @@ func TestChainListByNetworkType(t *testing.T) {
chains.OptimismMainnet,
chains.BaseMainnet,
chains.SolanaMainnet,
+ chains.TONMainnet,
},
},
{
@@ -50,6 +54,7 @@ func TestChainListByNetworkType(t *testing.T) {
chains.OptimismSepolia,
chains.BaseSepolia,
chains.SolanaDevnet,
+ chains.TONTestnet,
},
},
{
@@ -60,6 +65,7 @@ func TestChainListByNetworkType(t *testing.T) {
chains.BitcoinRegtest,
chains.GoerliLocalnet,
chains.SolanaLocalnet,
+ chains.TONLocalnet,
},
},
}
@@ -156,6 +162,9 @@ func TestDefaultChainList(t *testing.T) {
chains.SolanaMainnet,
chains.SolanaDevnet,
chains.SolanaLocalnet,
+ chains.TONMainnet,
+ chains.TONTestnet,
+ chains.TONLocalnet,
}, chains.DefaultChainsList())
}
@@ -188,6 +197,9 @@ func TestChainListByGateway(t *testing.T) {
chains.SolanaMainnet,
chains.SolanaDevnet,
chains.SolanaLocalnet,
+ chains.TONMainnet,
+ chains.TONTestnet,
+ chains.TONLocalnet,
},
},
{
@@ -230,6 +242,9 @@ func TestExternalChainList(t *testing.T) {
chains.SolanaMainnet,
chains.SolanaDevnet,
chains.SolanaLocalnet,
+ chains.TONMainnet,
+ chains.TONTestnet,
+ chains.TONLocalnet,
}, chains.ExternalChainList([]chains.Chain{}))
}
diff --git a/proto/zetachain/zetacore/pkg/chains/chains.proto b/proto/zetachain/zetacore/pkg/chains/chains.proto
index 63b146cf42..dca1db9fa8 100644
--- a/proto/zetachain/zetacore/pkg/chains/chains.proto
+++ b/proto/zetachain/zetacore/pkg/chains/chains.proto
@@ -63,6 +63,7 @@ enum Network {
optimism = 5;
base = 6;
solana = 7;
+ ton = 8;
}
// NetworkType represents the network type of the chain
@@ -82,6 +83,7 @@ enum Vm {
no_vm = 0;
evm = 1;
svm = 2;
+ tvm = 3;
}
// Consensus represents the consensus algorithm used by the chain
@@ -94,6 +96,7 @@ enum Consensus {
bitcoin = 2;
op_stack = 3;
solana_consensus = 4;
+ catchain_consensus = 5; // ton
}
// CCTXGateway describes for the chain the gateway used to handle CCTX outbounds
diff --git a/typescript/zetachain/zetacore/pkg/chains/chains_pb.d.ts b/typescript/zetachain/zetacore/pkg/chains/chains_pb.d.ts
index af161efd7b..9e68854962 100644
--- a/typescript/zetachain/zetacore/pkg/chains/chains_pb.d.ts
+++ b/typescript/zetachain/zetacore/pkg/chains/chains_pb.d.ts
@@ -197,6 +197,11 @@ export declare enum Network {
* @generated from enum value: solana = 7;
*/
solana = 7,
+
+ /**
+ * @generated from enum value: ton = 8;
+ */
+ ton = 8,
}
/**
@@ -248,6 +253,11 @@ export declare enum Vm {
* @generated from enum value: svm = 2;
*/
svm = 2,
+
+ /**
+ * @generated from enum value: tvm = 3;
+ */
+ tvm = 3,
}
/**
@@ -282,6 +292,13 @@ export declare enum Consensus {
* @generated from enum value: solana_consensus = 4;
*/
solana_consensus = 4,
+
+ /**
+ * ton
+ *
+ * @generated from enum value: catchain_consensus = 5;
+ */
+ catchain_consensus = 5,
}
/**
From 28b12985d4e58ba0f515e0366ca6a47c0aec5ad4 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Wed, 18 Sep 2024 13:05:51 +0300
Subject: [PATCH 05/36] Add liteapi wrapper client; locate first tx
---
zetaclient/chains/ton/liteapi/client.go | 90 +++++++++++++++++++
.../chains/ton/liteapi/client_live_test.go | 82 +++++++++++++++++
zetaclient/chains/ton/liteapi/client_test.go | 18 ++++
3 files changed, 190 insertions(+)
create mode 100644 zetaclient/chains/ton/liteapi/client.go
create mode 100644 zetaclient/chains/ton/liteapi/client_live_test.go
create mode 100644 zetaclient/chains/ton/liteapi/client_test.go
diff --git a/zetaclient/chains/ton/liteapi/client.go b/zetaclient/chains/ton/liteapi/client.go
new file mode 100644
index 0000000000..0af19c5d72
--- /dev/null
+++ b/zetaclient/chains/ton/liteapi/client.go
@@ -0,0 +1,90 @@
+package liteapi
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/tonkeeper/tongo/liteapi"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/ton"
+)
+
+// Client extends tongo's liteapi.Client with some high-level tools
+type Client struct {
+ *liteapi.Client
+}
+
+// GetFirstTransaction scrolls through the transactions of the given account to find the first one.
+// Note that it will fail in case of old transactions. Ideally, use archival node.
+// Also returns the number of scrolled transactions for this account i.e. total transactions
+func (c *Client) GetFirstTransaction(ctx context.Context, acc ton.AccountID) (*ton.Transaction, int, error) {
+ const pageSize = 100
+
+ state, err := c.GetAccountState(ctx, acc)
+ if err != nil {
+ return nil, 0, errors.Wrap(err, "unable to get account state")
+ }
+
+ if state.Account.Status() != tlb.AccountActive {
+ return nil, 0, errors.New("account is not active")
+ }
+
+ var tx *ton.Transaction
+
+ // logical time and hash of the last transaction
+ lt, hash, scrolled := state.LastTransLt, state.LastTransHash, 0
+
+ for {
+ hashBits := ton.Bits256(hash)
+
+ txs, err := c.GetTransactions(ctx, pageSize, acc, lt, hashBits)
+ if err != nil {
+ return nil, scrolled, errors.Wrapf(err, "unable to get transactions [lt %d, hash %s]", lt, hashBits.Hex())
+ }
+
+ if len(txs) == 0 {
+ break
+ }
+
+ scrolled += len(txs)
+
+ tx = &txs[len(txs)-1]
+
+ // Not we take the latest item in the list (oldest tx in the page)
+ // and set it as the new last tx
+ lt, hash = tx.PrevTransLt, tx.PrevTransHash
+ }
+
+ if tx == nil {
+ return nil, scrolled, fmt.Errorf("no transactions found [lt %d, hash %s]", lt, ton.Bits256(hash).Hex())
+ }
+
+ return tx, scrolled, nil
+}
+
+func TransactionHashToString(lt uint64, hash ton.Bits256) string {
+ return fmt.Sprintf("%d:%s", lt, hash.Hex())
+}
+
+func TransactionHashFromString(hash string) (uint64, ton.Bits256, error) {
+ parts := strings.Split(hash, ":")
+ if len(parts) != 2 {
+ return 0, ton.Bits256{}, fmt.Errorf("invalid hash string format")
+ }
+
+ lt, err := strconv.ParseUint(parts[0], 10, 64)
+ if err != nil {
+ return 0, ton.Bits256{}, fmt.Errorf("invalid logical time: %w", err)
+ }
+
+ var hashBits ton.Bits256
+
+ if err = hashBits.FromHex(parts[1]); err != nil {
+ return 0, ton.Bits256{}, fmt.Errorf("invalid hash: %w", err)
+ }
+
+ return lt, hashBits, nil
+}
diff --git a/zetaclient/chains/ton/liteapi/client_live_test.go b/zetaclient/chains/ton/liteapi/client_live_test.go
new file mode 100644
index 0000000000..2fb2e00385
--- /dev/null
+++ b/zetaclient/chains/ton/liteapi/client_live_test.go
@@ -0,0 +1,82 @@
+package liteapi
+
+import (
+ "context"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/tonkeeper/tongo/config"
+ "github.com/tonkeeper/tongo/liteapi"
+ "github.com/tonkeeper/tongo/ton"
+ "github.com/zeta-chain/node/zetaclient/common"
+)
+
+func TestClient(t *testing.T) {
+ if !common.LiveTestEnabled() {
+ t.Skip("Live tests are disabled")
+ }
+
+ var (
+ ctx = context.Background()
+ client = &Client{Client: mustCreateClient(t)}
+ )
+
+ t.Run("GetFirstTransaction", func(t *testing.T) {
+ // ARRANGE
+ // Given sample account id (a dev wallet)
+ // https://tonviewer.com/UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr?section=transactions
+ accountID, err := ton.ParseAccountID("UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr")
+ require.NoError(t, err)
+
+ // Given expected hash for the first tx
+ const expect = "b73df4853ca02a040df46f56635d6b8f49b554d5f556881ab389111bbfce4498"
+
+ // as of 2024-09-18
+ const expectedTransactions = 23
+
+ start := time.Now()
+
+ // ACT
+ tx, scrolled, err := client.GetFirstTransaction(ctx, accountID)
+
+ finish := time.Since(start)
+
+ // ASSERT
+ require.NoError(t, err)
+
+ assert.GreaterOrEqual(t, scrolled, expectedTransactions)
+ assert.Equal(t, expect, tx.Hash().Hex())
+
+ t.Logf("Time taken %s; transactions scanned: %d", finish.String(), scrolled)
+ })
+}
+
+func mustCreateClient(t *testing.T) *liteapi.Client {
+ client, err := liteapi.NewClient(
+ liteapi.WithConfigurationFile(mustFetchConfig(t)),
+ liteapi.WithDetectArchiveNodes(),
+ )
+
+ require.NoError(t, err)
+
+ return client
+}
+
+func mustFetchConfig(t *testing.T) config.GlobalConfigurationFile {
+ // archival light client for mainnet
+ const url = "https://api.tontech.io/ton/archive-mainnet.autoconf.json"
+
+ res, err := http.Get(url)
+ require.NoError(t, err)
+ require.Equal(t, http.StatusOK, res.StatusCode)
+
+ defer res.Body.Close()
+
+ conf, err := config.ParseConfig(res.Body)
+ require.NoError(t, err)
+
+ return *conf
+}
diff --git a/zetaclient/chains/ton/liteapi/client_test.go b/zetaclient/chains/ton/liteapi/client_test.go
new file mode 100644
index 0000000000..3ab56422a5
--- /dev/null
+++ b/zetaclient/chains/ton/liteapi/client_test.go
@@ -0,0 +1,18 @@
+package liteapi
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestHashes(t *testing.T) {
+ const sample = `48644940000001:e02b8c7cec103e08175ade8106619a8908707623c31451df2a68497c7d23d15a`
+
+ lt, hash, err := TransactionHashFromString(sample)
+ require.NoError(t, err)
+
+ require.Equal(t, uint64(48644940000001), lt)
+ require.Equal(t, "e02b8c7cec103e08175ade8106619a8908707623c31451df2a68497c7d23d15a", hash.Hex())
+ require.Equal(t, sample, TransactionHashToString(lt, hash))
+}
From 7cd1c0671a31377ae9e268fb7b3d5ef6fde7ceaf Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Wed, 18 Sep 2024 18:37:11 +0300
Subject: [PATCH 06/36] Add TON tx scraper
---
pkg/contracts/ton/gateway_test.go | 54 +++++++++++
pkg/contracts/ton/testdata/01.json | 8 ++
pkg/contracts/ton/testdata/readme.md | 29 ++++++
pkg/contracts/ton/testdata/scraper.go | 131 ++++++++++++++++++++++++++
4 files changed, 222 insertions(+)
create mode 100644 pkg/contracts/ton/gateway_test.go
create mode 100644 pkg/contracts/ton/testdata/01.json
create mode 100644 pkg/contracts/ton/testdata/readme.md
create mode 100644 pkg/contracts/ton/testdata/scraper.go
diff --git a/pkg/contracts/ton/gateway_test.go b/pkg/contracts/ton/gateway_test.go
new file mode 100644
index 0000000000..5e05bd4ec7
--- /dev/null
+++ b/pkg/contracts/ton/gateway_test.go
@@ -0,0 +1,54 @@
+package ton
+
+import (
+ "embed"
+ "encoding/json"
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "github.com/tonkeeper/tongo/boc"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/ton"
+)
+
+func TestFixtures(t *testing.T) {
+ // ACT
+ tx := getFixtureTX(t, "01")
+
+ // ASSERT
+ require.Equal(t, uint64(26023788000003), tx.Lt)
+ require.Equal(t, "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf", tx.Hash().Hex())
+}
+
+//go:embed testdata
+var fixtures embed.FS
+
+// testdata/$name.json tx
+func getFixtureTX(t *testing.T, name string) ton.Transaction {
+ t.Helper()
+
+ filename := fmt.Sprintf("testdata/%s.json", name)
+
+ b, err := fixtures.ReadFile(filename)
+ require.NoError(t, err, filename)
+
+ // bag of cells
+ var raw struct {
+ BOC string `json:"boc"`
+ }
+
+ require.NoError(t, json.Unmarshal(b, &raw))
+
+ cells, err := boc.DeserializeBocHex(raw.BOC)
+ require.NoError(t, err)
+ require.Len(t, cells, 1)
+
+ cell := cells[0]
+
+ var tx ton.Transaction
+
+ require.NoError(t, tx.UnmarshalTLB(cell, &tlb.Decoder{}))
+
+ return tx
+}
diff --git a/pkg/contracts/ton/testdata/01.json b/pkg/contracts/ton/testdata/01.json
new file mode 100644
index 0000000000..5c05bcc425
--- /dev/null
+++ b/pkg/contracts/ton/testdata/01.json
@@ -0,0 +1,8 @@
+{
+ "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b",
+ "boc": "b5ee9c7201020a0100023d0003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017ab22a3b3031b48df020aa3647a59163a25772d81991916a2bf523b771d89deec9e5be15d58000017ab20f8740366ead1e30003465b1d0080102030201e004050082723e346a6461f48fab691e87d9fd5954595eb15351fa1c536486a863d9708b79c313c7e41677dcade29f2b424cdad8712132fbd5465b37df2e1763369e2fb12da0021904222490ee6b28018646dca110080900f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d0ee6b28000608235a00002f5645476604cdd5a3c60000003280000000000000006c6d35f934b257cebf76cf01f29a0ae9bd54b022c00101df06015de004cbec44e40ad75610e23fc357071c1c1f7e1e1a31833ac17b131d6a462d0a42d800002f5645476608cdd5a3c6c007008b0000006500000000000000008012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1e876046701b1b4d7e4d2c95f3afddb3c07ca682ba6f552c08b009e42d5ac3d090000000000000000007e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98510184c2880c0000000000002000000000003da7f47a5d1898330bd18801617ae23a388cbaf527c312921718ef36ce9cf8c4e40901d04",
+ "description": "A deposit to gw contract on testnet",
+ "hash": "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf",
+ "logicalTime": 26023788000003,
+ "test": false
+}
diff --git a/pkg/contracts/ton/testdata/readme.md b/pkg/contracts/ton/testdata/readme.md
new file mode 100644
index 0000000000..30a59a1639
--- /dev/null
+++ b/pkg/contracts/ton/testdata/readme.md
@@ -0,0 +1,29 @@
+# TON transaction scraper
+
+`scraper.go` represents a handy tool that allows to fetch transactions from TON blockchain
+for further usage in test cases.
+
+`go run pkg/contracts/ton/testdata/scraper.go
[--testnet]`
+
+## Example usage
+
+```sh
+go run pkg/contracts/ton/testdata/scraper.go \
+ kQCZfYicgVrqwhxH-Grg44OD78PDRjBnWC9iY61IxaFIW77M \
+ 26023788000003 \
+ cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf | jq
+```
+
+Returns
+
+```json
+{
+ "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b",
+ "boc": "b5ee9c7201020a0100023d0003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017ab22a3b3031b48df020aa3647a59163a25772d81991916a2bf523b771d89deec9e5be15d58000017ab20f8740366ead1e30003465b1d0080102030201e004050082723e346a6461f48fab691e87d9fd5954595eb15351fa1c536486a863d9708b79c313c7e41677dcade29f2b424cdad8712132fbd5465b37df2e1763369e2fb12da0021904222490ee6b28018646dca110080900f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d0ee6b28000608235a00002f5645476604cdd5a3c60000003280000000000000006c6d35f934b257cebf76cf01f29a0ae9bd54b022c00101df06015de004cbec44e40ad75610e23fc357071c1c1f7e1e1a31833ac17b131d6a462d0a42d800002f5645476608cdd5a3c6c007008b0000006500000000000000008012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1e876046701b1b4d7e4d2c95f3afddb3c07ca682ba6f552c08b009e42d5ac3d090000000000000000007e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98510184c2880c0000000000002000000000003da7f47a5d1898330bd18801617ae23a388cbaf527c312921718ef36ce9cf8c4e40901d04",
+ "description": "todo",
+ "hash": "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf",
+ "logicalTime": 26023788000003,
+ "test": false
+}
+```
+
diff --git a/pkg/contracts/ton/testdata/scraper.go b/pkg/contracts/ton/testdata/scraper.go
new file mode 100644
index 0000000000..c9c2705375
--- /dev/null
+++ b/pkg/contracts/ton/testdata/scraper.go
@@ -0,0 +1,131 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "log"
+ "strconv"
+
+ "github.com/tonkeeper/tongo/boc"
+ "github.com/tonkeeper/tongo/liteapi"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/ton"
+)
+
+func main() {
+ var testnet bool
+
+ flag.BoolVar(&testnet, "testnet", false, "Use testnet network")
+ flag.Parse()
+
+ if len(flag.Args()) != 3 {
+ log.Fatalf("Usage: go run scrape.go [--testnet]")
+ }
+
+ // Parse account
+ acc, err := ton.ParseAccountID(flag.Arg(0))
+ must(err, "Unable to parse account")
+
+ // Parse LT
+ lt, err := strconv.ParseUint(flag.Arg(1), 10, 64)
+ must(err, "Unable to parse logical time")
+
+ // Parse hash
+ var hash ton.Bits256
+
+ must(hash.FromHex(flag.Arg(2)), "Unable to parse hash")
+
+ ctx, client := context.Background(), getClient(testnet)
+
+ state, err := client.GetAccountState(ctx, acc)
+ must(err, "Unable to get account state")
+
+ if state.Account.Status() != tlb.AccountActive {
+ fail("account %s is not active", acc.ToRaw())
+ }
+
+ txs, err := client.GetTransactions(ctx, 1, acc, lt, hash)
+ must(err, "Unable to get transactions")
+
+ switch {
+ case len(txs) == 0:
+ fail("Not found")
+ case len(txs) > 1:
+ fail("invalid tx list length (got %d, want 1); lt %d, hash %s", len(txs), hash.Hex())
+ }
+
+ // Print the transaction
+ tx := txs[0]
+
+ cell, err := transactionToCell(tx)
+ must(err, "unable to convert tx to cell")
+
+ bocRaw, err := cell.MarshalJSON()
+ must(err, "unable to marshal cell to JSON")
+
+ printAny(map[string]any{
+ "test": testnet,
+ "account": acc.ToRaw(),
+ "description": "todo",
+ "logicalTime": lt,
+ "hash": hash.Hex(),
+ "boc": json.RawMessage(bocRaw),
+ })
+}
+
+func getClient(testnet bool) *liteapi.Client {
+ if testnet {
+ c, err := liteapi.NewClientWithDefaultTestnet()
+ must(err, "unable to create testnet lite client")
+
+ return c
+ }
+
+ c, err := liteapi.NewClientWithDefaultTestnet()
+ must(err, "unable to create mainnet lite client")
+
+ return c
+}
+
+func printAny(v any) {
+ b, err := json.MarshalIndent(v, "", " ")
+ must(err, "unable marshal data")
+
+ fmt.Println(string(b))
+}
+
+func transactionToCell(tx ton.Transaction) (*boc.Cell, error) {
+ b, err := tx.SourceBoc()
+ if err != nil {
+ return nil, err
+ }
+
+ cells, err := boc.DeserializeBoc(b)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(cells) != 1 {
+ return nil, fmt.Errorf("invalid cell count: %d", len(cells))
+ }
+
+ return cells[0], nil
+}
+
+func must(err error, msg string) {
+ if err == nil {
+ return
+ }
+
+ if msg == "" {
+ log.Fatalf("Error: %s", err.Error())
+ }
+
+ log.Fatalf("%s; error: %s", msg, err.Error())
+}
+
+func fail(msg string, args ...any) {
+ must(fmt.Errorf(msg, args...), "FAIL")
+}
From e0d806fa0216e6651f37a3cac766c15279175fdd Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 20 Sep 2024 15:33:37 +0300
Subject: [PATCH 07/36] Add client.GetTransactionsUntil
---
zetaclient/chains/ton/liteapi/client.go | 90 ++++++++--
.../chains/ton/liteapi/client_live_test.go | 71 ++++++++
zetaclient/chains/ton/observer/inbound.go | 162 ++++++++++++++++++
zetaclient/chains/ton/observer/observer.go | 23 ++-
4 files changed, 328 insertions(+), 18 deletions(-)
create mode 100644 zetaclient/chains/ton/observer/inbound.go
diff --git a/zetaclient/chains/ton/liteapi/client.go b/zetaclient/chains/ton/liteapi/client.go
index 0af19c5d72..e895ff4298 100644
--- a/zetaclient/chains/ton/liteapi/client.go
+++ b/zetaclient/chains/ton/liteapi/client.go
@@ -17,25 +17,21 @@ type Client struct {
*liteapi.Client
}
+const pageSize = 200
+
// GetFirstTransaction scrolls through the transactions of the given account to find the first one.
-// Note that it will fail in case of old transactions. Ideally, use archival node.
-// Also returns the number of scrolled transactions for this account i.e. total transactions
+// Note that it might fail w/o using an archival node. Also returns the number of
+// scrolled transactions for this account i.e. total transactions
func (c *Client) GetFirstTransaction(ctx context.Context, acc ton.AccountID) (*ton.Transaction, int, error) {
- const pageSize = 100
-
- state, err := c.GetAccountState(ctx, acc)
+ lt, hash, err := c.getLastTranHash(ctx, acc)
if err != nil {
- return nil, 0, errors.Wrap(err, "unable to get account state")
- }
-
- if state.Account.Status() != tlb.AccountActive {
- return nil, 0, errors.New("account is not active")
+ return nil, 0, err
}
- var tx *ton.Transaction
-
- // logical time and hash of the last transaction
- lt, hash, scrolled := state.LastTransLt, state.LastTransHash, 0
+ var (
+ tx *ton.Transaction
+ scrolled int
+ )
for {
hashBits := ton.Bits256(hash)
@@ -65,6 +61,72 @@ func (c *Client) GetFirstTransaction(ctx context.Context, acc ton.AccountID) (*t
return tx, scrolled, nil
}
+// GetTransactionsUntil returns all transactions in a range of (from,to] where from is "lt && hash".
+// - oldestLT && oldestHash tx is EXCLUDED from the result.
+// - ordered by DESC
+func (c *Client) GetTransactionsUntil(
+ ctx context.Context,
+ acc ton.AccountID,
+ oldestLT uint64,
+ oldestHash ton.Bits256,
+) ([]ton.Transaction, error) {
+ lt, hash, err := c.getLastTranHash(ctx, acc)
+ if err != nil {
+ return nil, err
+ }
+
+ var result []ton.Transaction
+
+ for {
+ hashBits := ton.Bits256(hash)
+
+ txs, err := c.GetTransactions(ctx, pageSize, acc, lt, hashBits)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to get transactions [lt %d, hash %s]", lt, hashBits.Hex())
+ }
+
+ if len(txs) == 0 {
+ break
+ }
+
+ for i := range txs {
+ found := txs[i].Lt == oldestLT && txs[i].Hash() == tlb.Bits256(oldestHash)
+ if !found {
+ continue
+ }
+
+ // early exit
+ result = append(result, txs[:i]...)
+
+ return result, nil
+ }
+
+ // otherwise, append all page results
+ result = append(result, txs...)
+
+ // prepare pagination params for the next page
+ oldestIndex := len(txs) - 1
+
+ lt, hash = txs[oldestIndex].PrevTransLt, txs[oldestIndex].PrevTransHash
+ }
+
+ return result, nil
+}
+
+// getLastTranHash returns logical time and hash of the last transaction
+func (c *Client) getLastTranHash(ctx context.Context, acc ton.AccountID) (uint64, tlb.Bits256, error) {
+ state, err := c.GetAccountState(ctx, acc)
+ if err != nil {
+ return 0, tlb.Bits256{}, errors.Wrap(err, "unable to get account state")
+ }
+
+ if state.Account.Status() != tlb.AccountActive {
+ return 0, tlb.Bits256{}, errors.New("account is not active")
+ }
+
+ return state.LastTransLt, state.LastTransHash, nil
+}
+
func TransactionHashToString(lt uint64, hash ton.Bits256) string {
return fmt.Sprintf("%d:%s", lt, hash.Hex())
}
diff --git a/zetaclient/chains/ton/liteapi/client_live_test.go b/zetaclient/chains/ton/liteapi/client_live_test.go
index 2fb2e00385..435da2787a 100644
--- a/zetaclient/chains/ton/liteapi/client_live_test.go
+++ b/zetaclient/chains/ton/liteapi/client_live_test.go
@@ -2,6 +2,8 @@ package liteapi
import (
"context"
+ "encoding/json"
+ "fmt"
"net/http"
"testing"
"time"
@@ -10,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/tonkeeper/tongo/config"
"github.com/tonkeeper/tongo/liteapi"
+ "github.com/tonkeeper/tongo/tlb"
"github.com/tonkeeper/tongo/ton"
"github.com/zeta-chain/node/zetaclient/common"
)
@@ -52,6 +55,42 @@ func TestClient(t *testing.T) {
t.Logf("Time taken %s; transactions scanned: %d", finish.String(), scrolled)
})
+
+ t.Run("GetTransactionsUntil", func(t *testing.T) {
+ // ARRANGE
+ // Given sample account id (a dev wallet)
+ // https://tonviewer.com/UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr?section=transactions
+ accountID, err := ton.ParseAccountID("UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr")
+ require.NoError(t, err)
+
+ const getUntilLT = uint64(48645164000001)
+ const getUntilHash = `2e107215e634bbc3492bdf4b1466d59432623295072f59ab526d15737caa9531`
+
+ // as of 2024-09-20
+ const expectedTX = 3
+
+ var hash ton.Bits256
+ require.NoError(t, hash.FromHex(getUntilHash))
+
+ start := time.Now()
+
+ // ACT
+ // https://tonviewer.com/UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr?section=transactions
+ txs, err := client.GetTransactionsUntil(ctx, accountID, getUntilLT, hash)
+
+ finish := time.Since(start)
+
+ // ASSERT
+ require.NoError(t, err)
+
+ t.Logf("Time taken %s; transactions fetched: %d", finish.String(), len(txs))
+ for _, tx := range txs {
+ printTx(t, tx)
+ }
+
+ mustContainTX(t, txs, "a6672a0e80193c1f705ef1cf45a5883441b8252523b1d08f7656c80e400c74a8")
+ assert.GreaterOrEqual(t, len(txs), expectedTX)
+ })
}
func mustCreateClient(t *testing.T) *liteapi.Client {
@@ -80,3 +119,35 @@ func mustFetchConfig(t *testing.T) config.GlobalConfigurationFile {
return *conf
}
+
+func mustContainTX(t *testing.T, txs []ton.Transaction, hash string) {
+ var h ton.Bits256
+ require.NoError(t, h.FromHex(hash))
+
+ for _, tx := range txs {
+ if tx.Hash() == tlb.Bits256(h) {
+ return
+ }
+ }
+
+ t.Fatalf("transaction %q not found", hash)
+}
+
+func printTx(t *testing.T, tx ton.Transaction) {
+ b, err := json.MarshalIndent(simplifyTx(tx), "", " ")
+ require.NoError(t, err)
+
+ t.Logf("TX %s", string(b))
+}
+
+func simplifyTx(tx ton.Transaction) map[string]any {
+ return map[string]any{
+ "block": fmt.Sprintf("shard: %d, seqno: %d", tx.BlockID.Shard, tx.BlockID.Seqno),
+ "hash": tx.Hash().Hex(),
+ "logicalTime": tx.Lt,
+ "unixTime": time.Unix(int64(tx.Transaction.Now), 0).UTC().String(),
+ "outMessagesCount": tx.OutMsgCnt,
+ // "inMessageInfo": tx.Msgs.InMsg.Value.Value.Info.IntMsgInfo,
+ // "outMessages": tx.Msgs.OutMsgs,
+ }
+}
diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go
new file mode 100644
index 0000000000..e8d403d5e8
--- /dev/null
+++ b/zetaclient/chains/ton/observer/inbound.go
@@ -0,0 +1,162 @@
+package observer
+
+import (
+ "context"
+ "fmt"
+ "slices"
+
+ "cosmossdk.io/errors"
+ "github.com/rs/zerolog"
+ "github.com/tonkeeper/tongo/ton"
+
+ "github.com/zeta-chain/node/pkg/ticker"
+ "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi"
+ zctx "github.com/zeta-chain/node/zetaclient/context"
+)
+
+const (
+ // MaxTransactionsPerTick is the maximum number of transactions to process on a ticker
+ MaxTransactionsPerTick = 100
+)
+
+func (ob *Observer) watchInbound(ctx context.Context) error {
+ app, err := zctx.FromContext(ctx)
+ if err != nil {
+ return err
+ }
+
+ var (
+ chainID = ob.Chain().ChainId
+ initialInterval = ticker.SecondsFromUint64(ob.ChainParams().InboundTicker)
+ sampledLogger = ob.Logger().Inbound.Sample(&zerolog.BasicSampler{N: 10})
+ )
+
+ ob.Logger().Inbound.Info().Msgf("WatchInbound started for chain %d", chainID)
+
+ task := func(ctx context.Context, t *ticker.Ticker) error {
+ if !app.IsInboundObservationEnabled() {
+ sampledLogger.Info().Msgf("WatchInbound: inbound observation is disabled for chain %d", chainID)
+ return nil
+ }
+
+ if err := ob.observeInbound(ctx, app); err != nil {
+ ob.Logger().Inbound.Err(err).Msg("WatchInbound: observeInbound error")
+ }
+
+ newInterval := ticker.SecondsFromUint64(ob.ChainParams().InboundTicker)
+ t.SetInterval(newInterval)
+
+ return nil
+ }
+
+ return ticker.Run(
+ ctx,
+ initialInterval,
+ task,
+ ticker.WithStopChan(ob.StopChannel()),
+ ticker.WithLogger(ob.Logger().Inbound, "WatchInbound"),
+ )
+}
+
+// Flow:
+// - [x] Ensure last scanned transaction is set
+// - [x] Get all transaction between [lastScannedTx; now]
+// - [ ] Filter only valid and inbound transactions
+// - [ ] For each transaction (ordered by *ASC*)
+// - [ ] Construct crosschain cosmos message
+// - [ ] Vote
+// - [ ] Save last scanned tx
+func (ob *Observer) observeInbound(ctx context.Context, _ *zctx.AppContext) error {
+ if err := ob.ensureLastScannedTX(ctx); err != nil {
+ return errors.Wrap(err, "unable to ensure last scanned tx")
+ }
+
+ lt, hashBits, err := liteapi.TransactionHashFromString(ob.LastTxScanned())
+ if err != nil {
+ return errors.Wrapf(err, "unable to parse last scanned tx %q", ob.LastTxScanned())
+ }
+
+ txs, err := ob.client.GetTransactionsUntil(ctx, ob.gatewayID, lt, hashBits)
+ if err != nil {
+ return errors.Wrap(err, "unable to get transactions")
+ }
+
+ // Process from oldest to latest (ASC)
+ slices.Reverse(txs)
+
+ switch {
+ case len(txs) == 0:
+ // noop
+ return nil
+ case len(txs) > MaxTransactionsPerTick:
+ ob.Logger().Inbound.Info().
+ Msgf("ObserveInbound: got %d transactions. Taking first %d", len(txs), MaxTransactionsPerTick)
+
+ txs = txs[:MaxTransactionsPerTick]
+ default:
+ ob.Logger().Inbound.Info().Msgf("ObserveInbound: got %d transactions", len(txs))
+ }
+
+ // todo deploy sample GW to testnet
+ // todo send some TON and test
+
+ // todo FilterInboundEvent
+
+ for _, tx := range txs {
+ fmt.Println("TON TX", tx)
+ }
+
+ /*
+ // loop signature from oldest to latest to filter inbound events
+ for i := len(signatures) - 1; i >= 0; i-- {
+ sig := signatures[i]
+ sigString := sig.Signature.String()
+
+ // process successfully signature only
+ if sig.Err == nil {
+ txResult, err := ob.solClient.GetTransaction(ctx, sig.Signature, &rpc.GetTransactionOpts{})
+ if err != nil {
+ // we have to re-scan this signature on next ticker
+ return errors.Wrapf(err, "error GetTransaction for chain %d sig %s", chainID, sigString)
+ }
+
+ // filter inbound events and vote
+ err = ob.FilterInboundEventsAndVote(ctx, txResult)
+ if err != nil {
+ // we have to re-scan this signature on next ticker
+ return errors.Wrapf(err, "error FilterInboundEventAndVote for chain %d sig %s", chainID, sigString)
+ }
+ }
+
+ // signature scanned; save last scanned signature to both memory and db, ignore db error
+ if err := ob.SaveLastTxScanned(sigString, sig.Slot); err != nil {
+ ob.Logger().
+ Inbound.Error().
+ Err(err).
+ Msgf("ObserveInbound: error saving last sig %s for chain %d", sigString, chainID)
+ }
+ ob.Logger().
+ Inbound.Info().
+ Msgf("ObserveInbound: last scanned sig is %s for chain %d in slot %d", sigString, chainID, sig.Slot)
+ */
+
+ return nil
+}
+
+func (ob *Observer) ensureLastScannedTX(ctx context.Context) error {
+ // noop
+ if ob.LastTxScanned() != "" {
+ return nil
+ }
+
+ tx, _, err := ob.client.GetFirstTransaction(ctx, ob.gatewayID)
+ if err != nil {
+ return err
+ }
+
+ txHash := liteapi.TransactionHashToString(tx.Lt, ton.Bits256(tx.Hash()))
+
+ ob.WithLastTxScanned(txHash)
+
+ return ob.WriteLastTxScannedToDB(txHash)
+}
diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go
index d21312c31c..03f019d187 100644
--- a/zetaclient/chains/ton/observer/observer.go
+++ b/zetaclient/chains/ton/observer/observer.go
@@ -3,12 +3,13 @@ package observer
import (
"context"
- "github.com/tonkeeper/tongo/liteapi"
"github.com/tonkeeper/tongo/ton"
+ "github.com/zeta-chain/node/pkg/bg"
"github.com/zeta-chain/node/x/crosschain/types"
"github.com/zeta-chain/node/zetaclient/chains/base"
"github.com/zeta-chain/node/zetaclient/chains/interfaces"
+ "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi"
)
type Observer struct {
@@ -31,11 +32,25 @@ func New(bo *base.Observer, client *liteapi.Client, gatewayID ton.AccountID) (*O
}
func (ob *Observer) Start(ctx context.Context) {
- // todo
-}
+ if ok := ob.Observer.Start(); !ok {
+ ob.Logger().Chain.Info().Msgf("observer is already started for chain %d", ob.Chain().ChainId)
+ return
+ }
+
+ ob.Logger().Chain.Info().Msgf("observer is starting for chain %d", ob.Chain().ChainId)
+
+ // Note that each `watch*` method has a ticker that will stop as soon as
+ // baseObserver.Stop() was called (ticker.WithStopChan)
+
+ // watch for incoming txs and post votes to zetacore
+ bg.Work(ctx, ob.watchInbound, bg.WithName("WatchInbound"), bg.WithLogger(ob.Logger().Inbound))
-func (ob *Observer) Stop() {
// todo
+ // watchOutbound
+ // watchGasPrice
+ // watchInboundTracker
+ // watchOutbound
+ // watchRPCStatus
}
func (ob *Observer) VoteOutboundIfConfirmed(ctx context.Context, cctx *types.CrossChainTx) (bool, error) {
From 98a88133a6f73fcb5dfe795e6b7a05858b5e75ed Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Mon, 23 Sep 2024 18:18:26 +0300
Subject: [PATCH 08/36] Gateway WIP
---
pkg/contracts/ton/gateway.go | 298 ++++++++++++++++++
pkg/contracts/ton/gateway_test.go | 102 +++++-
pkg/contracts/ton/gateway_tx.go | 51 +++
pkg/contracts/ton/testdata/00-donation.json | 8 +
.../ton/testdata/{01.json => 01-deposit.json} | 4 +-
pkg/contracts/ton/testdata/readme.md | 10 +-
pkg/contracts/ton/testdata/scraper.go | 4 +-
.../chains/ton/observer/observer_test.go | 7 -
8 files changed, 460 insertions(+), 24 deletions(-)
create mode 100644 pkg/contracts/ton/gateway.go
create mode 100644 pkg/contracts/ton/gateway_tx.go
create mode 100644 pkg/contracts/ton/testdata/00-donation.json
rename pkg/contracts/ton/testdata/{01.json => 01-deposit.json} (88%)
delete mode 100644 zetaclient/chains/ton/observer/observer_test.go
diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go
new file mode 100644
index 0000000000..d41a37168f
--- /dev/null
+++ b/pkg/contracts/ton/gateway.go
@@ -0,0 +1,298 @@
+package ton
+
+import (
+ "cosmossdk.io/math"
+ eth "github.com/ethereum/go-ethereum/common"
+ "github.com/pkg/errors"
+ "github.com/tonkeeper/tongo/boc"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/ton"
+)
+
+type Op uint32
+
+// github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc
+// Inbound operations
+const (
+ OpDonate Op = 100 + iota
+ OpDeposit
+ OpDepositAndCall
+)
+
+// Outbound operations
+const (
+ OpWithdraw Op = 200 + iota
+ SetDepositsEnabled
+ UpdateTSS
+ UpdateCode
+)
+
+type Gateway struct {
+ accountID ton.AccountID
+}
+
+type Donation struct {
+ Sender ton.AccountID
+ Amount math.Uint
+}
+
+type Deposit struct {
+ Sender ton.AccountID
+ Amount math.Uint
+ Recipient eth.Address
+}
+
+type DepositAndCall struct {
+ Deposit
+ CallData []byte
+}
+
+const (
+ sizeOpCode = 32
+ sizeQueryID = 64
+)
+
+var (
+ ErrParse = errors.New("unable to parse tx")
+ ErrUnknownOp = errors.New("unknown op")
+ ErrCast = errors.New("unable to cast tx content")
+)
+
+func NewGateway(accountID ton.AccountID) *Gateway {
+ return &Gateway{accountID}
+}
+
+func (gw *Gateway) ParseTransaction(tx ton.Transaction) (*Transaction, error) {
+ if !tx.IsSuccess() {
+ return nil, errors.Wrapf(ErrParse, "tx %s is not successful", tx.Hash().Hex())
+ }
+
+ if tx.Msgs.InMsg.Exists {
+ inbound, err := gw.parseInbound(tx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to parse inbound tx %s", tx.Hash().Hex())
+ }
+
+ return inbound, nil
+ }
+
+ outbound, err := gw.parseOutbound(tx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to parse outbound tx %s", tx.Hash().Hex())
+ }
+
+ return outbound, nil
+}
+
+func (gw *Gateway) parseInbound(tx ton.Transaction) (*Transaction, error) {
+ body, err := parseInternalMessageBody(tx)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to parse body")
+ }
+
+ intMsgInfo := tx.Msgs.InMsg.Value.Value.Info.IntMsgInfo
+ if intMsgInfo == nil {
+ return nil, errors.Wrap(ErrParse, "no internal message info")
+ }
+
+ sourceID, err := ton.AccountIDFromTlb(intMsgInfo.Src)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to parse source account")
+ }
+
+ destinationID, err := ton.AccountIDFromTlb(intMsgInfo.Dest)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to parse destination account")
+ }
+
+ if gw.accountID != *destinationID {
+ return nil, errors.Wrap(ErrParse, "destination account is not gateway")
+ }
+
+ op, err := body.ReadUint(sizeOpCode)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to read op code")
+ }
+
+ var (
+ sender = *sourceID
+ opCode = Op(op)
+
+ content any
+ errContent error
+ )
+
+ switch opCode {
+ case OpDonate:
+ amount := intMsgInfo.Value.Grams - tx.TotalFees.Grams
+ content = Donation{Sender: sender, Amount: gramToUint(amount)}
+ case OpDeposit:
+ content, errContent = parseDeposit(tx, sender, body)
+ case OpDepositAndCall:
+ content, errContent = parseDepositAndCall(tx, sender, body)
+ default:
+ return nil, errors.Wrapf(ErrUnknownOp, "op code %d", int64(op))
+ }
+
+ if errContent != nil {
+ return nil, errors.Wrapf(ErrParse, "unable to parse content for op code %d: %s", int64(op), errContent.Error())
+ }
+
+ return &Transaction{
+ Transaction: tx,
+ Operation: opCode,
+
+ content: content,
+ inbound: true,
+ }, nil
+}
+
+func parseDeposit(tx ton.Transaction, sender ton.AccountID, body *boc.Cell) (Deposit, error) {
+ // skip query id
+ if err := body.Skip(sizeQueryID); err != nil {
+ return Deposit{}, err
+ }
+
+ recipient, err := readEVMAddress(body)
+ if err != nil {
+ return Deposit{}, errors.Wrap(err, "unable to read recipient")
+ }
+
+ dl, err := parseDepositLog(tx)
+ if err != nil {
+ return Deposit{}, errors.Wrap(err, "unable to parse deposit log")
+ }
+
+ return Deposit{
+ Sender: sender,
+ Amount: dl.Amount,
+ Recipient: recipient,
+ }, nil
+}
+
+type depositLog struct {
+ Amount math.Uint
+}
+
+func parseDepositLog(tx ton.Transaction) (depositLog, error) {
+ messages := tx.Msgs.OutMsgs.Values()
+ if len(messages) == 0 {
+ return depositLog{}, errors.Wrap(ErrParse, "no out messages")
+ }
+
+ // stored as ref
+ // cell log = begin_cell()
+ // .store_uint(op::internal::deposit, size::op_code_size)
+ // .store_uint(0, size::query_id_size)
+ // .store_slice(sender)
+ // .store_coins(deposit_amount)
+ // .store_uint(evm_recipient, size::evm_address)
+ // .end_cell();
+
+ body, err := marshalCellRef(messages[0].Value.Body)
+ if err != nil {
+ return depositLog{}, errors.Wrap(err, "unable to read body cell")
+ }
+
+ if err := body.Skip(sizeOpCode + sizeQueryID); err != nil {
+ return depositLog{}, errors.Wrap(err, "unable to skip bits")
+ }
+
+ // skip msg address (ton sender)
+ if err := unmarshalTLB(&tlb.MsgAddress{}, body); err != nil {
+ return depositLog{}, errors.Wrap(err, "unable to read sender address")
+ }
+
+ var deposited tlb.Grams
+ if err := unmarshalTLB(&deposited, body); err != nil {
+ return depositLog{}, errors.Wrap(err, "unable to read deposited amount")
+ }
+
+ return depositLog{Amount: gramToUint(deposited)}, nil
+}
+
+func parseDepositAndCall(tx ton.Transaction, sender ton.AccountID, body *boc.Cell) (DepositAndCall, error) {
+ deposit, err := parseDeposit(tx, sender, body)
+ if err != nil {
+ return DepositAndCall{}, err
+ }
+
+ callDataCell, err := body.NextRef()
+ if err != nil {
+ return DepositAndCall{}, errors.Wrap(err, "unable to read call data cell")
+ }
+
+ var sd tlb.SnakeData
+ if err = sd.UnmarshalTLB(callDataCell, &tlb.Decoder{}); err != nil {
+ return DepositAndCall{}, errors.Wrap(err, "unable to unmarshal call data")
+ }
+
+ cd := boc.BitString(sd)
+
+ return DepositAndCall{Deposit: deposit, CallData: cd.Buffer()}, nil
+}
+
+func (gw *Gateway) parseOutbound(_ ton.Transaction) (*Transaction, error) {
+ return nil, errors.New("not implemented")
+}
+
+func parseInternalMessageBody(tx ton.Transaction) (*boc.Cell, error) {
+ if !tx.Msgs.InMsg.Exists {
+ return nil, errors.Wrap(ErrParse, "tx should have an internal message")
+ }
+
+ either := tx.Msgs.InMsg.Value.Value.Body
+ if either.IsRight {
+ return nil, errors.Wrap(ErrParse, "tx body should not be a Ref")
+ }
+
+ body, err := marshalCell(&either.Value)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to read body cell")
+ }
+
+ return body, nil
+}
+
+func marshalCell(v tlb.MarshalerTLB) (*boc.Cell, error) {
+ cell := boc.NewCell()
+
+ if err := v.MarshalTLB(cell, &tlb.Encoder{}); err != nil {
+ return nil, err
+ }
+
+ return cell, nil
+}
+
+func marshalCellRef(v tlb.MarshalerTLB) (*boc.Cell, error) {
+ c, err := marshalCell(v)
+ if err != nil {
+ return nil, err
+ }
+
+ c, err = c.NextRef()
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to create ref cell")
+ }
+
+ return c, nil
+}
+
+func unmarshalTLB(t tlb.UnmarshalerTLB, cell *boc.Cell) error {
+ return t.UnmarshalTLB(cell, &tlb.Decoder{})
+}
+
+func gramToUint(g tlb.Grams) math.Uint {
+ return math.NewUint(uint64(g))
+}
+
+func readEVMAddress(cell *boc.Cell) (eth.Address, error) {
+ const evmAddrBits = 20 * 8
+
+ s, err := cell.ReadBits(evmAddrBits)
+ if err != nil {
+ return eth.Address{}, err
+ }
+
+ return eth.BytesToAddress(s.Buffer()), nil
+}
diff --git a/pkg/contracts/ton/gateway_test.go b/pkg/contracts/ton/gateway_test.go
index 5e05bd4ec7..6e9ee2b3d1 100644
--- a/pkg/contracts/ton/gateway_test.go
+++ b/pkg/contracts/ton/gateway_test.go
@@ -6,15 +6,92 @@ import (
"fmt"
"testing"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tonkeeper/tongo/boc"
"github.com/tonkeeper/tongo/tlb"
"github.com/tonkeeper/tongo/ton"
)
+func TestParsing(t *testing.T) {
+ t.Run("Donate", func(t *testing.T) {
+ // ARRANGE
+ // Given a tx
+ tx, fx := getFixtureTX(t, "00-donation")
+
+ // Given a gateway contract
+ gw := NewGateway(ton.MustParseAccountID(fx.Account))
+
+ // ACT
+ parsedTX, err := gw.ParseTransaction(tx)
+
+ // ASSERT
+ require.NoError(t, err)
+
+ assert.Equal(t, int(OpDonate), int(parsedTX.Operation))
+ assert.Equal(t, true, parsedTX.IsInbound())
+
+ const (
+ expectedSender = "0:9594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f"
+ expectedDonation = 1_499_432_947 // 1.49... TON
+ )
+
+ donation, err := parsedTX.Donation()
+ assert.NoError(t, err)
+ assert.Equal(t, expectedSender, donation.Sender.ToRaw())
+ assert.Equal(t, expectedDonation, int(donation.Amount.Uint64()))
+ })
+
+ t.Run("Deposit", func(t *testing.T) {
+ // ARRANGE
+ // Given a tx
+ tx, fx := getFixtureTX(t, "01-deposit")
+
+ // Given a gateway contract
+ gw := NewGateway(ton.MustParseAccountID(fx.Account))
+
+ // ACT
+ parsedTX, err := gw.ParseTransaction(tx)
+
+ // ASSERT
+ require.NoError(t, err)
+
+ // Check tx props
+ assert.Equal(t, int(OpDeposit), int(parsedTX.Operation))
+
+ // Check
+ deposit, err := parsedTX.Deposit()
+ assert.NoError(t, err)
+
+ const (
+ expectedSender = "0:9594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f"
+ vitalikDotETH = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
+ expectedDeposit = 990_000_000 // 0.99 TON
+ )
+
+ assert.Equal(t, expectedSender, deposit.Sender.ToRaw())
+ assert.Equal(t, expectedDeposit, int(deposit.Amount.Uint64()))
+ assert.Equal(t, vitalikDotETH, deposit.Recipient.Hex())
+
+ // Check that other casting fails
+ _, err = parsedTX.Donation()
+ assert.ErrorIs(t, err, ErrCast)
+ })
+
+ t.Run("Deposit and call", func(t *testing.T) {
+ // todo
+ })
+
+ t.Run("Irrelevant tx", func(t *testing.T) {
+ // todo
+ })
+
+ // todo
+}
+
func TestFixtures(t *testing.T) {
// ACT
- tx := getFixtureTX(t, "01")
+ tx, _ := getFixtureTX(t, "01-deposit")
// ASSERT
require.Equal(t, uint64(26023788000003), tx.Lt)
@@ -24,8 +101,17 @@ func TestFixtures(t *testing.T) {
//go:embed testdata
var fixtures embed.FS
+type fixture struct {
+ Account string `json:"account"`
+ BOC string `json:"boc"`
+ Description string `json:"description"`
+ Hash string `json:"hash"`
+ LogicalTime uint64 `json:"logicalTime"`
+ Test bool `json:"test"`
+}
+
// testdata/$name.json tx
-func getFixtureTX(t *testing.T, name string) ton.Transaction {
+func getFixtureTX(t *testing.T, name string) (ton.Transaction, fixture) {
t.Helper()
filename := fmt.Sprintf("testdata/%s.json", name)
@@ -34,13 +120,11 @@ func getFixtureTX(t *testing.T, name string) ton.Transaction {
require.NoError(t, err, filename)
// bag of cells
- var raw struct {
- BOC string `json:"boc"`
- }
+ var fx fixture
- require.NoError(t, json.Unmarshal(b, &raw))
+ require.NoError(t, json.Unmarshal(b, &fx))
- cells, err := boc.DeserializeBocHex(raw.BOC)
+ cells, err := boc.DeserializeBocHex(fx.BOC)
require.NoError(t, err)
require.Len(t, cells, 1)
@@ -50,5 +134,7 @@ func getFixtureTX(t *testing.T, name string) ton.Transaction {
require.NoError(t, tx.UnmarshalTLB(cell, &tlb.Decoder{}))
- return tx
+ t.Logf("Loaded fixture %s\n%s", filename, fx.Description)
+
+ return tx, fx
}
diff --git a/pkg/contracts/ton/gateway_tx.go b/pkg/contracts/ton/gateway_tx.go
new file mode 100644
index 0000000000..75c12c8eff
--- /dev/null
+++ b/pkg/contracts/ton/gateway_tx.go
@@ -0,0 +1,51 @@
+package ton
+
+import (
+ "cosmossdk.io/errors"
+ "cosmossdk.io/math"
+ "github.com/tonkeeper/tongo/ton"
+)
+
+// Transaction represents a Gateway transaction.
+type Transaction struct {
+ ton.Transaction
+ Operation Op
+
+ content any
+ inbound bool
+}
+
+// IsInbound returns true if the transaction is inbound.
+func (tx *Transaction) IsInbound() bool {
+ return tx.inbound
+}
+
+// GasUsed returns the amount of gas used by the transaction.
+func (tx *Transaction) GasUsed() math.Uint {
+ return math.NewUint(uint64(tx.TotalFees.Grams))
+}
+
+// Donation casts the transaction content to a Donation.
+func (tx *Transaction) Donation() (Donation, error) {
+ return retrieveContent[Donation](tx)
+}
+
+// Deposit casts the transaction content to a Deposit.
+func (tx *Transaction) Deposit() (Deposit, error) {
+ return retrieveContent[Deposit](tx)
+}
+
+// DepositAndCall casts the transaction content to a DepositAndCall.
+func (tx *Transaction) DepositAndCall() (DepositAndCall, error) {
+ return retrieveContent[DepositAndCall](tx)
+}
+
+func retrieveContent[T any](tx *Transaction) (T, error) {
+ typed, ok := tx.content.(T)
+ if !ok {
+ var tt T
+ return tt, errors.Wrapf(ErrCast, "not a %T (op %d)", tt, int(tx.Operation))
+ }
+
+ return typed, nil
+}
diff --git a/pkg/contracts/ton/testdata/00-donation.json b/pkg/contracts/ton/testdata/00-donation.json
new file mode 100644
index 0000000000..867f096a90
--- /dev/null
+++ b/pkg/contracts/ton/testdata/00-donation.json
@@ -0,0 +1,8 @@
+{
+ "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b",
+ "boc": "b5ee9c72010207010001a10003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017d46c458143cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf000017ab22a3b30366f17efd000146114e1a80102030101a00400827213c7e41677dcade29f2b424cdad8712132fbd5465b37df2e1763369e2fb12da0a7b38b0f8722a81351a279e9d229e4f5d92a5d91aed6c197ab4b5ef756b042cc021b04c0731749165a0bc01860db5611050600c968012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d165a0bc000608235a00002fa8d88b0284cde2fdfa00000032000000000000000040009e408c6c3d090000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005bc00000000000000000000000012d452da449e50b8cf7dd27861f146122afe1b546bb8b70fc8216f0c614139f8e04",
+ "description": "Sample donation to gw contract. https://testnet.tonviewer.com/transaction/d9339c9e78a55ee9ea0cd46cab798926c139db2a7c17a002041c3db90a80d5ea",
+ "hash": "d9339c9e78a55ee9ea0cd46cab798926c139db2a7c17a002041c3db90a80d5ea",
+ "logicalTime": 26201117000003,
+ "test": true
+}
\ No newline at end of file
diff --git a/pkg/contracts/ton/testdata/01.json b/pkg/contracts/ton/testdata/01-deposit.json
similarity index 88%
rename from pkg/contracts/ton/testdata/01.json
rename to pkg/contracts/ton/testdata/01-deposit.json
index 5c05bcc425..d22170ddf0 100644
--- a/pkg/contracts/ton/testdata/01.json
+++ b/pkg/contracts/ton/testdata/01-deposit.json
@@ -1,8 +1,8 @@
{
"account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b",
"boc": "b5ee9c7201020a0100023d0003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017ab22a3b3031b48df020aa3647a59163a25772d81991916a2bf523b771d89deec9e5be15d58000017ab20f8740366ead1e30003465b1d0080102030201e004050082723e346a6461f48fab691e87d9fd5954595eb15351fa1c536486a863d9708b79c313c7e41677dcade29f2b424cdad8712132fbd5465b37df2e1763369e2fb12da0021904222490ee6b28018646dca110080900f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d0ee6b28000608235a00002f5645476604cdd5a3c60000003280000000000000006c6d35f934b257cebf76cf01f29a0ae9bd54b022c00101df06015de004cbec44e40ad75610e23fc357071c1c1f7e1e1a31833ac17b131d6a462d0a42d800002f5645476608cdd5a3c6c007008b0000006500000000000000008012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1e876046701b1b4d7e4d2c95f3afddb3c07ca682ba6f552c08b009e42d5ac3d090000000000000000007e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98510184c2880c0000000000002000000000003da7f47a5d1898330bd18801617ae23a388cbaf527c312921718ef36ce9cf8c4e40901d04",
- "description": "A deposit to gw contract on testnet",
+ "description": "Sample deposit to gw contract. https://testnet.tonviewer.com/transaction/cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf",
"hash": "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf",
"logicalTime": 26023788000003,
- "test": false
+ "test": true
}
diff --git a/pkg/contracts/ton/testdata/readme.md b/pkg/contracts/ton/testdata/readme.md
index 30a59a1639..af8a878904 100644
--- a/pkg/contracts/ton/testdata/readme.md
+++ b/pkg/contracts/ton/testdata/readme.md
@@ -8,10 +8,10 @@ for further usage in test cases.
## Example usage
```sh
-go run pkg/contracts/ton/testdata/scraper.go \
- kQCZfYicgVrqwhxH-Grg44OD78PDRjBnWC9iY61IxaFIW77M \
- 26023788000003 \
- cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf | jq
+go run pkg/contracts/ton/testdata/scraper.go -testnet \
+ kQCZfYicgVrqwhxH-Grg44OD78PDRjBnWC9iY61IxaFIW77M \
+ 26023788000003 \
+ cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf | jq
```
Returns
@@ -23,7 +23,7 @@ Returns
"description": "todo",
"hash": "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf",
"logicalTime": 26023788000003,
- "test": false
+ "test": true
}
```
diff --git a/pkg/contracts/ton/testdata/scraper.go b/pkg/contracts/ton/testdata/scraper.go
index c9c2705375..e75f1bba23 100644
--- a/pkg/contracts/ton/testdata/scraper.go
+++ b/pkg/contracts/ton/testdata/scraper.go
@@ -20,8 +20,8 @@ func main() {
flag.BoolVar(&testnet, "testnet", false, "Use testnet network")
flag.Parse()
- if len(flag.Args()) != 3 {
- log.Fatalf("Usage: go run scrape.go [--testnet]")
+ if len(flag.Args()) < 3 {
+ log.Fatalf("Usage: go run scrape.go [-testnet] ")
}
// Parse account
diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go
deleted file mode 100644
index e4d0ce4e35..0000000000
--- a/zetaclient/chains/ton/observer/observer_test.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package observer
-
-import "testing"
-
-func TestObserver(t *testing.T) {
- // todo
-}
From 861cb4a31ad74c43eeeb8bfe77a6413ecae041da Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Mon, 23 Sep 2024 22:05:46 +0300
Subject: [PATCH 09/36] Implement Gateway inbound tx parsing
---
pkg/contracts/ton/gateway.go | 12 ++-
pkg/contracts/ton/gateway_test.go | 87 +++++++++++++++++--
.../ton/testdata/02-deposit-and-call.json | 8 ++
pkg/contracts/ton/testdata/03-failed-tx.json | 8 ++
.../ton/testdata/04-bounced-msg.json | 8 ++
pkg/contracts/ton/testdata/long-call-data.txt | 28 ++++++
6 files changed, 139 insertions(+), 12 deletions(-)
create mode 100644 pkg/contracts/ton/testdata/02-deposit-and-call.json
create mode 100644 pkg/contracts/ton/testdata/03-failed-tx.json
create mode 100644 pkg/contracts/ton/testdata/04-bounced-msg.json
create mode 100644 pkg/contracts/ton/testdata/long-call-data.txt
diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go
index d41a37168f..f2d24d78aa 100644
--- a/pkg/contracts/ton/gateway.go
+++ b/pkg/contracts/ton/gateway.go
@@ -1,6 +1,8 @@
package ton
import (
+ "bytes"
+
"cosmossdk.io/math"
eth "github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
@@ -64,7 +66,8 @@ func NewGateway(accountID ton.AccountID) *Gateway {
func (gw *Gateway) ParseTransaction(tx ton.Transaction) (*Transaction, error) {
if !tx.IsSuccess() {
- return nil, errors.Wrapf(ErrParse, "tx %s is not successful", tx.Hash().Hex())
+ exitCode := tx.Description.TransOrd.ComputePh.TrPhaseComputeVm.Vm.ExitCode
+ return nil, errors.Wrapf(ErrParse, "tx %s is not successful (exit code %d)", tx.Hash().Hex(), exitCode)
}
if tx.Msgs.InMsg.Exists {
@@ -223,13 +226,16 @@ func parseDepositAndCall(tx ton.Transaction, sender ton.AccountID, body *boc.Cel
}
var sd tlb.SnakeData
- if err = sd.UnmarshalTLB(callDataCell, &tlb.Decoder{}); err != nil {
+ if err = unmarshalTLB(&sd, callDataCell); err != nil {
return DepositAndCall{}, errors.Wrap(err, "unable to unmarshal call data")
}
cd := boc.BitString(sd)
- return DepositAndCall{Deposit: deposit, CallData: cd.Buffer()}, nil
+ // TLB operates with bits, so we (might) need to trim some "leftovers" (null chars)
+ callData := bytes.Trim(cd.Buffer(), "\x00")
+
+ return DepositAndCall{Deposit: deposit, CallData: callData}, nil
}
func (gw *Gateway) parseOutbound(_ ton.Transaction) (*Transaction, error) {
diff --git a/pkg/contracts/ton/gateway_test.go b/pkg/contracts/ton/gateway_test.go
index 6e9ee2b3d1..fee14a8aed 100644
--- a/pkg/contracts/ton/gateway_test.go
+++ b/pkg/contracts/ton/gateway_test.go
@@ -59,7 +59,7 @@ func TestParsing(t *testing.T) {
// Check tx props
assert.Equal(t, int(OpDeposit), int(parsedTX.Operation))
- // Check
+ // Check deposit
deposit, err := parsedTX.Deposit()
assert.NoError(t, err)
@@ -79,14 +79,74 @@ func TestParsing(t *testing.T) {
})
t.Run("Deposit and call", func(t *testing.T) {
- // todo
+ // ARRANGE
+ // Given a tx
+ tx, fx := getFixtureTX(t, "02-deposit-and-call")
+
+ // Given a gateway contract
+ gw := NewGateway(ton.MustParseAccountID(fx.Account))
+
+ // ACT
+ parsedTX, err := gw.ParseTransaction(tx)
+
+ // ASSERT
+ require.NoError(t, err)
+
+ // Check tx props
+ assert.Equal(t, int(OpDepositAndCall), int(parsedTX.Operation))
+
+ // Check deposit and call
+ depositAndCall, err := parsedTX.DepositAndCall()
+ assert.NoError(t, err)
+
+ const (
+ expectedSender = "0:9594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f"
+ vitalikDotETH = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
+ expectedDeposit = 490_000_000 // 0.49 TON
+ )
+
+ expectedCallData := readFixtureFile(t, "testdata/long-call-data.txt")
+
+ assert.Equal(t, expectedSender, depositAndCall.Sender.ToRaw())
+ assert.Equal(t, expectedDeposit, int(depositAndCall.Amount.Uint64()))
+ assert.Equal(t, vitalikDotETH, depositAndCall.Recipient.Hex())
+ assert.Equal(t, expectedCallData, depositAndCall.CallData)
})
t.Run("Irrelevant tx", func(t *testing.T) {
- // todo
- })
+ t.Run("Failed tx", func(t *testing.T) {
+ // ARRANGE
+ // Given a tx
+ tx, fx := getFixtureTX(t, "03-failed-tx")
+
+ // Given a gateway contract
+ gw := NewGateway(ton.MustParseAccountID(fx.Account))
+
+ // ACT
+ _, err := gw.ParseTransaction(tx)
+
+ assert.ErrorIs(t, err, ErrParse)
- // todo
+ // 102 is 'unknown op'
+ // https://github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/common/errors.fc
+ assert.ErrorContains(t, err, "is not successful (exit code 102)")
+ })
+
+ t.Run("not a deposit nor withdrawal", func(t *testing.T) {
+ // actually, it's a bounce of the previous tx
+
+ // ARRANGE
+ // Given a tx
+ tx, fx := getFixtureTX(t, "04-bounced-msg")
+
+ // Given a gateway contract
+ gw := NewGateway(ton.MustParseAccountID(fx.Account))
+
+ // ACT
+ _, err := gw.ParseTransaction(tx)
+ assert.Error(t, err)
+ })
+ })
}
func TestFixtures(t *testing.T) {
@@ -114,10 +174,10 @@ type fixture struct {
func getFixtureTX(t *testing.T, name string) (ton.Transaction, fixture) {
t.Helper()
- filename := fmt.Sprintf("testdata/%s.json", name)
-
- b, err := fixtures.ReadFile(filename)
- require.NoError(t, err, filename)
+ var (
+ filename = fmt.Sprintf("testdata/%s.json", name)
+ b = readFixtureFile(t, filename)
+ )
// bag of cells
var fx fixture
@@ -138,3 +198,12 @@ func getFixtureTX(t *testing.T, name string) (ton.Transaction, fixture) {
return tx, fx
}
+
+func readFixtureFile(t *testing.T, filename string) []byte {
+ t.Helper()
+
+ b, err := fixtures.ReadFile(filename)
+ require.NoError(t, err, filename)
+
+ return b
+}
diff --git a/pkg/contracts/ton/testdata/02-deposit-and-call.json b/pkg/contracts/ton/testdata/02-deposit-and-call.json
new file mode 100644
index 0000000000..60f824a828
--- /dev/null
+++ b/pkg/contracts/ton/testdata/02-deposit-and-call.json
@@ -0,0 +1,8 @@
+{
+ "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b",
+ "boc": "b5ee9c7201021a01000a040003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017d4acf14a83d9339c9e78a55ee9ea0cd46cab798926c139db2a7c17a002041c3db90a80d5ea000017d46c45814366f1896b000347487d2080102030201e00405008272a7b38b0f8722a81351a279e9d229e4f5d92a5d91aed6c197ab4b5ef756b042ccdb7075fb2e3f1dc397aa3c93d8dd352cbe54af9fb033df63082875f03a70947102190480b409077359401866309211181901f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d077359400069397a000002fa959e29504cde312d60000003300000000000000006c6d35f934b257cebf76cf01f29a0ae9bd54b022c0080101df06015de004cbec44e40ad75610e23fc357071c1c1f7e1e1a31833ac17b131d6a462d0a42d800002fa959e29508cde312d6c007018b0000006600000000000000008012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1e83a699d01b1b4d7e4d2c95f3afddb3c07ca682ba6f552c08b0801fe57686174206973204c6f72656d20497073756d3f2028617070726f782032204b696c6f6279746573290a0a4c6f72656d20497073756d2069732073696d706c792064756d6d792074657874206f6620746865207072696e74696e6720616e64207479706573657474696e6720696e6475737472792e0a4c6f72656d204970730901fe756d20686173206265656e2074686520696e6475737472792773207374616e646172642064756d6d79207465787420657665722073696e6365207468652031353030732c207768656e20616e20756e6b6e6f776e207072696e74657220746f6f6b0a612067616c6c6579206f66207479706520616e6420736372616d626c650a01fe6420697420746f206d616b65206120747970652073706563696d656e20626f6f6b2e20497420686173207375727669766564206e6f74206f6e6c7920666976652063656e7475726965732c0a62757420616c736f20746865206c65617020696e746f20656c656374726f6e6963207479706573657474696e672c2072656d610b01fe696e696e6720657373656e7469616c6c7920756e6368616e6765642e0a0a49742077617320706f70756c61726973656420696e207468652031393630732077697468207468652072656c65617365206f66204c657472617365742073686565747320636f6e7461696e696e67204c6f72656d20497073756d207061737361670c01fe65732c0a616e64206d6f726520726563656e746c792077697468206465736b746f70207075626c697368696e6720736f667477617265206c696b6520416c64757320506167654d616b657220696e636c7564696e672076657273696f6e73206f66204c6f72656d20497073756d2e0a0a57687920646f2077652075736520690d01fe743f0a0a49742069732061206c6f6e672d65737461626c6973686564206661637420746861742061207265616465722077696c6c206265206469737472616374656420627920746865207265616461626c6520636f6e74656e74206f6620612070616765207768656e0a6c6f6f6b696e6720617420697473206c61796f75740e01fe2e2054686520706f696e74206f66207573696e67204c6f72656d20497073756d2069732074686174206974206861732061206d6f72652d6f722d6c657373206e6f726d616c20646973747269627574696f6e206f66206c6574746572732c0a6173206f70706f73656420746f207573696e672027436f6e74656e74206865720f01fe652c20636f6e74656e742068657265272c206d616b696e67206974206c6f6f6b206c696b65207265616461626c6520456e676c6973682e204d616e79206465736b746f70207075626c697368696e670a7061636b6167657320616e6420776562207061676520656469746f7273206e6f7720757365204c6f72656d204970731001fe756d2061732074686569722064656661756c74206d6f64656c20746578742c20616e6420612073656172636820666f7220276c6f72656d20697073756d270a77696c6c20756e636f766572206d616e7920776562207369746573207374696c6c20696e20746865697220696e66616e63792e20566172696f757320766572731101fe696f6e7320686176652065766f6c766564206f766572207468652079656172732c20736f6d6574696d65730a6279206163636964656e742c20736f6d6574696d6573206f6e20707572706f73652028696e6a65637465642068756d6f757220616e6420746865206c696b65292e0a0a576865726520646f657320697420636f1201fe6d652066726f6d3f0a0a436f6e747261727920746f20706f70756c61722062656c6965662c204c6f72656d20497073756d206973206e6f742073696d706c792072616e646f6d20746578742e2049742068617320726f6f747320696e2061207069656365206f6620636c6173736963616c0a4c6174696e206c6974657261741301fe7572652066726f6d2034352042432c206d616b696e67206974206f7665722032303030207965617273206f6c642e2052696368617264204d63436c696e746f636b2c2061204c6174696e2070726f666573736f720a61742048616d7064656e2d5379646e657920436f6c6c65676520696e2056697267696e69612c206c6f6f1401fe6b6564207570206f6e65206f6620746865206d6f7265206f627363757265204c6174696e20776f7264732c20636f6e73656374657475722c0a66726f6d2061204c6f72656d20497073756d20706173736167652c20616e6420676f696e67207468726f75676820746865206369746573206f662074686520776f726420696e1501fe20636c6173736963616c206c6974657261747572652c20646973636f76657265640a74686520756e646f75627461626c6520736f757263652e204c6f72656d20497073756d20636f6d65732066726f6d2073656374696f6e7320312e31302e333220616e6420312e31302e3333206f66202264652046696e6962757320426f1601fe6e6f72756d206574204d616c6f72756d220a285468652045787472656d6573206f6620476f6f6420616e64204576696c292062792043696365726f2c207772697474656e20696e2034352042432e205468697320626f6f6b2069732061207472656174697365206f6e20746865207468656f7279206f66206574686963732c17004a0a7665727920706f70756c617220647572696e67207468652052656e61697373616e63652e009e43f62c3d090000000000000000009b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc9b95b984dcadcc0000000000002000000000003fa4c79ea2219e757506c681b3ab938299662dccbcef3f334edcddee1cd13bade44920284",
+ "description": "Sample deposit-and-call to gw contract. https://testnet.tonviewer.com/transaction/3647f17cc28e4a70404a10c62ad6262fbf67aa72579acde449d66cc0d0fd7ca8",
+ "hash": "3647f17cc28e4a70404a10c62ad6262fbf67aa72579acde449d66cc0d0fd7ca8",
+ "logicalTime": 26202202000003,
+ "test": true
+}
\ No newline at end of file
diff --git a/pkg/contracts/ton/testdata/03-failed-tx.json b/pkg/contracts/ton/testdata/03-failed-tx.json
new file mode 100644
index 0000000000..0fa772b7fe
--- /dev/null
+++ b/pkg/contracts/ton/testdata/03-failed-tx.json
@@ -0,0 +1,8 @@
+{
+ "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b",
+ "boc": "b5ee9c72010208010001e90003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017d5af81eb033647f17cc28e4a70404a10c62ad6262fbf67aa72579acde449d66cc0d0fd7ca8000017d4acf14a8366f1b2b00003461489a480102030201e00405008272db7075fb2e3f1dc397aa3c93d8dd352cbe54af9fb033df63082875f03a709471f41d1551f3ff76f22096fabf1806f354a5437a60a611df452f2a78e6dbd9adab01290482c7c9017d78401061061c0e0181046998208d6a0700cb68012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d017d784000608235a00002fab5f03d604cde365602432b6363796102bb7b9363210c00101df0600d3580132fb113902b5d584388ff0d5c1c70707df87868c60ceb05ec4c75a918b4290b700256531c67b13257d99a0ecbec7282c27792907dbec21ee93234996e5a9333943d0179e56800608235a00002fab5f03d608cde365607fffffffa432b6363796102bb7b9363210c0009e40a7cc0f424000000000cc0000002600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "description": "failed tx with body='Hello, World!'; https://testnet.tonviewer.com/transaction/653d37cfbff76585d336fb74a0eaa7fe6d1a2b3cae56d5e5f9609a821c9f1e45",
+ "hash": "653d37cfbff76585d336fb74a0eaa7fe6d1a2b3cae56d5e5f9609a821c9f1e45",
+ "logicalTime": 26206540000003,
+ "test": true
+}
diff --git a/pkg/contracts/ton/testdata/04-bounced-msg.json b/pkg/contracts/ton/testdata/04-bounced-msg.json
new file mode 100644
index 0000000000..9887db3123
--- /dev/null
+++ b/pkg/contracts/ton/testdata/04-bounced-msg.json
@@ -0,0 +1,8 @@
+{
+ "account": "0:9594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f",
+ "boc": "b5ee9c72010207010001a30003b579594c719ec4c95f66683b2fb1ca0b09de4a41f6fb087ba4c8d265b96a4cce50f000017d5af81eb05b4269279feefdc3ea4220465ec2bce20c6e7f0a8c09b51dac55b90aef3c342c6000017d5af81eb0166f1b2b0000146097f4080102030101a0040082727682d6ce21cbeaec70573e8d6ab7dc6357b875cbffc8c50608035fda3fe3bc378db05a075336855e0ae2db0067bf7de2341a87b9d555e968d64b216fe5491aba02150c090179e568186097f411050600d3580132fb113902b5d584388ff0d5c1c70707df87868c60ceb05ec4c75a918b4290b700256531c67b13257d99a0ecbec7282c27792907dbec21ee93234996e5a9333943d0179e56800608235a00002fab5f03d608cde365607fffffffa432b6363796102bb7b9363210c0009e40614c0f1da800000000000000001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005bc00000000000000000000000012d452da449e50b8cf7dd27861f146122afe1b546bb8b70fc8216f0c614139f8e04",
+ "description": "Sample bounced message. This address is not even a gw. https://testnet.tonviewer.com/transaction/b3c46f5faf8aee7348083e7adbbc9a60ab1c8e0eac09133d64e2c4eb831e607b",
+ "hash": "b3c46f5faf8aee7348083e7adbbc9a60ab1c8e0eac09133d64e2c4eb831e607b",
+ "logicalTime": 26206540000005,
+ "test": true
+}
diff --git a/pkg/contracts/ton/testdata/long-call-data.txt b/pkg/contracts/ton/testdata/long-call-data.txt
new file mode 100644
index 0000000000..d66c4d103a
--- /dev/null
+++ b/pkg/contracts/ton/testdata/long-call-data.txt
@@ -0,0 +1,28 @@
+What is Lorem Ipsum? (approx 2 Kilobytes)
+
+Lorem Ipsum is simply dummy text of the printing and typesetting industry.
+Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took
+a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries,
+but also the leap into electronic typesetting, remaining essentially unchanged.
+
+It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,
+and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
+
+Why do we use it?
+
+It is a long-established fact that a reader will be distracted by the readable content of a page when
+looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters,
+as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing
+packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum'
+will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes
+by accident, sometimes on purpose (injected humour and the like).
+
+Where does it come from?
+
+Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical
+Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor
+at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur,
+from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered
+the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum"
+(The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics,
+very popular during the Renaissance.
\ No newline at end of file
From a19a61b4c6d969ebe1f418c793bd716550360b32 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Tue, 24 Sep 2024 13:54:06 +0300
Subject: [PATCH 10/36] Add Gateway tx filtration
---
pkg/contracts/ton/gateway.go | 25 ++++++++++++++++
pkg/contracts/ton/gateway_test.go | 50 +++++++++++++++++++++++++++++++
2 files changed, 75 insertions(+)
diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go
index f2d24d78aa..1c331d17c8 100644
--- a/pkg/contracts/ton/gateway.go
+++ b/pkg/contracts/ton/gateway.go
@@ -87,6 +87,31 @@ func (gw *Gateway) ParseTransaction(tx ton.Transaction) (*Transaction, error) {
return outbound, nil
}
+// ParseAndFilter parses transaction and applies filter to it. Returns (tx, skip?, error)
+// If parse fails due to known error, skip is set to true
+func (gw *Gateway) ParseAndFilter(tx ton.Transaction, filter func(*Transaction) bool) (*Transaction, bool, error) {
+ parsedTX, err := gw.ParseTransaction(tx)
+ switch {
+ case errors.Is(err, ErrParse):
+ return nil, true, nil
+ case errors.Is(err, ErrUnknownOp):
+ return nil, true, nil
+ case err != nil:
+ return nil, false, err
+ }
+
+ if !filter(parsedTX) {
+ return nil, true, nil
+ }
+
+ return parsedTX, false, nil
+}
+
+// FilterDeposit filters transactions with deposit operations
+func FilterDeposit(tx *Transaction) bool {
+ return tx.Operation == OpDeposit || tx.Operation == OpDepositAndCall
+}
+
func (gw *Gateway) parseInbound(tx ton.Transaction) (*Transaction, error) {
body, err := parseInternalMessageBody(tx)
if err != nil {
diff --git a/pkg/contracts/ton/gateway_test.go b/pkg/contracts/ton/gateway_test.go
index fee14a8aed..5820f59edc 100644
--- a/pkg/contracts/ton/gateway_test.go
+++ b/pkg/contracts/ton/gateway_test.go
@@ -149,6 +149,56 @@ func TestParsing(t *testing.T) {
})
}
+func TestFiltering(t *testing.T) {
+ t.Run("Inbound", func(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ skip bool
+ error bool
+ }{
+ // donation is not a deposit :)
+ {"00-donation", true, false},
+
+ // Should be parsed and filtered
+ {"01-deposit", false, false},
+ {"02-deposit-and-call", false, false},
+
+ // Should be skipped
+ {"03-failed-tx", true, false},
+ {"04-bounced-msg", true, false},
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ // ARRANGE
+ // Given a tx
+ tx, fx := getFixtureTX(t, tt.name)
+
+ // Given a gateway
+ gw := NewGateway(ton.MustParseAccountID(fx.Account))
+
+ // ACT
+ parsedTX, skip, err := gw.ParseAndFilter(tx, FilterDeposit)
+
+ if tt.error {
+ require.Error(t, err)
+ assert.False(t, skip)
+ assert.Nil(t, parsedTX)
+ return
+ }
+
+ require.NoError(t, err)
+ assert.Equal(t, tt.skip, skip)
+
+ if tt.skip {
+ assert.Nil(t, parsedTX)
+ return
+ }
+
+ assert.NotNil(t, parsedTX)
+ })
+ }
+ })
+}
+
func TestFixtures(t *testing.T) {
// ACT
tx, _ := getFixtureTX(t, "01-deposit")
From 5f14515b2668c123779514463b0d9046bc8783c1 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Wed, 25 Sep 2024 00:26:20 +0300
Subject: [PATCH 11/36] Add GetBlockHeader cache; Add masterchain seqno.
Implement watchInbound
---
pkg/contracts/ton/gateway.go | 32 ++-
pkg/contracts/ton/gateway_test.go | 6 +-
zetaclient/chains/ton/liteapi/client.go | 53 ++++-
.../chains/ton/liteapi/client_live_test.go | 35 +++-
zetaclient/chains/ton/observer/inbound.go | 186 +++++++++++++-----
zetaclient/chains/ton/observer/observer.go | 24 ++-
6 files changed, 260 insertions(+), 76 deletions(-)
diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go
index 1c331d17c8..847d967c9e 100644
--- a/pkg/contracts/ton/gateway.go
+++ b/pkg/contracts/ton/gateway.go
@@ -11,6 +11,7 @@ import (
"github.com/tonkeeper/tongo/ton"
)
+// Op operation code
type Op uint32
// github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc
@@ -29,26 +30,46 @@ const (
UpdateCode
)
+// Gateway wrapper around zeta gateway contract on TON
type Gateway struct {
accountID ton.AccountID
}
+// Donation represents a donation operation
type Donation struct {
Sender ton.AccountID
Amount math.Uint
}
+// Deposit represents a deposit operation
type Deposit struct {
Sender ton.AccountID
Amount math.Uint
Recipient eth.Address
}
+// Memo casts deposit to memo bytes
+func (d Deposit) Memo() []byte {
+ return d.Recipient.Bytes()
+}
+
+// DepositAndCall represents a deposit and call operation
type DepositAndCall struct {
Deposit
CallData []byte
}
+// Memo casts deposit and call to memo bytes
+func (d DepositAndCall) Memo() []byte {
+ recipient := d.Recipient.Bytes()
+ out := make([]byte, 0, len(recipient)+len(d.CallData))
+
+ out = append(out, recipient...)
+ out = append(out, d.CallData...)
+
+ return out
+}
+
const (
sizeOpCode = 32
sizeQueryID = 64
@@ -64,6 +85,11 @@ func NewGateway(accountID ton.AccountID) *Gateway {
return &Gateway{accountID}
}
+func (gw *Gateway) AccountID() ton.AccountID {
+ return gw.accountID
+}
+
+// ParseTransaction parses transaction to Transaction
func (gw *Gateway) ParseTransaction(tx ton.Transaction) (*Transaction, error) {
if !tx.IsSuccess() {
exitCode := tx.Description.TransOrd.ComputePh.TrPhaseComputeVm.Vm.ExitCode
@@ -107,10 +133,8 @@ func (gw *Gateway) ParseAndFilter(tx ton.Transaction, filter func(*Transaction)
return parsedTX, false, nil
}
-// FilterDeposit filters transactions with deposit operations
-func FilterDeposit(tx *Transaction) bool {
- return tx.Operation == OpDeposit || tx.Operation == OpDepositAndCall
-}
+// FilterInbounds filters transactions with deposit operations
+func FilterInbounds(tx *Transaction) bool { return tx.IsInbound() }
func (gw *Gateway) parseInbound(tx ton.Transaction) (*Transaction, error) {
body, err := parseInternalMessageBody(tx)
diff --git a/pkg/contracts/ton/gateway_test.go b/pkg/contracts/ton/gateway_test.go
index 5820f59edc..70d56e11c0 100644
--- a/pkg/contracts/ton/gateway_test.go
+++ b/pkg/contracts/ton/gateway_test.go
@@ -156,10 +156,8 @@ func TestFiltering(t *testing.T) {
skip bool
error bool
}{
- // donation is not a deposit :)
- {"00-donation", true, false},
-
// Should be parsed and filtered
+ {"00-donation", false, false},
{"01-deposit", false, false},
{"02-deposit-and-call", false, false},
@@ -176,7 +174,7 @@ func TestFiltering(t *testing.T) {
gw := NewGateway(ton.MustParseAccountID(fx.Account))
// ACT
- parsedTX, skip, err := gw.ParseAndFilter(tx, FilterDeposit)
+ parsedTX, skip, err := gw.ParseAndFilter(tx, FilterInbounds)
if tt.error {
require.Error(t, err)
diff --git a/zetaclient/chains/ton/liteapi/client.go b/zetaclient/chains/ton/liteapi/client.go
index e895ff4298..bbe94147fd 100644
--- a/zetaclient/chains/ton/liteapi/client.go
+++ b/zetaclient/chains/ton/liteapi/client.go
@@ -6,6 +6,7 @@ import (
"strconv"
"strings"
+ lru "github.com/hashicorp/golang-lru"
"github.com/pkg/errors"
"github.com/tonkeeper/tongo/liteapi"
"github.com/tonkeeper/tongo/tlb"
@@ -13,11 +14,61 @@ import (
)
// Client extends tongo's liteapi.Client with some high-level tools
+// Reference: https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl
type Client struct {
*liteapi.Client
+ blockCache *lru.Cache
}
-const pageSize = 200
+const (
+ pageSize = 200
+ blockCacheSize = 250
+)
+
+// New Client constructor.
+func New(client *liteapi.Client) *Client {
+ blockCache, _ := lru.New(blockCacheSize)
+
+ return &Client{Client: client, blockCache: blockCache}
+}
+
+// GetBlockHeader returns block header by block ID.
+// Uses LRU cache for network efficiency.
+// I haven't found what mode means but `0` works fine.
+func (c *Client) GetBlockHeader(ctx context.Context, blockID ton.BlockIDExt, mode uint32) (tlb.BlockInfo, error) {
+ if c.blockCache == nil {
+ return tlb.BlockInfo{}, errors.New("block cache is not initialized")
+ }
+
+ cached, ok := c.getBlockHeaderCache(blockID)
+ if ok {
+ return cached, nil
+ }
+
+ header, err := c.Client.GetBlockHeader(ctx, blockID, mode)
+ if err != nil {
+ return tlb.BlockInfo{}, err
+ }
+
+ c.setBlockHeaderCache(blockID, header)
+
+ return header, nil
+}
+
+func (c *Client) getBlockHeaderCache(blockID ton.BlockIDExt) (tlb.BlockInfo, bool) {
+ raw, ok := c.blockCache.Get(blockID.String())
+ if !ok {
+ return tlb.BlockInfo{}, false
+ }
+
+ header, ok := raw.(tlb.BlockInfo)
+
+ return header, ok
+}
+
+func (c *Client) setBlockHeaderCache(blockID ton.BlockIDExt, header tlb.BlockInfo) {
+ c.blockCache.Add(blockID.String(), header)
+}
// GetFirstTransaction scrolls through the transactions of the given account to find the first one.
// Note that it might fail w/o using an archival node. Also returns the number of
diff --git a/zetaclient/chains/ton/liteapi/client_live_test.go b/zetaclient/chains/ton/liteapi/client_live_test.go
index 435da2787a..d090771ed9 100644
--- a/zetaclient/chains/ton/liteapi/client_live_test.go
+++ b/zetaclient/chains/ton/liteapi/client_live_test.go
@@ -19,12 +19,12 @@ import (
func TestClient(t *testing.T) {
if !common.LiveTestEnabled() {
- t.Skip("Live tests are disabled")
+ //t.Skip("Live tests are disabled")
}
var (
ctx = context.Background()
- client = &Client{Client: mustCreateClient(t)}
+ client = New(mustCreateClient(t))
)
t.Run("GetFirstTransaction", func(t *testing.T) {
@@ -58,7 +58,7 @@ func TestClient(t *testing.T) {
t.Run("GetTransactionsUntil", func(t *testing.T) {
// ARRANGE
- // Given sample account id (a dev wallet)
+ // Given sample account id (dev wallet)
// https://tonviewer.com/UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr?section=transactions
accountID, err := ton.ParseAccountID("UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr")
require.NoError(t, err)
@@ -91,6 +91,35 @@ func TestClient(t *testing.T) {
mustContainTX(t, txs, "a6672a0e80193c1f705ef1cf45a5883441b8252523b1d08f7656c80e400c74a8")
assert.GreaterOrEqual(t, len(txs), expectedTX)
})
+
+ t.Run("GetBlockHeader", func(t *testing.T) {
+ // ARRANGE
+ // Given sample account id (dev wallet)
+ // https://tonscan.org/address/UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr
+ accountID, err := ton.ParseAccountID("UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr")
+ require.NoError(t, err)
+
+ const getUntilLT = uint64(48645164000001)
+ const getUntilHash = `2e107215e634bbc3492bdf4b1466d59432623295072f59ab526d15737caa9531`
+
+ var hash ton.Bits256
+ require.NoError(t, hash.FromHex(getUntilHash))
+
+ txs, err := client.GetTransactions(ctx, 1, accountID, getUntilLT, hash)
+ require.NoError(t, err)
+ require.Len(t, txs, 1)
+
+ // Given a block
+ blockID := txs[0].BlockID
+
+ // ACT
+ header, err := client.GetBlockHeader(ctx, blockID, 0)
+
+ // ASSERT
+ require.NoError(t, err)
+ require.NotZero(t, header.MinRefMcSeqno)
+ require.Equal(t, header.MinRefMcSeqno, header.MasterRef.Master.SeqNo)
+ })
}
func mustCreateClient(t *testing.T) *liteapi.Client {
diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go
index e8d403d5e8..2d0950bd89 100644
--- a/zetaclient/chains/ton/observer/inbound.go
+++ b/zetaclient/chains/ton/observer/inbound.go
@@ -5,13 +5,17 @@ import (
"fmt"
"slices"
- "cosmossdk.io/errors"
+ "cosmossdk.io/math"
+ "github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/tonkeeper/tongo/ton"
+ "github.com/zeta-chain/node/pkg/coin"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
"github.com/zeta-chain/node/pkg/ticker"
"github.com/zeta-chain/node/zetaclient/chains/ton/liteapi"
zctx "github.com/zeta-chain/node/zetaclient/context"
+ "github.com/zeta-chain/node/zetaclient/zetacore"
)
const (
@@ -58,14 +62,6 @@ func (ob *Observer) watchInbound(ctx context.Context) error {
)
}
-// Flow:
-// - [x] Ensure last scanned transaction is set
-// - [x] Get all transaction between [lastScannedTx; now]
-// - [ ] Filter only valid and inbound transactions
-// - [ ] For each transaction (ordered by *ASC*)
-// - [ ] Construct crosschain cosmos message
-// - [ ] Vote
-// - [ ] Save last scanned tx
func (ob *Observer) observeInbound(ctx context.Context, _ *zctx.AppContext) error {
if err := ob.ensureLastScannedTX(ctx); err != nil {
return errors.Wrap(err, "unable to ensure last scanned tx")
@@ -76,7 +72,7 @@ func (ob *Observer) observeInbound(ctx context.Context, _ *zctx.AppContext) erro
return errors.Wrapf(err, "unable to parse last scanned tx %q", ob.LastTxScanned())
}
- txs, err := ob.client.GetTransactionsUntil(ctx, ob.gatewayID, lt, hashBits)
+ txs, err := ob.client.GetTransactionsUntil(ctx, ob.gateway.AccountID(), lt, hashBits)
if err != nil {
return errors.Wrap(err, "unable to get transactions")
}
@@ -97,66 +93,154 @@ func (ob *Observer) observeInbound(ctx context.Context, _ *zctx.AppContext) erro
ob.Logger().Inbound.Info().Msgf("ObserveInbound: got %d transactions", len(txs))
}
- // todo deploy sample GW to testnet
- // todo send some TON and test
+ for i := range txs {
+ tx := txs[i]
- // todo FilterInboundEvent
+ parsedTX, skip, err := ob.gateway.ParseAndFilter(tx, toncontracts.FilterInbounds)
+ if err != nil {
+ return errors.Wrap(err, "unable to parse and filter tx")
+ }
- for _, tx := range txs {
- fmt.Println("TON TX", tx)
- }
+ if skip {
+ ob.setLastScannedTX(&tx)
+ continue
+ }
- /*
- // loop signature from oldest to latest to filter inbound events
- for i := len(signatures) - 1; i >= 0; i-- {
- sig := signatures[i]
- sigString := sig.Signature.String()
-
- // process successfully signature only
- if sig.Err == nil {
- txResult, err := ob.solClient.GetTransaction(ctx, sig.Signature, &rpc.GetTransactionOpts{})
- if err != nil {
- // we have to re-scan this signature on next ticker
- return errors.Wrapf(err, "error GetTransaction for chain %d sig %s", chainID, sigString)
- }
-
- // filter inbound events and vote
- err = ob.FilterInboundEventsAndVote(ctx, txResult)
- if err != nil {
- // we have to re-scan this signature on next ticker
- return errors.Wrapf(err, "error FilterInboundEventAndVote for chain %d sig %s", chainID, sigString)
- }
- }
-
- // signature scanned; save last scanned signature to both memory and db, ignore db error
- if err := ob.SaveLastTxScanned(sigString, sig.Slot); err != nil {
- ob.Logger().
- Inbound.Error().
- Err(err).
- Msgf("ObserveInbound: error saving last sig %s for chain %d", sigString, chainID)
- }
- ob.Logger().
- Inbound.Info().
- Msgf("ObserveInbound: last scanned sig is %s for chain %d in slot %d", sigString, chainID, sig.Slot)
- */
+ if _, err := ob.voteInbound(ctx, parsedTX); err != nil {
+ return errors.Wrapf(err, "unable to vote inbound (hash %s)", parsedTX.Hash().Hex())
+ }
+
+ ob.setLastScannedTX(&parsedTX.Transaction)
+ }
return nil
}
+func (ob *Observer) voteInbound(ctx context.Context, tx *toncontracts.Transaction) (string, error) {
+ // noop
+ if tx.Operation == toncontracts.OpDonate {
+ ob.Logger().Inbound.Info().
+ Uint64("tx.lt", tx.Lt).
+ Str("tx.hash", tx.Hash().Hex()).
+ Msg("Thank you rich folk for your donation!")
+
+ return "", nil
+ }
+
+ // todo add compliance check
+ // https://github.com/zeta-chain/node/issues/2916
+
+ blockHeader, err := ob.client.GetBlockHeader(ctx, tx.BlockID, 0)
+ if err != nil {
+ return "", errors.Wrapf(err, "unable to get block header %s", tx.BlockID.String())
+ }
+
+ sender, amount, memo, err := extractInboundData(tx)
+ if err != nil {
+ return "", err
+ }
+
+ seqno := blockHeader.MinRefMcSeqno
+
+ return ob.voteDeposit(ctx, tx, sender, amount, memo, seqno)
+}
+
+func extractInboundData(tx *toncontracts.Transaction) (string, math.Uint, []byte, error) {
+ if tx.Operation == toncontracts.OpDeposit {
+ d, err := tx.Deposit()
+ if err != nil {
+ return "", math.NewUint(0), nil, err
+ }
+
+ return d.Sender.ToRaw(), d.Amount, d.Memo(), nil
+ }
+
+ if tx.Operation == toncontracts.OpDepositAndCall {
+ d, err := tx.DepositAndCall()
+ if err != nil {
+ return "", math.NewUint(0), nil, err
+ }
+
+ return d.Sender.ToRaw(), d.Amount, d.Memo(), nil
+ }
+
+ return "", math.NewUint(0), nil, fmt.Errorf("unknown operation %d", tx.Operation)
+}
+
+func (ob *Observer) voteDeposit(
+ ctx context.Context,
+ tx *toncontracts.Transaction,
+ sender string,
+ amount math.Uint,
+ memo []byte,
+ seqno uint32,
+) (string, error) {
+ const (
+ eventIndex = 0 // not a smart contract call
+ coinType = coin.CoinType_Gas
+ asset = "" // empty for gas coin
+ gasLimit = 0
+ retryGasLimit = zetacore.PostVoteInboundExecutionGasLimit
+ )
+
+ var (
+ operatorAddress = ob.ZetacoreClient().GetKeys().GetOperatorAddress()
+ inboundHash = liteapi.TransactionHashToString(tx.Lt, ton.Bits256(tx.Hash()))
+ )
+
+ msg := zetacore.GetInboundVoteMessage(
+ sender,
+ ob.Chain().ChainId,
+ sender,
+ sender,
+ ob.ZetacoreClient().Chain().ChainId,
+ amount,
+ string(memo),
+ inboundHash,
+ uint64(seqno),
+ gasLimit,
+ coinType,
+ asset,
+ operatorAddress.String(),
+ eventIndex,
+ )
+
+ return ob.PostVoteInbound(ctx, msg, retryGasLimit)
+}
+
func (ob *Observer) ensureLastScannedTX(ctx context.Context) error {
// noop
if ob.LastTxScanned() != "" {
return nil
}
- tx, _, err := ob.client.GetFirstTransaction(ctx, ob.gatewayID)
+ tx, _, err := ob.client.GetFirstTransaction(ctx, ob.gateway.AccountID())
if err != nil {
return err
}
+ ob.setLastScannedTX(tx)
+
+ return nil
+}
+
+func (ob *Observer) setLastScannedTX(tx *ton.Transaction) {
txHash := liteapi.TransactionHashToString(tx.Lt, ton.Bits256(tx.Hash()))
ob.WithLastTxScanned(txHash)
- return ob.WriteLastTxScannedToDB(txHash)
+ if err := ob.WriteLastTxScannedToDB(txHash); err != nil {
+ ob.Logger().Inbound.Error().
+ Err(err).
+ Uint64("tx.lt", tx.Lt).
+ Str("tx.hash", tx.Hash().Hex()).
+ Msgf("ObserveInbound: unable to WriteLastTxScannedToDB")
+
+ return
+ }
+
+ ob.Logger().Inbound.Info().
+ Uint64("tx.lt", tx.Lt).
+ Str("tx.hash", tx.Hash().Hex()).
+ Msgf("ObserveInbound: WriteLastTxScannedToDB")
}
diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go
index 03f019d187..5989293795 100644
--- a/zetaclient/chains/ton/observer/observer.go
+++ b/zetaclient/chains/ton/observer/observer.go
@@ -2,10 +2,10 @@ package observer
import (
"context"
-
- "github.com/tonkeeper/tongo/ton"
+ "errors"
"github.com/zeta-chain/node/pkg/bg"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
"github.com/zeta-chain/node/x/crosschain/types"
"github.com/zeta-chain/node/zetaclient/chains/base"
"github.com/zeta-chain/node/zetaclient/chains/interfaces"
@@ -15,19 +15,19 @@ import (
type Observer struct {
base.Observer
- client *liteapi.Client
- gatewayID ton.AccountID
+ client *liteapi.Client
+ gateway *toncontracts.Gateway
}
var _ interfaces.ChainObserver = (*Observer)(nil)
-func New(bo *base.Observer, client *liteapi.Client, gatewayID ton.AccountID) (*Observer, error) {
+func New(bo *base.Observer, client *liteapi.Client, gateway *toncontracts.Gateway) (*Observer, error) {
bo.LoadLastTxScanned()
return &Observer{
- Observer: *bo,
- gatewayID: gatewayID,
- client: client,
+ Observer: *bo,
+ client: client,
+ gateway: gateway,
}, nil
}
@@ -46,14 +46,12 @@ func (ob *Observer) Start(ctx context.Context) {
bg.Work(ctx, ob.watchInbound, bg.WithName("WatchInbound"), bg.WithLogger(ob.Logger().Inbound))
// todo
- // watchOutbound
- // watchGasPrice
// watchInboundTracker
// watchOutbound
+ // watchGasPrice
// watchRPCStatus
}
-func (ob *Observer) VoteOutboundIfConfirmed(ctx context.Context, cctx *types.CrossChainTx) (bool, error) {
- // todo
- return false, nil
+func (ob *Observer) VoteOutboundIfConfirmed(_ context.Context, _ *types.CrossChainTx) (bool, error) {
+ return false, errors.New("not implemented")
}
From bfa9af828a58ffcc52cb6350a4b1ceb3cf4d09e7 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 27 Sep 2024 17:53:55 +0200
Subject: [PATCH 12/36] Improve ton contracts pkg
---
e2e/runner/ton/accounts.go | 14 +---
pkg/contracts/ton/gateway.go | 97 +++++++++++-----------------
pkg/contracts/ton/gateway_op.go | 103 ++++++++++++++++++++++++++++++
pkg/contracts/ton/gateway_test.go | 56 ++++++++++++++++
4 files changed, 198 insertions(+), 72 deletions(-)
create mode 100644 pkg/contracts/ton/gateway_op.go
diff --git a/e2e/runner/ton/accounts.go b/e2e/runner/ton/accounts.go
index f1a3fea659..3ffd8c0907 100644
--- a/e2e/runner/ton/accounts.go
+++ b/e2e/runner/ton/accounts.go
@@ -12,6 +12,8 @@ import (
"github.com/tonkeeper/tongo/ton"
"github.com/tonkeeper/tongo/wallet"
"golang.org/x/crypto/ed25519"
+
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
)
const workchainID = 0
@@ -138,7 +140,7 @@ func buildGatewayData(tss eth.Address) (*boc.Cell, error) {
cell = boc.NewCell()
)
- err := errCollect(
+ err := toncontracts.ErrCollect(
cell.WriteBit(true), // deposits_enabled
zeroCoins.MarshalTLB(cell, enc), // total_locked
zeroCoins.MarshalTLB(cell, enc), // fees
@@ -153,16 +155,6 @@ func buildGatewayData(tss eth.Address) (*boc.Cell, error) {
return cell, nil
}
-func errCollect(errs ...error) error {
- for i, err := range errs {
- if err != nil {
- return errors.Wrapf(err, "error at index %d", i)
- }
- }
-
- return nil
-}
-
// copied from tongo wallets_common.go
func generateStateInit(code, data *boc.Cell) *tlb.StateInit {
return &tlb.StateInit{
diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go
index 847d967c9e..bdd2c433e3 100644
--- a/pkg/contracts/ton/gateway.go
+++ b/pkg/contracts/ton/gateway.go
@@ -11,65 +11,11 @@ import (
"github.com/tonkeeper/tongo/ton"
)
-// Op operation code
-type Op uint32
-
-// github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc
-// Inbound operations
-const (
- OpDonate Op = 100 + iota
- OpDeposit
- OpDepositAndCall
-)
-
-// Outbound operations
-const (
- OpWithdraw Op = 200 + iota
- SetDepositsEnabled
- UpdateTSS
- UpdateCode
-)
-
// Gateway wrapper around zeta gateway contract on TON
type Gateway struct {
accountID ton.AccountID
}
-// Donation represents a donation operation
-type Donation struct {
- Sender ton.AccountID
- Amount math.Uint
-}
-
-// Deposit represents a deposit operation
-type Deposit struct {
- Sender ton.AccountID
- Amount math.Uint
- Recipient eth.Address
-}
-
-// Memo casts deposit to memo bytes
-func (d Deposit) Memo() []byte {
- return d.Recipient.Bytes()
-}
-
-// DepositAndCall represents a deposit and call operation
-type DepositAndCall struct {
- Deposit
- CallData []byte
-}
-
-// Memo casts deposit and call to memo bytes
-func (d DepositAndCall) Memo() []byte {
- recipient := d.Recipient.Bytes()
- out := make([]byte, 0, len(recipient)+len(d.CallData))
-
- out = append(out, recipient...)
- out = append(out, d.CallData...)
-
- return out
-}
-
const (
sizeOpCode = 32
sizeQueryID = 64
@@ -274,16 +220,11 @@ func parseDepositAndCall(tx ton.Transaction, sender ton.AccountID, body *boc.Cel
return DepositAndCall{}, errors.Wrap(err, "unable to read call data cell")
}
- var sd tlb.SnakeData
- if err = unmarshalTLB(&sd, callDataCell); err != nil {
+ callData, err := unmarshalSnakeCell(callDataCell)
+ if err != nil {
return DepositAndCall{}, errors.Wrap(err, "unable to unmarshal call data")
}
- cd := boc.BitString(sd)
-
- // TLB operates with bits, so we (might) need to trim some "leftovers" (null chars)
- callData := bytes.Trim(cd.Buffer(), "\x00")
-
return DepositAndCall{Deposit: deposit, CallData: callData}, nil
}
@@ -309,6 +250,16 @@ func parseInternalMessageBody(tx ton.Transaction) (*boc.Cell, error) {
return body, nil
}
+func ErrCollect(errs ...error) error {
+ for i, err := range errs {
+ if err != nil {
+ return errors.Wrapf(err, "error at index %d", i)
+ }
+ }
+
+ return nil
+}
+
func marshalCell(v tlb.MarshalerTLB) (*boc.Cell, error) {
cell := boc.NewCell()
@@ -337,6 +288,30 @@ func unmarshalTLB(t tlb.UnmarshalerTLB, cell *boc.Cell) error {
return t.UnmarshalTLB(cell, &tlb.Decoder{})
}
+func unmarshalSnakeCell(cell *boc.Cell) ([]byte, error) {
+ var sd tlb.SnakeData
+
+ if err := unmarshalTLB(&sd, cell); err != nil {
+ return nil, err
+ }
+
+ cd := boc.BitString(sd)
+
+ // TLB operates with bits, so we (might) need to trim some "leftovers" (null chars)
+ return bytes.Trim(cd.Buffer(), "\x00"), nil
+}
+
+func marshalSnakeData(data []byte) (*boc.Cell, error) {
+ b := boc.NewCell()
+
+ wrapped := tlb.Bytes(data)
+ if err := wrapped.MarshalTLB(b, &tlb.Encoder{}); err != nil {
+ return nil, err
+ }
+
+ return b, nil
+}
+
func gramToUint(g tlb.Grams) math.Uint {
return math.NewUint(uint64(g))
}
diff --git a/pkg/contracts/ton/gateway_op.go b/pkg/contracts/ton/gateway_op.go
new file mode 100644
index 0000000000..f476d49623
--- /dev/null
+++ b/pkg/contracts/ton/gateway_op.go
@@ -0,0 +1,103 @@
+package ton
+
+import (
+ "cosmossdk.io/math"
+ eth "github.com/ethereum/go-ethereum/common"
+ "github.com/tonkeeper/tongo/boc"
+ "github.com/tonkeeper/tongo/ton"
+)
+
+// Op operation code
+type Op uint32
+
+// github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc
+// Inbound operations
+const (
+ OpDonate Op = 100 + iota
+ OpDeposit
+ OpDepositAndCall
+)
+
+// Outbound operations
+const (
+ OpWithdraw Op = 200 + iota
+ SetDepositsEnabled
+ UpdateTSS
+ UpdateCode
+)
+
+// Donation represents a donation operation
+type Donation struct {
+ Sender ton.AccountID
+ Amount math.Uint
+}
+
+// AsBody casts struct as internal message body.
+func (d Donation) AsBody() (*boc.Cell, error) {
+ b := boc.NewCell()
+ err := ErrCollect(
+ b.WriteUint(uint64(OpDonate), sizeOpCode),
+ b.WriteUint(0, sizeQueryID),
+ )
+
+ return b, err
+}
+
+// Deposit represents a deposit operation
+type Deposit struct {
+ Sender ton.AccountID
+ Amount math.Uint
+ Recipient eth.Address
+}
+
+// Memo casts deposit to memo bytes
+func (d Deposit) Memo() []byte {
+ return d.Recipient.Bytes()
+}
+
+// AsBody casts struct as internal message body.
+func (d Deposit) AsBody() (*boc.Cell, error) {
+ b := boc.NewCell()
+ err := ErrCollect(
+ b.WriteUint(uint64(OpDeposit), sizeOpCode),
+ b.WriteUint(0, sizeQueryID),
+ b.WriteBytes(d.Recipient.Bytes()),
+ )
+
+ return b, err
+}
+
+// DepositAndCall represents a deposit and call operation
+type DepositAndCall struct {
+ Deposit
+ CallData []byte
+}
+
+// Memo casts deposit to call to memo bytes
+func (d DepositAndCall) Memo() []byte {
+ recipient := d.Recipient.Bytes()
+ out := make([]byte, 0, len(recipient)+len(d.CallData))
+
+ out = append(out, recipient...)
+ out = append(out, d.CallData...)
+
+ return out
+}
+
+// AsBody casts struct to internal message body.
+func (d DepositAndCall) AsBody() (*boc.Cell, error) {
+ callDataCell, err := marshalSnakeData(d.CallData)
+ if err != nil {
+ return nil, err
+ }
+
+ b := boc.NewCell()
+ err = ErrCollect(
+ b.WriteUint(uint64(OpDepositAndCall), sizeOpCode),
+ b.WriteUint(0, sizeQueryID),
+ b.WriteBytes(d.Recipient.Bytes()),
+ b.AddRef(callDataCell),
+ )
+
+ return b, err
+}
diff --git a/pkg/contracts/ton/gateway_test.go b/pkg/contracts/ton/gateway_test.go
index 70d56e11c0..6a300ae637 100644
--- a/pkg/contracts/ton/gateway_test.go
+++ b/pkg/contracts/ton/gateway_test.go
@@ -4,8 +4,10 @@ import (
"embed"
"encoding/json"
"fmt"
+ "strings"
"testing"
+ "github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tonkeeper/tongo/boc"
@@ -14,6 +16,15 @@ import (
)
func TestParsing(t *testing.T) {
+ swapBodyAndParse := func(gw *Gateway, tx ton.Transaction, body *boc.Cell) *Transaction {
+ tx.Msgs.InMsg.Value.Value.Body.Value = tlb.Any(*body)
+
+ parsed, err := gw.ParseTransaction(tx)
+ require.NoError(t, err)
+
+ return parsed
+ }
+
t.Run("Donate", func(t *testing.T) {
// ARRANGE
// Given a tx
@@ -40,6 +51,14 @@ func TestParsing(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, expectedSender, donation.Sender.ToRaw())
assert.Equal(t, expectedDonation, int(donation.Amount.Uint64()))
+
+ // Check that AsBody works
+ var (
+ parsedTX2 = swapBodyAndParse(gw, tx, lo.Must(donation.AsBody()))
+ donation2 = lo.Must(parsedTX2.Donation())
+ )
+
+ assert.Equal(t, donation, donation2)
})
t.Run("Deposit", func(t *testing.T) {
@@ -76,6 +95,14 @@ func TestParsing(t *testing.T) {
// Check that other casting fails
_, err = parsedTX.Donation()
assert.ErrorIs(t, err, ErrCast)
+
+ // Check that AsBody works
+ var (
+ parsedTX2 = swapBodyAndParse(gw, tx, lo.Must(deposit.AsBody()))
+ deposit2 = lo.Must(parsedTX2.Deposit())
+ )
+
+ assert.Equal(t, deposit, deposit2)
})
t.Run("Deposit and call", func(t *testing.T) {
@@ -111,6 +138,14 @@ func TestParsing(t *testing.T) {
assert.Equal(t, expectedDeposit, int(depositAndCall.Amount.Uint64()))
assert.Equal(t, vitalikDotETH, depositAndCall.Recipient.Hex())
assert.Equal(t, expectedCallData, depositAndCall.CallData)
+
+ // Check that AsBody works
+ var (
+ parsedTX2 = swapBodyAndParse(gw, tx, lo.Must(depositAndCall.AsBody()))
+ depositAndCall2 = lo.Must(parsedTX2.DepositAndCall())
+ )
+
+ assert.Equal(t, depositAndCall, depositAndCall2)
})
t.Run("Irrelevant tx", func(t *testing.T) {
@@ -206,6 +241,27 @@ func TestFixtures(t *testing.T) {
require.Equal(t, "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf", tx.Hash().Hex())
}
+func TestSnakeData(t *testing.T) {
+ for _, tt := range []string{
+ "Hello world",
+ "123",
+ strings.Repeat(`ZetaChain `, 300),
+ string(readFixtureFile(t, "testdata/long-call-data.txt")),
+ } {
+ a := []byte(tt)
+
+ cell, err := marshalSnakeData(a)
+ require.NoError(t, err)
+
+ b, err := unmarshalSnakeCell(cell)
+ require.NoError(t, err)
+
+ t.Logf(string(b))
+
+ assert.Equal(t, a, b, tt)
+ }
+}
+
//go:embed testdata
var fixtures embed.FS
From feda30f5c6ff27e7bb4d9d2949f7aea6691a3b39 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 27 Sep 2024 20:21:38 +0200
Subject: [PATCH 13/36] Add IsTONChain()
---
pkg/chains/chain.go | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/pkg/chains/chain.go b/pkg/chains/chain.go
index bfd7398625..19ac59830d 100644
--- a/pkg/chains/chain.go
+++ b/pkg/chains/chain.go
@@ -93,6 +93,10 @@ func (chain Chain) IsBitcoinChain() bool {
return chain.Consensus == Consensus_bitcoin
}
+func (chain Chain) IsTONChain() bool {
+ return IsTONChain(chain.ChainId, []Chain{chain})
+}
+
// DecodeAddressFromChainID decode the address string to bytes
// additionalChains is a list of additional chains to search from
// in practice, it is used in the protocol to dynamically support new chains without doing an upgrade
@@ -132,6 +136,11 @@ func IsSolanaChain(chainID int64, additionalChains []Chain) bool {
return ChainIDInChainList(chainID, ChainListByNetwork(Network_solana, additionalChains))
}
+// IsTONChain returns true is the chain is TON chain
+func IsTONChain(chainID int64, additionalChains []Chain) bool {
+ return ChainIDInChainList(chainID, ChainListByNetwork(Network_ton, additionalChains))
+}
+
// IsEthereumChain returns true if the chain is an Ethereum chain
// additionalChains is a list of additional chains to search from
// in practice, it is used in the protocol to dynamically support new chains without doing an upgrade
From 2be832f581f34da9b90a97a928e446ecc6de6abe Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 27 Sep 2024 20:22:00 +0200
Subject: [PATCH 14/36] Refactor Gateway package
---
pkg/contracts/ton/gateway.go | 115 ++++--------------------------
pkg/contracts/ton/gateway_op.go | 2 +-
pkg/contracts/ton/gateway_test.go | 4 +-
pkg/contracts/ton/tlb.go | 79 ++++++++++++++++++++
4 files changed, 97 insertions(+), 103 deletions(-)
create mode 100644 pkg/contracts/ton/tlb.go
diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go
index bdd2c433e3..201e8abb69 100644
--- a/pkg/contracts/ton/gateway.go
+++ b/pkg/contracts/ton/gateway.go
@@ -1,10 +1,7 @@
package ton
import (
- "bytes"
-
"cosmossdk.io/math"
- eth "github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/tonkeeper/tongo/boc"
"github.com/tonkeeper/tongo/tlb"
@@ -123,7 +120,7 @@ func (gw *Gateway) parseInbound(tx ton.Transaction) (*Transaction, error) {
switch opCode {
case OpDonate:
amount := intMsgInfo.Value.Grams - tx.TotalFees.Grams
- content = Donation{Sender: sender, Amount: gramToUint(amount)}
+ content = Donation{Sender: sender, Amount: GramsToUint(amount)}
case OpDeposit:
content, errContent = parseDeposit(tx, sender, body)
case OpDepositAndCall:
@@ -151,7 +148,7 @@ func parseDeposit(tx ton.Transaction, sender ton.AccountID, body *boc.Cell) (Dep
return Deposit{}, err
}
- recipient, err := readEVMAddress(body)
+ recipient, err := UnmarshalEVMAddress(body)
if err != nil {
return Deposit{}, errors.Wrap(err, "unable to read recipient")
}
@@ -187,26 +184,26 @@ func parseDepositLog(tx ton.Transaction) (depositLog, error) {
// .store_uint(evm_recipient, size::evm_address)
// .end_cell();
- body, err := marshalCellRef(messages[0].Value.Body)
- if err != nil {
- return depositLog{}, errors.Wrap(err, "unable to read body cell")
- }
+ var (
+ bodyValue = boc.Cell(messages[0].Value.Body.Value)
+ body = &bodyValue
+ )
if err := body.Skip(sizeOpCode + sizeQueryID); err != nil {
return depositLog{}, errors.Wrap(err, "unable to skip bits")
}
// skip msg address (ton sender)
- if err := unmarshalTLB(&tlb.MsgAddress{}, body); err != nil {
+ if err := UnmarshalTLB(&tlb.MsgAddress{}, body); err != nil {
return depositLog{}, errors.Wrap(err, "unable to read sender address")
}
var deposited tlb.Grams
- if err := unmarshalTLB(&deposited, body); err != nil {
+ if err := UnmarshalTLB(&deposited, body); err != nil {
return depositLog{}, errors.Wrap(err, "unable to read deposited amount")
}
- return depositLog{Amount: gramToUint(deposited)}, nil
+ return depositLog{Amount: GramsToUint(deposited)}, nil
}
func parseDepositAndCall(tx ton.Transaction, sender ton.AccountID, body *boc.Cell) (DepositAndCall, error) {
@@ -220,7 +217,7 @@ func parseDepositAndCall(tx ton.Transaction, sender ton.AccountID, body *boc.Cel
return DepositAndCall{}, errors.Wrap(err, "unable to read call data cell")
}
- callData, err := unmarshalSnakeCell(callDataCell)
+ callData, err := UnmarshalSnakeCell(callDataCell)
if err != nil {
return DepositAndCall{}, errors.Wrap(err, "unable to unmarshal call data")
}
@@ -237,92 +234,10 @@ func parseInternalMessageBody(tx ton.Transaction) (*boc.Cell, error) {
return nil, errors.Wrap(ErrParse, "tx should have an internal message")
}
- either := tx.Msgs.InMsg.Value.Value.Body
- if either.IsRight {
- return nil, errors.Wrap(ErrParse, "tx body should not be a Ref")
- }
-
- body, err := marshalCell(&either.Value)
- if err != nil {
- return nil, errors.Wrap(err, "unable to read body cell")
- }
-
- return body, nil
-}
-
-func ErrCollect(errs ...error) error {
- for i, err := range errs {
- if err != nil {
- return errors.Wrapf(err, "error at index %d", i)
- }
- }
-
- return nil
-}
-
-func marshalCell(v tlb.MarshalerTLB) (*boc.Cell, error) {
- cell := boc.NewCell()
-
- if err := v.MarshalTLB(cell, &tlb.Encoder{}); err != nil {
- return nil, err
- }
-
- return cell, nil
-}
-
-func marshalCellRef(v tlb.MarshalerTLB) (*boc.Cell, error) {
- c, err := marshalCell(v)
- if err != nil {
- return nil, err
- }
-
- c, err = c.NextRef()
- if err != nil {
- return nil, errors.Wrap(err, "unable to create ref cell")
- }
-
- return c, nil
-}
-
-func unmarshalTLB(t tlb.UnmarshalerTLB, cell *boc.Cell) error {
- return t.UnmarshalTLB(cell, &tlb.Decoder{})
-}
-
-func unmarshalSnakeCell(cell *boc.Cell) ([]byte, error) {
- var sd tlb.SnakeData
-
- if err := unmarshalTLB(&sd, cell); err != nil {
- return nil, err
- }
-
- cd := boc.BitString(sd)
-
- // TLB operates with bits, so we (might) need to trim some "leftovers" (null chars)
- return bytes.Trim(cd.Buffer(), "\x00"), nil
-}
-
-func marshalSnakeData(data []byte) (*boc.Cell, error) {
- b := boc.NewCell()
-
- wrapped := tlb.Bytes(data)
- if err := wrapped.MarshalTLB(b, &tlb.Encoder{}); err != nil {
- return nil, err
- }
-
- return b, nil
-}
-
-func gramToUint(g tlb.Grams) math.Uint {
- return math.NewUint(uint64(g))
-}
-
-func readEVMAddress(cell *boc.Cell) (eth.Address, error) {
- const evmAddrBits = 20 * 8
-
- s, err := cell.ReadBits(evmAddrBits)
- if err != nil {
- return eth.Address{}, err
- }
+ var (
+ inMsg = tx.Msgs.InMsg.Value.Value
+ body = boc.Cell(inMsg.Body.Value)
+ )
- return eth.BytesToAddress(s.Buffer()), nil
+ return &body, nil
}
diff --git a/pkg/contracts/ton/gateway_op.go b/pkg/contracts/ton/gateway_op.go
index f476d49623..962ebd7d53 100644
--- a/pkg/contracts/ton/gateway_op.go
+++ b/pkg/contracts/ton/gateway_op.go
@@ -86,7 +86,7 @@ func (d DepositAndCall) Memo() []byte {
// AsBody casts struct to internal message body.
func (d DepositAndCall) AsBody() (*boc.Cell, error) {
- callDataCell, err := marshalSnakeData(d.CallData)
+ callDataCell, err := MarshalSnakeCell(d.CallData)
if err != nil {
return nil, err
}
diff --git a/pkg/contracts/ton/gateway_test.go b/pkg/contracts/ton/gateway_test.go
index 6a300ae637..dc680761ff 100644
--- a/pkg/contracts/ton/gateway_test.go
+++ b/pkg/contracts/ton/gateway_test.go
@@ -250,10 +250,10 @@ func TestSnakeData(t *testing.T) {
} {
a := []byte(tt)
- cell, err := marshalSnakeData(a)
+ cell, err := MarshalSnakeCell(a)
require.NoError(t, err)
- b, err := unmarshalSnakeCell(cell)
+ b, err := UnmarshalSnakeCell(cell)
require.NoError(t, err)
t.Logf(string(b))
diff --git a/pkg/contracts/ton/tlb.go b/pkg/contracts/ton/tlb.go
new file mode 100644
index 0000000000..0ee3aea98a
--- /dev/null
+++ b/pkg/contracts/ton/tlb.go
@@ -0,0 +1,79 @@
+package ton
+
+import (
+ "bytes"
+
+ "cosmossdk.io/math"
+ eth "github.com/ethereum/go-ethereum/common"
+ "github.com/pkg/errors"
+ "github.com/tonkeeper/tongo/boc"
+ "github.com/tonkeeper/tongo/tlb"
+)
+
+// MarshalTLB encodes entity to BOC
+func MarshalTLB(v tlb.MarshalerTLB) (*boc.Cell, error) {
+ cell := boc.NewCell()
+
+ if err := v.MarshalTLB(cell, &tlb.Encoder{}); err != nil {
+ return nil, err
+ }
+
+ return cell, nil
+}
+
+// UnmarshalTLB decodes entity from BOC
+func UnmarshalTLB(t tlb.UnmarshalerTLB, cell *boc.Cell) error {
+ return t.UnmarshalTLB(cell, &tlb.Decoder{})
+}
+
+// UnmarshalSnakeCell decodes TLB cell to []byte using snake-cell encoding
+func UnmarshalSnakeCell(cell *boc.Cell) ([]byte, error) {
+ var sd tlb.SnakeData
+
+ if err := UnmarshalTLB(&sd, cell); err != nil {
+ return nil, err
+ }
+
+ cd := boc.BitString(sd)
+
+ // TLB operates with bits, so we (might) need to trim some "leftovers" (null chars)
+ return bytes.Trim(cd.Buffer(), "\x00"), nil
+}
+
+// MarshalSnakeCell encodes []byte to TLB using snake-cell encoding
+func MarshalSnakeCell(data []byte) (*boc.Cell, error) {
+ b := boc.NewCell()
+
+ wrapped := tlb.Bytes(data)
+ if err := wrapped.MarshalTLB(b, &tlb.Encoder{}); err != nil {
+ return nil, err
+ }
+
+ return b, nil
+}
+
+// UnmarshalEVMAddress decodes eth.Address from BOC
+func UnmarshalEVMAddress(cell *boc.Cell) (eth.Address, error) {
+ const evmAddrBits = 20 * 8
+
+ s, err := cell.ReadBits(evmAddrBits)
+ if err != nil {
+ return eth.Address{}, err
+ }
+
+ return eth.BytesToAddress(s.Buffer()), nil
+}
+
+func GramsToUint(g tlb.Grams) math.Uint {
+ return math.NewUint(uint64(g))
+}
+
+func ErrCollect(errs ...error) error {
+ for i, err := range errs {
+ if err != nil {
+ return errors.Wrapf(err, "error at index %d", i)
+ }
+ }
+
+ return nil
+}
From 0688c47feb0ba1a0e57de83d7f1773805a309de0 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 27 Sep 2024 20:22:30 +0200
Subject: [PATCH 15/36] Implement samples for TON
---
testutil/sample/sample_ton.go | 244 +++++++++++++++++++++++++++++
testutil/sample/sample_ton_test.go | 94 +++++++++++
2 files changed, 338 insertions(+)
create mode 100644 testutil/sample/sample_ton.go
create mode 100644 testutil/sample/sample_ton_test.go
diff --git a/testutil/sample/sample_ton.go b/testutil/sample/sample_ton.go
new file mode 100644
index 0000000000..7d55b2a48a
--- /dev/null
+++ b/testutil/sample/sample_ton.go
@@ -0,0 +1,244 @@
+package sample
+
+import (
+ "crypto/rand"
+ "testing"
+ "time"
+
+ "cosmossdk.io/math"
+ eth "github.com/ethereum/go-ethereum/common"
+ "github.com/stretchr/testify/require"
+ "github.com/tonkeeper/tongo/boc"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/ton"
+
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
+)
+
+const (
+ tonWorkchainID = 0
+ tonShardID = 123
+ tonDepositFee = 10_000_000 // 0.01 TON
+ tonSampleGasUsage = 50_000_000 // 0.05 TON
+)
+
+type TONTransactionProps struct {
+ Account ton.AccountID
+ GasUsed uint64
+ BlockID ton.BlockIDExt
+
+ // For simplicity let's have only one input
+ // and one output (both optional)
+ Input *tlb.Message
+ Output *tlb.Message
+}
+
+type intMsgInfo struct {
+ IhrDisabled bool
+ Bounce bool
+ Bounced bool
+ Src tlb.MsgAddress
+ Dest tlb.MsgAddress
+ Value tlb.CurrencyCollection
+ IhrFee tlb.Grams
+ FwdFee tlb.Grams
+ CreatedLt uint64
+ CreatedAt uint32
+}
+
+func TONDonateProps(t *testing.T, acc ton.AccountID, d toncontracts.Donation) TONTransactionProps {
+ body, err := d.AsBody()
+ require.NoError(t, err)
+
+ deposited := tonSampleGasUsage + d.Amount.Uint64()
+
+ return TONTransactionProps{
+ Account: acc,
+ Input: &tlb.Message{
+ Info: internalMessageInfo(&intMsgInfo{
+ Bounce: true,
+ Src: d.Sender.ToMsgAddress(),
+ Dest: acc.ToMsgAddress(),
+ Value: tlb.CurrencyCollection{Grams: tlb.Grams(deposited)},
+ }),
+ Body: tlb.EitherRef[tlb.Any]{Value: tlb.Any(*body)},
+ },
+ }
+}
+
+func TONDepositProps(t *testing.T, acc ton.AccountID, d toncontracts.Deposit) TONTransactionProps {
+ body, err := d.AsBody()
+ require.NoError(t, err)
+
+ logBody := depositLogMock(t, d.Sender, d.Amount.Uint64(), d.Recipient, nil)
+
+ return TONTransactionProps{
+ Account: acc,
+ Input: &tlb.Message{
+ Info: internalMessageInfo(&intMsgInfo{
+ Bounce: true,
+ Src: d.Sender.ToMsgAddress(),
+ Dest: acc.ToMsgAddress(),
+ Value: tlb.CurrencyCollection{Grams: fakeDepositAmount(d.Amount)},
+ }),
+ Body: tlb.EitherRef[tlb.Any]{Value: tlb.Any(*body)},
+ },
+ Output: &tlb.Message{
+ Body: tlb.EitherRef[tlb.Any]{IsRight: true, Value: tlb.Any(*logBody)},
+ },
+ }
+}
+
+func TONDepositAndCallProps(t *testing.T, acc ton.AccountID, d toncontracts.DepositAndCall) TONTransactionProps {
+ body, err := d.AsBody()
+ require.NoError(t, err)
+
+ logBody := depositLogMock(t, d.Sender, d.Amount.Uint64(), d.Recipient, d.CallData)
+
+ return TONTransactionProps{
+ Account: acc,
+ Input: &tlb.Message{
+ Info: internalMessageInfo(&intMsgInfo{
+ Bounce: true,
+ Src: d.Sender.ToMsgAddress(),
+ Dest: acc.ToMsgAddress(),
+ Value: tlb.CurrencyCollection{Grams: fakeDepositAmount(d.Amount)},
+ }),
+ Body: tlb.EitherRef[tlb.Any]{Value: tlb.Any(*body)},
+ },
+ Output: &tlb.Message{
+ Body: tlb.EitherRef[tlb.Any]{IsRight: true, Value: tlb.Any(*logBody)},
+ },
+ }
+}
+
+// TONTransaction creates a sample TON transaction.
+func TONTransaction(t *testing.T, p TONTransactionProps) ton.Transaction {
+ require.False(t, p.Account.IsZero(), "account address is empty")
+ require.False(t, p.Input == nil && p.Output == nil, "both input and output are empty")
+
+ now := time.Now().UTC()
+
+ if p.GasUsed == 0 {
+ p.GasUsed = tonSampleGasUsage
+ }
+
+ if p.BlockID.BlockID.Seqno == 0 {
+ p.BlockID = tonBlockID(now)
+ }
+
+ // Simulate logical time as `2 * now()`
+ lt := uint64(2 * now.Unix())
+
+ input := tlb.Maybe[tlb.Ref[tlb.Message]]{}
+ if p.Input != nil {
+ input.Exists = true
+ input.Value.Value = *p.Input
+ }
+
+ var outputs tlb.HashmapE[tlb.Uint15, tlb.Ref[tlb.Message]]
+ if p.Output != nil {
+ outputs = tlb.NewHashmapE(
+ []tlb.Uint15{0},
+ []tlb.Ref[tlb.Message]{{*p.Output}},
+ )
+ }
+
+ type messages struct {
+ InMsg tlb.Maybe[tlb.Ref[tlb.Message]]
+ OutMsgs tlb.HashmapE[tlb.Uint15, tlb.Ref[tlb.Message]]
+ }
+
+ return ton.Transaction{
+ BlockID: p.BlockID,
+ Transaction: tlb.Transaction{
+ AccountAddr: p.Account.Address,
+ Lt: lt,
+ Now: uint32(now.Unix()),
+ OutMsgCnt: tlb.Uint15(len(outputs.Keys())),
+ TotalFees: tlb.CurrencyCollection{Grams: tlb.Grams(p.GasUsed)},
+ Msgs: messages{InMsg: input, OutMsgs: outputs},
+ },
+ }
+}
+
+func GenerateTONAccountID() ton.AccountID {
+ var addr [32]byte
+
+ //nolint:errcheck // test code
+ rand.Read(addr[:])
+
+ return *ton.NewAccountID(0, addr)
+}
+
+func internalMessageInfo(info *intMsgInfo) tlb.CommonMsgInfo {
+ return tlb.CommonMsgInfo{
+ SumType: "IntMsgInfo",
+ IntMsgInfo: (*struct {
+ IhrDisabled bool
+ Bounce bool
+ Bounced bool
+ Src tlb.MsgAddress
+ Dest tlb.MsgAddress
+ Value tlb.CurrencyCollection
+ IhrFee tlb.Grams
+ FwdFee tlb.Grams
+ CreatedLt uint64
+ CreatedAt uint32
+ })(info),
+ }
+}
+
+func tonBlockID(now time.Time) ton.BlockIDExt {
+ // simulate shard seqno as unix timestamp
+ seqno := uint32(now.Unix())
+
+ return ton.BlockIDExt{
+ BlockID: ton.BlockID{
+ Workchain: tonWorkchainID,
+ Shard: tonShardID,
+ Seqno: seqno,
+ },
+ }
+}
+
+func fakeDepositAmount(v math.Uint) tlb.Grams {
+ return tlb.Grams(v.Uint64() + tonDepositFee)
+}
+
+func depositLogMock(
+ t *testing.T,
+ sender ton.AccountID,
+ amount uint64,
+ recipient eth.Address,
+ callData []byte,
+) *boc.Cell {
+ // cell log = begin_cell()
+ // .store_uint(op::internal::deposit_and_call, size::op_code_size)
+ // .store_uint(0, size::query_id_size)
+ // .store_slice(sender)
+ // .store_coins(deposit_amount)
+ // .store_uint(evm_recipient, size::evm_address)
+ // .store_ref(call_data) // only for DepositAndCall
+ // .end_cell();
+
+ b := boc.NewCell()
+ require.NoError(t, b.WriteUint(0, 32+64))
+
+ // skip
+ msgAddr := sender.ToMsgAddress()
+ require.NoError(t, tlb.Marshal(b, msgAddr))
+
+ coins := tlb.Grams(amount)
+ require.NoError(t, coins.MarshalTLB(b, nil))
+
+ require.NoError(t, b.WriteBytes(recipient.Bytes()))
+
+ if callData != nil {
+ callDataCell, err := toncontracts.MarshalSnakeCell(callData)
+ require.NoError(t, err)
+ require.NoError(t, b.AddRef(callDataCell))
+ }
+
+ return b
+}
diff --git a/testutil/sample/sample_ton_test.go b/testutil/sample/sample_ton_test.go
new file mode 100644
index 0000000000..73474db0d4
--- /dev/null
+++ b/testutil/sample/sample_ton_test.go
@@ -0,0 +1,94 @@
+package sample
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/stretchr/testify/require"
+ "github.com/tonkeeper/tongo/ton"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
+)
+
+func TestTONSamples(t *testing.T) {
+ var (
+ gatewayID = ton.MustParseAccountID("0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b")
+ gw = toncontracts.NewGateway(gatewayID)
+ )
+
+ t.Run("Donate", func(t *testing.T) {
+ // ARRANGE
+ d := toncontracts.Donation{
+ Sender: GenerateTONAccountID(),
+ Amount: sdkmath.NewUint(100_000_000),
+ }
+
+ tx := TONTransaction(t, TONDonateProps(t, gatewayID, d))
+
+ // ACT
+ parsedTX, err := gw.ParseTransaction(tx)
+
+ // ASSERT
+ require.NoError(t, err)
+
+ d2, err := parsedTX.Donation()
+ require.NoError(t, err)
+
+ require.Equal(t, int(d.Amount.Uint64()), int(d2.Amount.Uint64()))
+ require.Equal(t, d.Sender.ToRaw(), d2.Sender.ToRaw())
+ })
+
+ t.Run("Deposit", func(t *testing.T) {
+ // ARRANGE
+ d := toncontracts.Deposit{
+ Sender: GenerateTONAccountID(),
+ Amount: sdkmath.NewUint(200_000_000),
+ Recipient: EthAddress(),
+ }
+
+ tx := TONTransaction(t, TONDepositProps(t, gatewayID, d))
+
+ // ACT
+ parsedTX, err := gw.ParseTransaction(tx)
+
+ // ASSERT
+ require.NoError(t, err)
+
+ d2, err := parsedTX.Deposit()
+ require.NoError(t, err)
+
+ require.Equal(t, int(d.Amount.Uint64()), int(d2.Amount.Uint64()))
+ require.Equal(t, d.Sender.ToRaw(), d2.Sender.ToRaw())
+ require.Equal(t, d.Recipient.Hex(), d2.Recipient.Hex())
+ require.Equal(t, d.Memo(), d2.Memo())
+ })
+
+ t.Run("Deposit and call", func(t *testing.T) {
+ // ARRANGE
+ d := toncontracts.DepositAndCall{
+ Deposit: toncontracts.Deposit{
+ Sender: GenerateTONAccountID(),
+ Amount: sdkmath.NewUint(300_000_000),
+ Recipient: EthAddress(),
+ },
+ CallData: []byte("Evidently, the most known and used kind of dictionaries in TON is hashmap."),
+ }
+
+ tx := TONTransaction(t, TONDepositAndCallProps(t, gatewayID, d))
+
+ // ACT
+ parsedTX, err := gw.ParseTransaction(tx)
+
+ // ASSERT
+ require.NoError(t, err)
+
+ d2, err := parsedTX.DepositAndCall()
+ require.NoError(t, err)
+
+ require.Equal(t, int(d.Amount.Uint64()), int(d2.Amount.Uint64()))
+ require.Equal(t, d.Sender.ToRaw(), d2.Sender.ToRaw())
+ require.Equal(t, d.Recipient.Hex(), d2.Recipient.Hex())
+ require.Equal(t, d.CallData, d2.CallData)
+ require.Equal(t, d.Memo(), d2.Memo())
+ })
+
+}
From 1fd2cfefe6699ce8c00268a4fd4eac055a2c3600 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 27 Sep 2024 20:23:08 +0200
Subject: [PATCH 16/36] Add liteClient mocks
---
zetaclient/chains/ton/liteapi/client.go | 2 +-
.../chains/ton/liteapi/client_live_test.go | 2 +-
zetaclient/chains/ton/observer/inbound.go | 4 +-
zetaclient/chains/ton/observer/observer.go | 26 +++-
zetaclient/testutils/mocks/ton_liteclient.go | 127 ++++++++++++++++++
5 files changed, 154 insertions(+), 7 deletions(-)
create mode 100644 zetaclient/testutils/mocks/ton_liteclient.go
diff --git a/zetaclient/chains/ton/liteapi/client.go b/zetaclient/chains/ton/liteapi/client.go
index bbe94147fd..03a659dd4f 100644
--- a/zetaclient/chains/ton/liteapi/client.go
+++ b/zetaclient/chains/ton/liteapi/client.go
@@ -13,7 +13,7 @@ import (
"github.com/tonkeeper/tongo/ton"
)
-// Client extends tongo's liteapi.Client with some high-level tools
+// Client extends liteapi.Client with some high-level tools
// Reference: https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl
type Client struct {
*liteapi.Client
diff --git a/zetaclient/chains/ton/liteapi/client_live_test.go b/zetaclient/chains/ton/liteapi/client_live_test.go
index d090771ed9..40c37dce71 100644
--- a/zetaclient/chains/ton/liteapi/client_live_test.go
+++ b/zetaclient/chains/ton/liteapi/client_live_test.go
@@ -19,7 +19,7 @@ import (
func TestClient(t *testing.T) {
if !common.LiveTestEnabled() {
- //t.Skip("Live tests are disabled")
+ t.Skip("Live tests are disabled")
}
var (
diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go
index 2d0950bd89..f2ab2af77c 100644
--- a/zetaclient/chains/ton/observer/inbound.go
+++ b/zetaclient/chains/ton/observer/inbound.go
@@ -43,7 +43,7 @@ func (ob *Observer) watchInbound(ctx context.Context) error {
return nil
}
- if err := ob.observeInbound(ctx, app); err != nil {
+ if err := ob.observeInbound(ctx); err != nil {
ob.Logger().Inbound.Err(err).Msg("WatchInbound: observeInbound error")
}
@@ -62,7 +62,7 @@ func (ob *Observer) watchInbound(ctx context.Context) error {
)
}
-func (ob *Observer) observeInbound(ctx context.Context, _ *zctx.AppContext) error {
+func (ob *Observer) observeInbound(ctx context.Context) error {
if err := ob.ensureLastScannedTX(ctx); err != nil {
return errors.Wrap(err, "unable to ensure last scanned tx")
}
diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go
index 5989293795..ee638ed61d 100644
--- a/zetaclient/chains/ton/observer/observer.go
+++ b/zetaclient/chains/ton/observer/observer.go
@@ -4,24 +4,44 @@ import (
"context"
"errors"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/ton"
+
"github.com/zeta-chain/node/pkg/bg"
toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
"github.com/zeta-chain/node/x/crosschain/types"
"github.com/zeta-chain/node/zetaclient/chains/base"
"github.com/zeta-chain/node/zetaclient/chains/interfaces"
- "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi"
)
type Observer struct {
base.Observer
- client *liteapi.Client
+ client LiteClient
gateway *toncontracts.Gateway
}
+// LiteClient represents a TON client
+//
+//go:generate mockery --name LiteClient --filename ton_liteclient.go --case underscore --output ../../../testutils/mocks
+type LiteClient interface {
+ GetBlockHeader(ctx context.Context, acc ton.BlockIDExt, mode int) (tlb.BlockInfo, error)
+ GetTransactionsUntil(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error)
+ GetFirstTransaction(ctx context.Context, id ton.AccountID) (*ton.Transaction, int, error)
+}
+
var _ interfaces.ChainObserver = (*Observer)(nil)
-func New(bo *base.Observer, client *liteapi.Client, gateway *toncontracts.Gateway) (*Observer, error) {
+func New(bo *base.Observer, client LiteClient, gateway *toncontracts.Gateway) (*Observer, error) {
+ switch {
+ case !bo.Chain().IsTONChain():
+ return nil, errors.New("base observer chain is not TON")
+ case client == nil:
+ return nil, errors.New("liteapi client is nil")
+ case gateway == nil:
+ return nil, errors.New("gateway is nil")
+ }
+
bo.LoadLastTxScanned()
return &Observer{
diff --git a/zetaclient/testutils/mocks/ton_liteclient.go b/zetaclient/testutils/mocks/ton_liteclient.go
new file mode 100644
index 0000000000..6074fc5949
--- /dev/null
+++ b/zetaclient/testutils/mocks/ton_liteclient.go
@@ -0,0 +1,127 @@
+// Code generated by mockery v2.43.2. DO NOT EDIT.
+
+package mocks
+
+import (
+ context "context"
+
+ mock "github.com/stretchr/testify/mock"
+
+ tlb "github.com/tonkeeper/tongo/tlb"
+
+ ton "github.com/tonkeeper/tongo/ton"
+)
+
+// LiteClient is an autogenerated mock type for the LiteClient type
+type LiteClient struct {
+ mock.Mock
+}
+
+// GetBlockHeader provides a mock function with given fields: ctx, acc, mode
+func (_m *LiteClient) GetBlockHeader(ctx context.Context, acc ton.BlockIDExt, mode int) (tlb.BlockInfo, error) {
+ ret := _m.Called(ctx, acc, mode)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetBlockHeader")
+ }
+
+ var r0 tlb.BlockInfo
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, ton.BlockIDExt, int) (tlb.BlockInfo, error)); ok {
+ return rf(ctx, acc, mode)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, ton.BlockIDExt, int) tlb.BlockInfo); ok {
+ r0 = rf(ctx, acc, mode)
+ } else {
+ r0 = ret.Get(0).(tlb.BlockInfo)
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, ton.BlockIDExt, int) error); ok {
+ r1 = rf(ctx, acc, mode)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// GetFirstTransaction provides a mock function with given fields: ctx, id
+func (_m *LiteClient) GetFirstTransaction(ctx context.Context, id ton.AccountID) (*ton.Transaction, int, error) {
+ ret := _m.Called(ctx, id)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetFirstTransaction")
+ }
+
+ var r0 *ton.Transaction
+ var r1 int
+ var r2 error
+ if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID) (*ton.Transaction, int, error)); ok {
+ return rf(ctx, id)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID) *ton.Transaction); ok {
+ r0 = rf(ctx, id)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*ton.Transaction)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, ton.AccountID) int); ok {
+ r1 = rf(ctx, id)
+ } else {
+ r1 = ret.Get(1).(int)
+ }
+
+ if rf, ok := ret.Get(2).(func(context.Context, ton.AccountID) error); ok {
+ r2 = rf(ctx, id)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// GetTransactionsUntil provides a mock function with given fields: ctx, acc, lt, bits
+func (_m *LiteClient) GetTransactionsUntil(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error) {
+ ret := _m.Called(ctx, acc, lt, bits)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetTransactionsUntil")
+ }
+
+ var r0 []ton.Transaction
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID, uint64, ton.Bits256) ([]ton.Transaction, error)); ok {
+ return rf(ctx, acc, lt, bits)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, ton.AccountID, uint64, ton.Bits256) []ton.Transaction); ok {
+ r0 = rf(ctx, acc, lt, bits)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]ton.Transaction)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, ton.AccountID, uint64, ton.Bits256) error); ok {
+ r1 = rf(ctx, acc, lt, bits)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// NewLiteClient creates a new instance of LiteClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewLiteClient(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *LiteClient {
+ mock := &LiteClient{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
From 6619394c0e9a04df8f162526030324f17910ff52 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Sun, 29 Sep 2024 17:23:28 +0200
Subject: [PATCH 17/36] Add unit tests for inbound TON observer
---
testutil/sample/sample_ton.go | 30 +-
.../chains/ton/observer/inbound_test.go | 317 ++++++++++++++++++
zetaclient/chains/ton/observer/observer.go | 14 +-
.../chains/ton/observer/observer_test.go | 170 ++++++++++
4 files changed, 524 insertions(+), 7 deletions(-)
create mode 100644 zetaclient/chains/ton/observer/inbound_test.go
create mode 100644 zetaclient/chains/ton/observer/observer_test.go
diff --git a/testutil/sample/sample_ton.go b/testutil/sample/sample_ton.go
index 7d55b2a48a..01ce8724c5 100644
--- a/testutil/sample/sample_ton.go
+++ b/testutil/sample/sample_ton.go
@@ -2,8 +2,10 @@ package sample
import (
"crypto/rand"
+ "reflect"
"testing"
"time"
+ "unsafe"
"cosmossdk.io/math"
eth "github.com/ethereum/go-ethereum/common"
@@ -46,6 +48,10 @@ type intMsgInfo struct {
CreatedAt uint32
}
+func TONDonation(t *testing.T, acc ton.AccountID, d toncontracts.Donation) ton.Transaction {
+ return TONTransaction(t, TONDonateProps(t, acc, d))
+}
+
func TONDonateProps(t *testing.T, acc ton.AccountID, d toncontracts.Donation) TONTransactionProps {
body, err := d.AsBody()
require.NoError(t, err)
@@ -66,6 +72,10 @@ func TONDonateProps(t *testing.T, acc ton.AccountID, d toncontracts.Donation) TO
}
}
+func TONDeposit(t *testing.T, acc ton.AccountID, d toncontracts.Deposit) ton.Transaction {
+ return TONTransaction(t, TONDepositProps(t, acc, d))
+}
+
func TONDepositProps(t *testing.T, acc ton.AccountID, d toncontracts.Deposit) TONTransactionProps {
body, err := d.AsBody()
require.NoError(t, err)
@@ -89,6 +99,10 @@ func TONDepositProps(t *testing.T, acc ton.AccountID, d toncontracts.Deposit) TO
}
}
+func TONDepositAndCall(t *testing.T, acc ton.AccountID, d toncontracts.DepositAndCall) ton.Transaction {
+ return TONTransaction(t, TONDepositAndCallProps(t, acc, d))
+}
+
func TONDepositAndCallProps(t *testing.T, acc ton.AccountID, d toncontracts.DepositAndCall) TONTransactionProps {
body, err := d.AsBody()
require.NoError(t, err)
@@ -149,7 +163,7 @@ func TONTransaction(t *testing.T, p TONTransactionProps) ton.Transaction {
OutMsgs tlb.HashmapE[tlb.Uint15, tlb.Ref[tlb.Message]]
}
- return ton.Transaction{
+ tx := ton.Transaction{
BlockID: p.BlockID,
Transaction: tlb.Transaction{
AccountAddr: p.Account.Address,
@@ -160,6 +174,10 @@ func TONTransaction(t *testing.T, p TONTransactionProps) ton.Transaction {
Msgs: messages{InMsg: input, OutMsgs: outputs},
},
}
+
+ setTXHash(&tx.Transaction, Hash())
+
+ return tx
}
func GenerateTONAccountID() ton.AccountID {
@@ -242,3 +260,13 @@ func depositLogMock(
return b
}
+
+// well, tlb.Transaction has unexported field `hash` that we need to set OUTSIDE tlb package.
+// It's a hack, but it works for testing purposes.
+func setTXHash(tx *tlb.Transaction, hash [32]byte) {
+ field := reflect.ValueOf(tx).Elem().FieldByName("hash")
+ ptr := unsafe.Pointer(field.UnsafeAddr())
+
+ arrPtr := (*[32]byte)(ptr)
+ *arrPtr = hash
+}
diff --git a/zetaclient/chains/ton/observer/inbound_test.go b/zetaclient/chains/ton/observer/inbound_test.go
new file mode 100644
index 0000000000..96f4b93d37
--- /dev/null
+++ b/zetaclient/chains/ton/observer/inbound_test.go
@@ -0,0 +1,317 @@
+package observer
+
+import (
+ "testing"
+
+ "github.com/pkg/errors"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/ton"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
+ "github.com/zeta-chain/node/testutil/sample"
+ "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi"
+)
+
+func TestInbound(t *testing.T) {
+ gw := toncontracts.NewGateway(
+ ton.MustParseAccountID("0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b"),
+ )
+
+ t.Run("Ensure last scanned tx", func(t *testing.T) {
+ t.Run("Unable to get first tx", func(t *testing.T) {
+ // ARRANGE
+ ts := newTestSuite(t)
+
+ // Given observer
+ ob, err := New(ts.baseObserver, ts.liteClient, gw)
+ require.NoError(t, err)
+
+ // Given mocked lite client call
+ ts.OnGetFirstTransaction(gw.AccountID(), nil, 0, errors.New("oops")).Once()
+
+ // ACT
+ // Observe inbounds once
+ err = ob.observeInbound(ts.ctx)
+
+ // ASSERT
+ assert.ErrorContains(t, err, "unable to ensure last scanned tx")
+ assert.Empty(t, ob.LastTxScanned())
+ })
+
+ t.Run("All good", func(t *testing.T) {
+ // ARRANGE
+ ts := newTestSuite(t)
+
+ // Given mocked lite client calls
+ firstTX := sample.TONDonation(t, gw.AccountID(), toncontracts.Donation{
+ Sender: sample.GenerateTONAccountID(),
+ Amount: tonCoins(t, "1"),
+ })
+
+ ts.OnGetFirstTransaction(gw.AccountID(), &firstTX, 0, nil).Once()
+ ts.OnGetTransactionsUntil(gw.AccountID(), firstTX.Lt, txHash(firstTX), nil, nil).Once()
+
+ // Given observer
+ ob, err := New(ts.baseObserver, ts.liteClient, gw)
+ require.NoError(t, err)
+
+ // ACT
+ // Observe inbounds once
+ err = ob.observeInbound(ts.ctx)
+
+ // ASSERT
+ assert.NoError(t, err)
+
+ // Check that last scanned tx is set and is valid
+ lastScanned, err := ob.ReadLastTxScannedFromDB()
+ assert.NoError(t, err)
+ assert.Equal(t, ob.LastTxScanned(), lastScanned)
+
+ lt, hash, err := liteapi.TransactionHashFromString(lastScanned)
+ assert.NoError(t, err)
+ assert.Equal(t, firstTX.Lt, lt)
+ assert.Equal(t, firstTX.Hash().Hex(), hash.Hex())
+ })
+ })
+
+ t.Run("Donation", func(t *testing.T) {
+ // ARRANGE
+ ts := newTestSuite(t)
+
+ // Given observer
+ ob, err := New(ts.baseObserver, ts.liteClient, gw)
+ require.NoError(t, err)
+
+ lastScanned := ts.SetupLastScannedTX(gw.AccountID())
+
+ // Given mocked lite client calls
+ donation := sample.TONDonation(t, gw.AccountID(), toncontracts.Donation{
+ Sender: sample.GenerateTONAccountID(),
+ Amount: tonCoins(t, "12"),
+ })
+
+ txs := []ton.Transaction{donation}
+
+ ts.
+ OnGetTransactionsUntil(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
+ Once()
+
+ // ACT
+ // Observe inbounds once
+ err = ob.observeInbound(ts.ctx)
+
+ // ASSERT
+ assert.NoError(t, err)
+
+ // nothing happened, but tx scanned
+ lt, hash, err := liteapi.TransactionHashFromString(ob.LastTxScanned())
+ assert.NoError(t, err)
+ assert.Equal(t, donation.Lt, lt)
+ assert.Equal(t, donation.Hash().Hex(), hash.Hex())
+ })
+
+ t.Run("Deposit", func(t *testing.T) {
+ // ARRANGE
+ ts := newTestSuite(t)
+
+ // Given observer
+ ob, err := New(ts.baseObserver, ts.liteClient, gw)
+ require.NoError(t, err)
+
+ lastScanned := ts.SetupLastScannedTX(gw.AccountID())
+
+ // Given mocked lite client calls
+ deposit := toncontracts.Deposit{
+ Sender: sample.GenerateTONAccountID(),
+ Amount: tonCoins(t, "12"),
+ Recipient: sample.EthAddress(),
+ }
+
+ depositTX := sample.TONDeposit(t, gw.AccountID(), deposit)
+ txs := []ton.Transaction{depositTX}
+
+ ts.
+ OnGetTransactionsUntil(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
+ Once()
+
+ ts.MockGetBlockHeader(depositTX.BlockID)
+
+ // ACT
+ // Observe inbounds once
+ err = ob.observeInbound(ts.ctx)
+
+ // ASSERT
+ assert.NoError(t, err)
+
+ // Check that cctx was sent to zetacore
+ require.Len(t, ts.votesBag, 1)
+
+ // Check CCTX
+ cctx := ts.votesBag[0]
+
+ assert.NotNil(t, cctx)
+
+ assert.Equal(t, deposit.Sender.ToRaw(), cctx.Sender)
+ assert.Equal(t, ts.chain.ChainId, cctx.SenderChainId)
+
+ assert.Equal(t, "", cctx.Asset)
+ assert.Equal(t, deposit.Amount.Uint64(), cctx.Amount.Uint64())
+ assert.Equal(t, string(deposit.Recipient.Bytes()), cctx.Message)
+
+ // Check hash & block height
+ expectedHash := liteapi.TransactionHashToString(depositTX.Lt, txHash(depositTX))
+ assert.Equal(t, expectedHash, cctx.InboundHash)
+
+ blockInfo, err := ts.liteClient.GetBlockHeader(ts.ctx, depositTX.BlockID, 0)
+ require.NoError(t, err)
+
+ assert.Equal(t, uint64(blockInfo.MinRefMcSeqno), cctx.InboundBlockHeight)
+ })
+
+ t.Run("Deposit and call", func(t *testing.T) {
+ // ARRANGE
+ ts := newTestSuite(t)
+
+ // Given observer
+ ob, err := New(ts.baseObserver, ts.liteClient, gw)
+ require.NoError(t, err)
+
+ lastScanned := ts.SetupLastScannedTX(gw.AccountID())
+
+ // Given mocked lite client calls
+ const callData = "hey there"
+ depositAndCall := toncontracts.DepositAndCall{
+ Deposit: toncontracts.Deposit{
+ Sender: sample.GenerateTONAccountID(),
+ Amount: tonCoins(t, "4"),
+ Recipient: sample.EthAddress(),
+ },
+ CallData: []byte(callData),
+ }
+
+ depositAndCallTX := sample.TONDepositAndCall(t, gw.AccountID(), depositAndCall)
+ txs := []ton.Transaction{depositAndCallTX}
+
+ ts.
+ OnGetTransactionsUntil(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
+ Once()
+
+ ts.MockGetBlockHeader(depositAndCallTX.BlockID)
+
+ // ACT
+ // Observe inbounds once
+ err = ob.observeInbound(ts.ctx)
+
+ // ASSERT
+ assert.NoError(t, err)
+
+ // Check that cctx was sent to zetacore
+ require.Len(t, ts.votesBag, 1)
+
+ // Check CCTX
+ cctx := ts.votesBag[0]
+
+ assert.NotNil(t, cctx)
+
+ assert.Equal(t, depositAndCall.Sender.ToRaw(), cctx.Sender)
+ assert.Equal(t, ts.chain.ChainId, cctx.SenderChainId)
+
+ assert.Equal(t, "", cctx.Asset)
+ assert.Equal(t, depositAndCall.Amount.Uint64(), cctx.Amount.Uint64())
+ assert.Equal(t, string(depositAndCall.Recipient.Bytes())+callData, cctx.Message)
+
+ // Check hash & block height
+ expectedHash := liteapi.TransactionHashToString(depositAndCallTX.Lt, txHash(depositAndCallTX))
+ assert.Equal(t, expectedHash, cctx.InboundHash)
+
+ blockInfo, err := ts.liteClient.GetBlockHeader(ts.ctx, depositAndCallTX.BlockID, 0)
+ require.NoError(t, err)
+
+ assert.Equal(t, uint64(blockInfo.MinRefMcSeqno), cctx.InboundBlockHeight)
+ })
+
+ t.Run("Multiple transactions", func(t *testing.T) {
+ // ARRANGE
+ ts := newTestSuite(t)
+
+ // Given observer
+ ob, err := New(ts.baseObserver, ts.liteClient, gw)
+ require.NoError(t, err)
+
+ lastScanned := ts.SetupLastScannedTX(gw.AccountID())
+
+ // Given several transactions
+ txs := []ton.Transaction{
+ // should be skipped
+ sample.TONDonation(t, gw.AccountID(), toncontracts.Donation{
+ Sender: sample.GenerateTONAccountID(),
+ Amount: tonCoins(t, "1"),
+ }),
+ // should be voted
+ sample.TONDeposit(t, gw.AccountID(), toncontracts.Deposit{
+ Sender: sample.GenerateTONAccountID(),
+ Amount: tonCoins(t, "3"),
+ Recipient: sample.EthAddress(),
+ }),
+ // should be skipped (invalid inbound message)
+ sample.TONTransaction(t, sample.TONTransactionProps{
+ Account: gw.AccountID(),
+ Input: &tlb.Message{},
+ }),
+ // should be voted
+ sample.TONDeposit(t, gw.AccountID(), toncontracts.Deposit{
+ Sender: sample.GenerateTONAccountID(),
+ Amount: tonCoins(t, "3"),
+ Recipient: sample.EthAddress(),
+ }),
+ // should be skipped (invalid inbound/outbound messages)
+ sample.TONTransaction(t, sample.TONTransactionProps{
+ Account: gw.AccountID(),
+ Input: &tlb.Message{},
+ Output: &tlb.Message{},
+ }),
+ }
+
+ ts.
+ OnGetTransactionsUntil(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
+ Once()
+
+ for _, tx := range txs {
+ ts.MockGetBlockHeader(tx.BlockID)
+ }
+
+ // ACT
+ // Observe inbounds once
+ err = ob.observeInbound(ts.ctx)
+
+ // ASSERT
+ assert.NoError(t, err)
+
+ // Check that cctx was sent to zetacore
+ assert.Equal(t, 2, len(ts.votesBag))
+
+ var (
+ hash1 = liteapi.TransactionHashToString(txs[1].Lt, txHash(txs[1]))
+ hash2 = liteapi.TransactionHashToString(txs[3].Lt, txHash(txs[3]))
+ )
+
+ assert.Equal(t, hash1, ts.votesBag[0].InboundHash)
+ assert.Equal(t, hash2, ts.votesBag[1].InboundHash)
+
+ // Check that last scanned tx points to the last tx in a list (even if it was skipped)
+ var (
+ lastTX = txs[len(txs)-1]
+ lastScannedHash = ob.LastTxScanned()
+ )
+
+ lastLT, lastHash, err := liteapi.TransactionHashFromString(lastScannedHash)
+ assert.NoError(t, err)
+ assert.Equal(t, lastTX.Lt, lastLT)
+ assert.Equal(t, lastTX.Hash().Hex(), lastHash.Hex())
+ })
+}
+
+func txHash(tx ton.Transaction) ton.Bits256 {
+ return ton.Bits256(tx.Hash())
+}
diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go
index ee638ed61d..f6792e5b1b 100644
--- a/zetaclient/chains/ton/observer/observer.go
+++ b/zetaclient/chains/ton/observer/observer.go
@@ -15,7 +15,7 @@ import (
)
type Observer struct {
- base.Observer
+ *base.Observer
client LiteClient
gateway *toncontracts.Gateway
@@ -45,7 +45,7 @@ func New(bo *base.Observer, client LiteClient, gateway *toncontracts.Gateway) (*
bo.LoadLastTxScanned()
return &Observer{
- Observer: *bo,
+ Observer: bo,
client: client,
gateway: gateway,
}, nil
@@ -66,10 +66,12 @@ func (ob *Observer) Start(ctx context.Context) {
bg.Work(ctx, ob.watchInbound, bg.WithName("WatchInbound"), bg.WithLogger(ob.Logger().Inbound))
// todo
- // watchInboundTracker
- // watchOutbound
- // watchGasPrice
- // watchRPCStatus
+ // watchInboundTracker https://github.com/zeta-chain/node/issues/2935
+
+ // todo outbounds/withdrawals https://github.com/zeta-chain/node/issues/2807
+ // watchOutbound
+ // watchGasPrice
+ // watchRPCStatus
}
func (ob *Observer) VoteOutboundIfConfirmed(_ context.Context, _ *types.CrossChainTx) (bool, error) {
diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go
new file mode 100644
index 0000000000..84e779f980
--- /dev/null
+++ b/zetaclient/chains/ton/observer/observer_test.go
@@ -0,0 +1,170 @@
+package observer
+
+import (
+ "context"
+ "strconv"
+ "testing"
+
+ "cosmossdk.io/math"
+ "github.com/rs/zerolog"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/ton"
+ "github.com/zeta-chain/node/pkg/chains"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
+ "github.com/zeta-chain/node/testutil/sample"
+ cctxtypes "github.com/zeta-chain/node/x/crosschain/types"
+ observertypes "github.com/zeta-chain/node/x/observer/types"
+ "github.com/zeta-chain/node/zetaclient/chains/base"
+ "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi"
+ "github.com/zeta-chain/node/zetaclient/db"
+ "github.com/zeta-chain/node/zetaclient/keys"
+ "github.com/zeta-chain/node/zetaclient/testutils/mocks"
+)
+
+type testSuite struct {
+ ctx context.Context
+ t *testing.T
+
+ chain chains.Chain
+ chainParams *observertypes.ChainParams
+
+ liteClient *mocks.LiteClient
+
+ zetacore *mocks.ZetacoreClient
+ tss *mocks.TSS
+ database *db.DB
+
+ baseObserver *base.Observer
+
+ votesBag []*cctxtypes.MsgVoteInbound
+}
+
+func newTestSuite(t *testing.T) *testSuite {
+ var (
+ ctx = context.Background()
+
+ chain = chains.TONTestnet
+ chainParams = sample.ChainParams(chain.ChainId)
+
+ liteClient = mocks.NewLiteClient(t)
+
+ tss = mocks.NewTSSAthens3()
+ zetacore = mocks.NewZetacoreClient(t).WithKeys(&keys.Keys{})
+
+ testLogger = zerolog.New(zerolog.NewTestWriter(t))
+ logger = base.Logger{Std: testLogger, Compliance: testLogger}
+ )
+
+ database, err := db.NewFromSqliteInMemory(true)
+ require.NoError(t, err)
+
+ baseObserver, err := base.NewObserver(
+ chain,
+ *chainParams,
+ zetacore,
+ tss,
+ 1,
+ 1,
+ 60,
+ nil,
+ database,
+ logger,
+ )
+
+ require.NoError(t, err)
+
+ ts := &testSuite{
+ ctx: ctx,
+ t: t,
+
+ chain: chain,
+ chainParams: chainParams,
+
+ liteClient: liteClient,
+
+ zetacore: zetacore,
+ tss: tss,
+ database: database,
+
+ baseObserver: baseObserver,
+ }
+
+ // Setup mocks
+ ts.zetacore.On("Chain").Return(chain)
+ setupVotesBag(ts)
+
+ return ts
+}
+
+func (ts *testSuite) SetupLastScannedTX(gw ton.AccountID) ton.Transaction {
+ lastScannedTX := sample.TONDonation(ts.t, gw, toncontracts.Donation{
+ Sender: sample.GenerateTONAccountID(),
+ Amount: tonCoins(ts.t, "1"),
+ })
+
+ txHash := liteapi.TransactionHashToString(lastScannedTX.Lt, ton.Bits256(lastScannedTX.Hash()))
+
+ ts.baseObserver.WithLastTxScanned(txHash)
+ require.NoError(ts.t, ts.baseObserver.WriteLastTxScannedToDB(txHash))
+
+ return lastScannedTX
+}
+
+func (ts *testSuite) OnGetFirstTransaction(acc ton.AccountID, tx *ton.Transaction, scanned int, err error) *mock.Call {
+ return ts.liteClient.
+ On("GetFirstTransaction", ts.ctx, acc).
+ Return(tx, scanned, err)
+}
+
+func (ts *testSuite) OnGetTransactionsUntil(
+ acc ton.AccountID,
+ lt uint64,
+ hash ton.Bits256,
+ txs []ton.Transaction,
+ err error,
+) *mock.Call {
+ return ts.liteClient.
+ On("GetTransactionsUntil", ts.ctx, acc, lt, hash).
+ Return(txs, err)
+}
+
+func (ts *testSuite) MockGetBlockHeader(id ton.BlockIDExt) *mock.Call {
+ // let's pretend that block's masterchain ref has the same seqno
+ blockInfo := tlb.BlockInfo{
+ BlockInfoPart: tlb.BlockInfoPart{MinRefMcSeqno: id.Seqno},
+ }
+
+ return ts.liteClient.On("GetBlockHeader", ts.ctx, id, 0).Return(blockInfo, nil)
+}
+
+// parses string to TON
+func tonCoins(t *testing.T, raw string) math.Uint {
+ t.Helper()
+
+ const oneTON = 1_000_000_000
+
+ f, err := strconv.ParseFloat(raw, 64)
+ require.NoError(t, err)
+
+ f *= oneTON
+
+ return math.NewUint(uint64(f))
+}
+
+func setupVotesBag(ts *testSuite) {
+ catcher := func(args mock.Arguments) {
+ vote := args.Get(3)
+ cctx, ok := vote.(*cctxtypes.MsgVoteInbound)
+ if !ok {
+ panic("unexpected cctx type")
+ }
+
+ ts.votesBag = append(ts.votesBag, cctx)
+ }
+ ts.zetacore.
+ On("PostVoteInbound", ts.ctx, mock.Anything, mock.Anything, mock.Anything).
+ Run(catcher).
+ Return("", "", nil) // zeta hash, ballot index, error
+}
From ccc1a9dafe7bec9643c0decc4051c4a9e3b9ae03 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Mon, 30 Sep 2024 13:43:19 +0200
Subject: [PATCH 18/36] Localnet: add TON ZRC20
---
e2e/runner/runner.go | 2 +
e2e/runner/setup_zeta.go | 18 +++++++++
e2e/txserver/zeta_tx_server.go | 71 +++++++++++++++++-----------------
3 files changed, 56 insertions(+), 35 deletions(-)
diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go
index e83245d01a..32fcb64b8e 100644
--- a/e2e/runner/runner.go
+++ b/e2e/runner/runner.go
@@ -127,6 +127,8 @@ type E2ERunner struct {
BTCZRC20 *zrc20.ZRC20
SOLZRC20Addr ethcommon.Address
SOLZRC20 *zrc20.ZRC20
+ TONZRC20Addr ethcommon.Address
+ TONZRC20 *zrc20.ZRC20
UniswapV2FactoryAddr ethcommon.Address
UniswapV2Factory *uniswapv2factory.UniswapV2Factory
UniswapV2RouterAddr ethcommon.Address
diff --git a/e2e/runner/setup_zeta.go b/e2e/runner/setup_zeta.go
index 0001ccb065..adb3c10ee0 100644
--- a/e2e/runner/setup_zeta.go
+++ b/e2e/runner/setup_zeta.go
@@ -191,6 +191,7 @@ func (r *E2ERunner) SetZEVMZRC20s() {
r.SetupETHZRC20()
r.SetupBTCZRC20()
r.SetupSOLZRC20()
+ r.SetupTONZRC20()
}
// SetupETHZRC20 sets up the ETH ZRC20 in the runner from the values queried from the chain
@@ -242,6 +243,23 @@ func (r *E2ERunner) SetupSOLZRC20() {
r.SOLZRC20 = SOLZRC20
}
+// SetupTONZRC20 sets up the TON ZRC20 in the runner from the values queried from the chain
+func (r *E2ERunner) SetupTONZRC20() {
+ TONZRC20Addr, err := r.SystemContract.GasCoinZRC20ByChainId(
+ &bind.CallOpts{},
+ big.NewInt(chains.TONLocalnet.ChainId),
+ )
+ require.NoError(r, err)
+
+ r.TONZRC20Addr = TONZRC20Addr
+ r.Logger.Info("TON ZRC20 address: %s", TONZRC20Addr.Hex())
+
+ TONZRC20, err := zrc20.NewZRC20(TONZRC20Addr, r.ZEVMClient)
+ require.NoError(r, err)
+
+ r.TONZRC20 = TONZRC20
+}
+
// EnableHeaderVerification enables the header verification for the given chain IDs
func (r *E2ERunner) EnableHeaderVerification(chainIDList []int64) error {
r.Logger.Print("⚙️ enabling verification flags for block headers")
diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go
index b0e6cecce2..0137cb483b 100644
--- a/e2e/txserver/zeta_tx_server.go
+++ b/e2e/txserver/zeta_tx_server.go
@@ -406,9 +406,7 @@ func (zts ZetaTxServer) DeploySystemContracts(
// DeployZRC20s deploys the ZRC20 contracts
// returns the addresses of erc20 zrc20
-func (zts ZetaTxServer) DeployZRC20s(
- accountOperational, accountAdmin, erc20Addr string,
-) (string, error) {
+func (zts ZetaTxServer) DeployZRC20s(accountOperational, accountAdmin, erc20Addr string) (string, error) {
// retrieve account
accOperational, err := zts.clientCtx.Keyring.Key(accountOperational)
if err != nil {
@@ -441,8 +439,26 @@ func (zts ZetaTxServer) DeployZRC20s(
deployerAddr = addrOperational.String()
}
+ deploy := func(msg *fungibletypes.MsgDeployFungibleCoinZRC20) (string, error) {
+ res, err := zts.BroadcastTx(deployerAccount, msg)
+ if err != nil {
+ return "", fmt.Errorf("failed to deploy eth zrc20: %w", err)
+ }
+
+ addr, err := fetchZRC20FromDeployResponse(res)
+ if err != nil {
+ return "", fmt.Errorf("unable to fetch zrc20 from deploy response: %w", err)
+ }
+
+ if err := zts.initializeLiquidityCap(addr); err != nil {
+ return "", fmt.Errorf("unable to initialize liquidity cap: %w", err)
+ }
+
+ return addr, nil
+ }
+
// deploy eth zrc20
- res, err := zts.BroadcastTx(deployerAccount, fungibletypes.NewMsgDeployFungibleCoinZRC20(
+ _, err = deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20(
deployerAddr,
"",
chains.GoerliLocalnet.ChainId,
@@ -455,16 +471,9 @@ func (zts ZetaTxServer) DeployZRC20s(
if err != nil {
return "", fmt.Errorf("failed to deploy eth zrc20: %s", err.Error())
}
- zrc20, err := fetchZRC20FromDeployResponse(res)
- if err != nil {
- return "", err
- }
- if err := zts.initializeLiquidityCap(zrc20); err != nil {
- return "", err
- }
// deploy btc zrc20
- res, err = zts.BroadcastTx(deployerAccount, fungibletypes.NewMsgDeployFungibleCoinZRC20(
+ _, err = deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20(
deployerAddr,
"",
chains.BitcoinRegtest.ChainId,
@@ -477,16 +486,9 @@ func (zts ZetaTxServer) DeployZRC20s(
if err != nil {
return "", fmt.Errorf("failed to deploy btc zrc20: %s", err.Error())
}
- zrc20, err = fetchZRC20FromDeployResponse(res)
- if err != nil {
- return "", err
- }
- if err := zts.initializeLiquidityCap(zrc20); err != nil {
- return "", err
- }
// deploy sol zrc20
- res, err = zts.BroadcastTx(deployerAccount, fungibletypes.NewMsgDeployFungibleCoinZRC20(
+ _, err = deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20(
deployerAddr,
"",
chains.SolanaLocalnet.ChainId,
@@ -499,16 +501,24 @@ func (zts ZetaTxServer) DeployZRC20s(
if err != nil {
return "", fmt.Errorf("failed to deploy sol zrc20: %s", err.Error())
}
- zrc20, err = fetchZRC20FromDeployResponse(res)
+
+ // deploy ton zrc20
+ _, err = deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20(
+ deployerAddr,
+ "",
+ chains.TONLocalnet.ChainId,
+ 9,
+ "TON",
+ "TON",
+ coin.CoinType_Gas,
+ 100_000,
+ ))
if err != nil {
- return "", err
- }
- if err := zts.initializeLiquidityCap(zrc20); err != nil {
- return "", err
+ return "", fmt.Errorf("failed to deploy ton zrc20: %s", err.Error())
}
// deploy erc20 zrc20
- res, err = zts.BroadcastTx(deployerAccount, fungibletypes.NewMsgDeployFungibleCoinZRC20(
+ erc20zrc20Addr, err := deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20(
deployerAddr,
erc20Addr,
chains.GoerliLocalnet.ChainId,
@@ -522,15 +532,6 @@ func (zts ZetaTxServer) DeployZRC20s(
return "", fmt.Errorf("failed to deploy erc20 zrc20: %s", err.Error())
}
- // fetch the erc20 zrc20 contract address and remove the quotes
- erc20zrc20Addr, err := fetchZRC20FromDeployResponse(res)
- if err != nil {
- return "", err
- }
- if err := zts.initializeLiquidityCap(erc20zrc20Addr); err != nil {
- return "", err
- }
-
return erc20zrc20Addr, nil
}
From 9669adc4b4db5e1f78259c5c79ad0b6a701c77ab Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Mon, 30 Sep 2024 18:43:21 +0200
Subject: [PATCH 19/36] Wire the TON observer into the orchestrator
---
zetaclient/chains/ton/config.go | 13 ++++
zetaclient/chains/ton/liteapi/client.go | 20 +++++++
zetaclient/chains/ton/observer/observer.go | 5 +-
.../chains/ton/observer/observer_test.go | 10 +++-
zetaclient/config/config_chain.go | 10 +++-
zetaclient/config/types.go | 16 +++++
zetaclient/context/chain.go | 4 ++
zetaclient/orchestrator/bootstap_test.go | 33 ++++++++---
zetaclient/orchestrator/bootstrap.go | 59 +++++++++++++++++++
zetaclient/testutils/mocks/ton_liteclient.go | 18 +++---
zetaclient/testutils/testrpc/rpc.go | 2 +-
11 files changed, 168 insertions(+), 22 deletions(-)
diff --git a/zetaclient/chains/ton/config.go b/zetaclient/chains/ton/config.go
index ee7eaac701..a8578936a3 100644
--- a/zetaclient/chains/ton/config.go
+++ b/zetaclient/chains/ton/config.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
+ "net/url"
"time"
"github.com/tonkeeper/tongo/config"
@@ -38,3 +39,15 @@ func ConfigFromURL(ctx context.Context, url string) (*GlobalConfigurationFile, e
return config.ParseConfig(res.Body)
}
+
+func ConfigFromPath(path string) (*GlobalConfigurationFile, error) {
+ return config.ParseConfigFile(path)
+}
+
+func ConfigFromAny(ctx context.Context, urlOrPath string) (*GlobalConfigurationFile, error) {
+ if u, err := url.Parse(urlOrPath); err == nil {
+ return ConfigFromURL(ctx, u.String())
+ }
+
+ return ConfigFromPath(urlOrPath)
+}
diff --git a/zetaclient/chains/ton/liteapi/client.go b/zetaclient/chains/ton/liteapi/client.go
index 03a659dd4f..ca79f462d9 100644
--- a/zetaclient/chains/ton/liteapi/client.go
+++ b/zetaclient/chains/ton/liteapi/client.go
@@ -11,6 +11,8 @@ import (
"github.com/tonkeeper/tongo/liteapi"
"github.com/tonkeeper/tongo/tlb"
"github.com/tonkeeper/tongo/ton"
+
+ zetaton "github.com/zeta-chain/node/zetaclient/chains/ton"
)
// Client extends liteapi.Client with some high-level tools
@@ -32,6 +34,24 @@ func New(client *liteapi.Client) *Client {
return &Client{Client: client, blockCache: blockCache}
}
+// NewFromAny creates a new client from a URL or a file path.
+func NewFromAny(ctx context.Context, urlOrPath string) (*Client, error) {
+ cfg, err := zetaton.ConfigFromAny(ctx, urlOrPath)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to get config")
+ }
+
+ client, err := liteapi.NewClient(
+ liteapi.WithConfigurationFile(*cfg),
+ liteapi.WithDetectArchiveNodes(),
+ )
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to create client")
+ }
+
+ return New(client), nil
+}
+
// GetBlockHeader returns block header by block ID.
// Uses LRU cache for network efficiency.
// I haven't found what mode means but `0` works fine.
diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go
index f6792e5b1b..acdda42b46 100644
--- a/zetaclient/chains/ton/observer/observer.go
+++ b/zetaclient/chains/ton/observer/observer.go
@@ -14,6 +14,7 @@ import (
"github.com/zeta-chain/node/zetaclient/chains/interfaces"
)
+// Observer is a TON observer.
type Observer struct {
*base.Observer
@@ -25,13 +26,14 @@ type Observer struct {
//
//go:generate mockery --name LiteClient --filename ton_liteclient.go --case underscore --output ../../../testutils/mocks
type LiteClient interface {
- GetBlockHeader(ctx context.Context, acc ton.BlockIDExt, mode int) (tlb.BlockInfo, error)
+ GetBlockHeader(ctx context.Context, blockID ton.BlockIDExt, mode uint32) (tlb.BlockInfo, error)
GetTransactionsUntil(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error)
GetFirstTransaction(ctx context.Context, id ton.AccountID) (*ton.Transaction, int, error)
}
var _ interfaces.ChainObserver = (*Observer)(nil)
+// New constructor for TON Observer.
func New(bo *base.Observer, client LiteClient, gateway *toncontracts.Gateway) (*Observer, error) {
switch {
case !bo.Chain().IsTONChain():
@@ -51,6 +53,7 @@ func New(bo *base.Observer, client LiteClient, gateway *toncontracts.Gateway) (*
}, nil
}
+// Start starts the observer. This method is NOT blocking.
func (ob *Observer) Start(ctx context.Context) {
if ok := ob.Observer.Start(); !ok {
ob.Logger().Chain.Info().Msgf("observer is already started for chain %d", ob.Chain().ChainId)
diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go
index 84e779f980..3237833920 100644
--- a/zetaclient/chains/ton/observer/observer_test.go
+++ b/zetaclient/chains/ton/observer/observer_test.go
@@ -92,7 +92,8 @@ func newTestSuite(t *testing.T) *testSuite {
}
// Setup mocks
- ts.zetacore.On("Chain").Return(chain)
+ ts.zetacore.On("Chain").Return(chain).Maybe()
+
setupVotesBag(ts)
return ts
@@ -126,7 +127,7 @@ func (ts *testSuite) OnGetTransactionsUntil(
err error,
) *mock.Call {
return ts.liteClient.
- On("GetTransactionsUntil", ts.ctx, acc, lt, hash).
+ On("GetTransactionsUntil", mock.Anything, acc, lt, hash).
Return(txs, err)
}
@@ -136,7 +137,9 @@ func (ts *testSuite) MockGetBlockHeader(id ton.BlockIDExt) *mock.Call {
BlockInfoPart: tlb.BlockInfoPart{MinRefMcSeqno: id.Seqno},
}
- return ts.liteClient.On("GetBlockHeader", ts.ctx, id, 0).Return(blockInfo, nil)
+ return ts.liteClient.
+ On("GetBlockHeader", mock.Anything, id, uint32(0)).
+ Return(blockInfo, nil)
}
// parses string to TON
@@ -165,6 +168,7 @@ func setupVotesBag(ts *testSuite) {
}
ts.zetacore.
On("PostVoteInbound", ts.ctx, mock.Anything, mock.Anything, mock.Anything).
+ Maybe().
Run(catcher).
Return("", "", nil) // zeta hash, ballot index, error
}
diff --git a/zetaclient/config/config_chain.go b/zetaclient/config/config_chain.go
index 54d01baaf7..9bdfcc6bbb 100644
--- a/zetaclient/config/config_chain.go
+++ b/zetaclient/config/config_chain.go
@@ -21,8 +21,9 @@ func New(setDefaults bool) Config {
if setDefaults {
cfg.BitcoinConfig = bitcoinConfigRegnet()
- cfg.SolanaConfig = solanaConfigLocalnet()
cfg.EVMChainConfigs = evmChainsConfigs()
+ cfg.SolanaConfig = solanaConfigLocalnet()
+ cfg.TONConfig = tonConfigLocalnet()
}
return cfg
@@ -47,6 +48,13 @@ func solanaConfigLocalnet() SolanaConfig {
}
}
+func tonConfigLocalnet() TONConfig {
+ return TONConfig{
+ LiteClientConfigURL: "http://ton:8000/lite-client.json",
+ RPCAlertLatency: 60,
+ }
+}
+
// evmChainsConfigs contains EVM chain configs
// it contains list of EVM chains with empty endpoint except for localnet
func evmChainsConfigs() map[int64]EVMConfig {
diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go
index 9ef1a5d5a5..055714bfea 100644
--- a/zetaclient/config/types.go
+++ b/zetaclient/config/types.go
@@ -58,6 +58,13 @@ type SolanaConfig struct {
RPCAlertLatency int64
}
+// TONConfig is the config for TON chain
+type TONConfig struct {
+ // Can be either URL of local file path
+ LiteClientConfigURL string `json:"liteClientConfigURL"`
+ RPCAlertLatency int64 `json:"rpcAlertLatency"`
+}
+
// ComplianceConfig is the config for compliance
type ComplianceConfig struct {
LogPath string `json:"LogPath"`
@@ -93,6 +100,7 @@ type Config struct {
EVMChainConfigs map[int64]EVMConfig `json:"EVMChainConfigs"`
BitcoinConfig BTCConfig `json:"BitcoinConfig"`
SolanaConfig SolanaConfig `json:"SolanaConfig"`
+ TONConfig TONConfig `json:"TONConfig"`
// compliance config
ComplianceConfig ComplianceConfig `json:"ComplianceConfig"`
@@ -137,6 +145,14 @@ func (c Config) GetSolanaConfig() (SolanaConfig, bool) {
return c.SolanaConfig, c.SolanaConfig != (SolanaConfig{})
}
+// GetTONConfig returns the TONConfig and a bool indicating if it's present.
+func (c Config) GetTONConfig() (TONConfig, bool) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ return c.TONConfig, c.TONConfig != TONConfig{}
+}
+
// String returns the string representation of the config
func (c Config) String() string {
s, err := json.MarshalIndent(c, "", "\t")
diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go
index 946dbae598..b23fce1a54 100644
--- a/zetaclient/context/chain.go
+++ b/zetaclient/context/chain.go
@@ -165,6 +165,10 @@ func (c Chain) IsSolana() bool {
return chains.IsSolanaChain(c.ID(), c.registry.additionalChains)
}
+func (c Chain) IsTON() bool {
+ return chains.IsTONChain(c.ID(), c.registry.additionalChains)
+}
+
// RelayerKeyPassword returns the relayer key password for the chain
func (c Chain) RelayerKeyPassword() string {
network := c.RawChain().Network
diff --git a/zetaclient/orchestrator/bootstap_test.go b/zetaclient/orchestrator/bootstap_test.go
index 6153961183..6c263ff4f7 100644
--- a/zetaclient/orchestrator/bootstap_test.go
+++ b/zetaclient/orchestrator/bootstap_test.go
@@ -21,7 +21,11 @@ import (
"github.com/zeta-chain/node/zetaclient/testutils/testrpc"
)
-const solanaGatewayAddress = "2kJndCL9NBR36ySiQ4bmArs4YgWQu67LmCDfLzk5Gb7s"
+const (
+ solanaGatewayAddress = "2kJndCL9NBR36ySiQ4bmArs4YgWQu67LmCDfLzk5Gb7s"
+ tonGatewayAddress = "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b"
+ tonMainnet = "https://ton.org/global-config.json"
+)
func TestCreateSignerMap(t *testing.T) {
var (
@@ -211,9 +215,12 @@ func TestCreateChainObserverMap(t *testing.T) {
evmServer := testrpc.NewEVMServer(t)
evmServer.SetBlockNumber(100)
- // Given generic SOL RPC
+ // Given SOL config
_, solConfig := testrpc.NewSolanaServer(t)
+ // Given TON config
+ tonConfig := config.TONConfig{LiteClientConfigURL: tonMainnet, RPCAlertLatency: 1}
+
// Given a zetaclient config with ETH, MATIC, and BTC chains
cfg := config.New(false)
@@ -229,6 +236,7 @@ func TestCreateChainObserverMap(t *testing.T) {
cfg.BitcoinConfig = btcConfig
cfg.SolanaConfig = solConfig
+ cfg.TONConfig = tonConfig
// Given AppContext
app := zctx.New(cfg, nil, log)
@@ -239,6 +247,7 @@ func TestCreateChainObserverMap(t *testing.T) {
mustUpdateAppContextChainParams(t, app, []chains.Chain{
chains.Ethereum,
chains.BitcoinMainnet,
+ chains.TONMainnet,
})
// ACT
@@ -249,11 +258,12 @@ func TestCreateChainObserverMap(t *testing.T) {
assert.NotEmpty(t, observers)
// Okay, now we want to check that signers for EVM and BTC were created
- assert.Equal(t, 2, len(observers))
+ assert.Equal(t, 3, len(observers))
hasObserver(t, observers, chains.Ethereum.ChainId)
hasObserver(t, observers, chains.BitcoinMainnet.ChainId)
+ hasObserver(t, observers, chains.TONMainnet.ChainId)
- t.Run("Add polygon in the runtime", func(t *testing.T) {
+ t.Run("Add polygon and remove TON in the runtime", func(t *testing.T) {
// ARRANGE
mustUpdateAppContextChainParams(t, app, []chains.Chain{
chains.Ethereum, chains.BitcoinMainnet, chains.Polygon,
@@ -265,7 +275,7 @@ func TestCreateChainObserverMap(t *testing.T) {
// ASSERT
assert.NoError(t, err)
assert.Equal(t, 1, added)
- assert.Equal(t, 0, removed)
+ assert.Equal(t, 1, removed)
hasObserver(t, observers, chains.Ethereum.ChainId)
hasObserver(t, observers, chains.Polygon.ChainId)
@@ -400,6 +410,11 @@ func chainParams(supportedChains []chains.Chain) ([]chains.Chain, map[int64]*obs
continue
}
+ if chains.IsEVMChain(chainID, nil) {
+ params[chainID] = ptr.Ptr(mocks.MockChainParams(chainID, 100))
+ continue
+ }
+
if chains.IsSolanaChain(chainID, nil) {
p := mocks.MockChainParams(chainID, 100)
p.GatewayAddress = solanaGatewayAddress
@@ -407,10 +422,14 @@ func chainParams(supportedChains []chains.Chain) ([]chains.Chain, map[int64]*obs
continue
}
- if chains.IsEVMChain(chainID, nil) {
- params[chainID] = ptr.Ptr(mocks.MockChainParams(chainID, 100))
+ if chains.IsTONChain(chainID, nil) {
+ p := mocks.MockChainParams(chainID, 100)
+ p.GatewayAddress = tonGatewayAddress
+ params[chainID] = &p
continue
}
+
+ panic("unknown chain: " + chain.String())
}
return supportedChains, params
diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go
index f7be6ad504..8742368825 100644
--- a/zetaclient/orchestrator/bootstrap.go
+++ b/zetaclient/orchestrator/bootstrap.go
@@ -9,7 +9,9 @@ import (
solrpc "github.com/gagliardetto/solana-go/rpc"
ethrpc2 "github.com/onrik/ethrpc"
"github.com/pkg/errors"
+ "github.com/tonkeeper/tongo/ton"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
"github.com/zeta-chain/node/zetaclient/chains/base"
btcobserver "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer"
"github.com/zeta-chain/node/zetaclient/chains/bitcoin/rpc"
@@ -19,6 +21,8 @@ import (
"github.com/zeta-chain/node/zetaclient/chains/interfaces"
solbserver "github.com/zeta-chain/node/zetaclient/chains/solana/observer"
solanasigner "github.com/zeta-chain/node/zetaclient/chains/solana/signer"
+ "github.com/zeta-chain/node/zetaclient/chains/ton/liteapi"
+ tonobserver "github.com/zeta-chain/node/zetaclient/chains/ton/observer"
zctx "github.com/zeta-chain/node/zetaclient/context"
"github.com/zeta-chain/node/zetaclient/db"
"github.com/zeta-chain/node/zetaclient/keys"
@@ -181,6 +185,9 @@ func syncSignerMap(
}
addSigner(chainID, signer)
+ case chain.IsTON():
+ logger.Std.Error().Err(err).Msgf("TON signer is not implemented yet for chain id %d", chainID)
+ continue
default:
logger.Std.Warn().
Int64("signer.chain_id", chain.ID()).
@@ -394,6 +401,58 @@ func syncObserverMap(
}
addObserver(chainID, solObserver)
+ case chain.IsTON():
+ cfg, found := app.Config().GetTONConfig()
+ if !found {
+ logger.Std.Warn().Msgf("Unable to find chain params for TON chain %d", chainID)
+ continue
+ }
+
+ database, err := db.NewFromSqlite(dbpath, chainName, true)
+ if err != nil {
+ logger.Std.Error().Err(err).Msgf("unable to open database for TON chain %d", chainID)
+ continue
+ }
+
+ baseObserver, err := base.NewObserver(
+ *rawChain,
+ *params,
+ client,
+ tss,
+ base.DefaultBlockCacheSize,
+ base.DefaultHeaderCacheSize,
+ cfg.RPCAlertLatency,
+ ts,
+ database,
+ logger,
+ )
+
+ if err != nil {
+ logger.Std.Error().Err(err).Msgf("Unable to create base observer for TON chain %d", chainID)
+ continue
+ }
+
+ tonClient, err := liteapi.NewFromAny(ctx, cfg.LiteClientConfigURL)
+ if err != nil {
+ logger.Std.Error().Err(err).Msgf("Unable to create TON liteapi for chain %d", chainID)
+ continue
+ }
+
+ gatewayID, err := ton.ParseAccountID(params.GatewayAddress)
+ if err != nil {
+ logger.Std.Error().Err(err).
+ Msgf("Unable to parse gateway address %q for chain %d", params.GatewayAddress, chainID)
+ continue
+ }
+
+ gw := toncontracts.NewGateway(gatewayID)
+ tonObserver, err := tonobserver.New(baseObserver, tonClient, gw)
+ if err != nil {
+ logger.Std.Error().Err(err).Msgf("Unable to create TON observer for chain %d", chainID)
+ continue
+ }
+
+ addObserver(chainID, tonObserver)
default:
logger.Std.Warn().
Int64("observer.chain_id", chain.ID()).
diff --git a/zetaclient/testutils/mocks/ton_liteclient.go b/zetaclient/testutils/mocks/ton_liteclient.go
index 6074fc5949..573d2d600e 100644
--- a/zetaclient/testutils/mocks/ton_liteclient.go
+++ b/zetaclient/testutils/mocks/ton_liteclient.go
@@ -17,9 +17,9 @@ type LiteClient struct {
mock.Mock
}
-// GetBlockHeader provides a mock function with given fields: ctx, acc, mode
-func (_m *LiteClient) GetBlockHeader(ctx context.Context, acc ton.BlockIDExt, mode int) (tlb.BlockInfo, error) {
- ret := _m.Called(ctx, acc, mode)
+// GetBlockHeader provides a mock function with given fields: ctx, blockID, mode
+func (_m *LiteClient) GetBlockHeader(ctx context.Context, blockID ton.BlockIDExt, mode uint32) (tlb.BlockInfo, error) {
+ ret := _m.Called(ctx, blockID, mode)
if len(ret) == 0 {
panic("no return value specified for GetBlockHeader")
@@ -27,17 +27,17 @@ func (_m *LiteClient) GetBlockHeader(ctx context.Context, acc ton.BlockIDExt, mo
var r0 tlb.BlockInfo
var r1 error
- if rf, ok := ret.Get(0).(func(context.Context, ton.BlockIDExt, int) (tlb.BlockInfo, error)); ok {
- return rf(ctx, acc, mode)
+ if rf, ok := ret.Get(0).(func(context.Context, ton.BlockIDExt, uint32) (tlb.BlockInfo, error)); ok {
+ return rf(ctx, blockID, mode)
}
- if rf, ok := ret.Get(0).(func(context.Context, ton.BlockIDExt, int) tlb.BlockInfo); ok {
- r0 = rf(ctx, acc, mode)
+ if rf, ok := ret.Get(0).(func(context.Context, ton.BlockIDExt, uint32) tlb.BlockInfo); ok {
+ r0 = rf(ctx, blockID, mode)
} else {
r0 = ret.Get(0).(tlb.BlockInfo)
}
- if rf, ok := ret.Get(1).(func(context.Context, ton.BlockIDExt, int) error); ok {
- r1 = rf(ctx, acc, mode)
+ if rf, ok := ret.Get(1).(func(context.Context, ton.BlockIDExt, uint32) error); ok {
+ r1 = rf(ctx, blockID, mode)
} else {
r1 = ret.Error(1)
}
diff --git a/zetaclient/testutils/testrpc/rpc.go b/zetaclient/testutils/testrpc/rpc.go
index f444631813..12f368fb3f 100644
--- a/zetaclient/testutils/testrpc/rpc.go
+++ b/zetaclient/testutils/testrpc/rpc.go
@@ -59,7 +59,7 @@ func (s *Server) httpHandler(w http.ResponseWriter, r *http.Request) {
// Decode request
raw, err := io.ReadAll(r.Body)
require.NoError(s.t, err)
- require.NoError(s.t, json.Unmarshal(raw, &req), "unable to unmarshal request")
+ require.NoError(s.t, json.Unmarshal(raw, &req), "unable to unmarshal request for %s", s.name)
// Process request
res := s.rpcHandler(req)
From 2c1f47c6e9ba4f27ec3c56302c38e8e9d83f47af Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Tue, 1 Oct 2024 18:04:47 +0200
Subject: [PATCH 20/36] Add TON chain params of the fly!
---
cmd/zetae2e/local/ton.go | 1 +
e2e/runner/setup_ton.go | 65 ++++++++++++++++++++++++-
zetaclient/chains/base/observer.go | 8 ++-
zetaclient/logs/fields.go | 13 ++---
zetaclient/orchestrator/bootstrap.go | 11 +++--
zetaclient/orchestrator/orchestrator.go | 29 ++++-------
6 files changed, 95 insertions(+), 32 deletions(-)
diff --git a/cmd/zetae2e/local/ton.go b/cmd/zetae2e/local/ton.go
index 000c872b25..bbbbd991de 100644
--- a/cmd/zetae2e/local/ton.go
+++ b/cmd/zetae2e/local/ton.go
@@ -25,6 +25,7 @@ func tonTestRoutine(
deployerRunner,
conf.DefaultAccount,
runner.NewLogger(verbose, color.FgCyan, "ton"),
+ runner.WithZetaTxServer(deployerRunner.ZetaTxServer),
)
if err != nil {
return errors.Wrap(err, "unable to init ton test runner")
diff --git a/e2e/runner/setup_ton.go b/e2e/runner/setup_ton.go
index 9b744401ad..19e91d2608 100644
--- a/e2e/runner/setup_ton.go
+++ b/e2e/runner/setup_ton.go
@@ -2,10 +2,15 @@ package runner
import (
"fmt"
+ "time"
"github.com/pkg/errors"
"github.com/zeta-chain/node/e2e/runner/ton"
+ "github.com/zeta-chain/node/e2e/utils"
+ "github.com/zeta-chain/node/pkg/chains"
+ "github.com/zeta-chain/node/pkg/constant"
+ observertypes "github.com/zeta-chain/node/x/observer/types"
)
// SetupTON setups TON deployer and deploys Gateway contract
@@ -42,7 +47,12 @@ func (r *E2ERunner) SetupTON() error {
return errors.Wrapf(err, "unable to deploy TON gateway")
}
- r.Logger.Print("💎TON Gateway deployed %s (%s)", gwAccount.ID.ToRaw(), gwAccount.ID.ToHuman(false, true))
+ r.Logger.Print(
+ "💎TON Gateway deployed %s (%s) with TSS address %s",
+ gwAccount.ID.ToRaw(),
+ gwAccount.ID.ToHuman(false, true),
+ r.TSSAddress.Hex(),
+ )
// 3. Check that the gateway indeed was deployed and has desired TON balance.
gwBalance, err := deployer.GetBalanceOf(ctx, gwAccount.ID)
@@ -57,5 +67,56 @@ func (r *E2ERunner) SetupTON() error {
r.TONDeployer = deployer
r.TONGateway = gwAccount.ID
- return nil
+ return r.ensureTONChainParams(gwAccount)
+}
+
+func (r *E2ERunner) ensureTONChainParams(gw *ton.AccountInit) error {
+ if r.ZetaTxServer == nil {
+ return errors.New("ZetaTxServer is not initialized")
+ }
+
+ creator := r.ZetaTxServer.MustGetAccountAddressFromName(utils.OperationalPolicyName)
+
+ chainID := chains.TONLocalnet.ChainId
+
+ chainParams := &observertypes.ChainParams{
+ ChainId: chainID,
+ ConfirmationCount: 1,
+ GasPriceTicker: 5,
+ InboundTicker: 5,
+ OutboundTicker: 5,
+ ZetaTokenContractAddress: constant.EVMZeroAddress,
+ ConnectorContractAddress: constant.EVMZeroAddress,
+ Erc20CustodyContractAddress: constant.EVMZeroAddress,
+ OutboundScheduleInterval: 2,
+ OutboundScheduleLookahead: 5,
+ BallotThreshold: observertypes.DefaultBallotThreshold,
+ MinObserverDelegation: observertypes.DefaultMinObserverDelegation,
+ IsSupported: true,
+ GatewayAddress: gw.ID.ToRaw(),
+ }
+
+ msg := observertypes.NewMsgUpdateChainParams(creator, chainParams)
+
+ if _, err := r.ZetaTxServer.BroadcastTx(utils.OperationalPolicyName, msg); err != nil {
+ return errors.Wrap(err, "unable to broadcast TON chain params tx")
+ }
+
+ r.Logger.Print("💎Voted for adding TON chain params (localnet). Waiting for confirmation")
+
+ query := &observertypes.QueryGetChainParamsForChainRequest{ChainId: chainID}
+
+ const duration = 2 * time.Second
+
+ for i := 0; i < 10; i++ {
+ _, err := r.ObserverClient.GetChainParamsForChain(r.Ctx, query)
+ if err == nil {
+ r.Logger.Print("💎TON chain params are set")
+ return nil
+ }
+
+ time.Sleep(duration)
+ }
+
+ return errors.New("unable to set TON chain params")
}
diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go
index e9e8e05ba2..db5232fa30 100644
--- a/zetaclient/chains/base/observer.go
+++ b/zetaclient/chains/base/observer.go
@@ -313,7 +313,12 @@ func (ob *Observer) Logger() *ObserverLogger {
// WithLogger attaches a new logger to the observer.
func (ob *Observer) WithLogger(logger Logger) *Observer {
- chainLogger := logger.Std.With().Int64(logs.FieldChain, ob.chain.ChainId).Logger()
+ chainLogger := logger.Std.
+ With().
+ Int64(logs.FieldChain, ob.chain.ChainId).
+ Str(logs.FieldChainNetwork, ob.chain.Network.String()).
+ Logger()
+
ob.logger = ObserverLogger{
Chain: chainLogger,
Inbound: chainLogger.With().Str(logs.FieldModule, logs.ModNameInbound).Logger(),
@@ -322,6 +327,7 @@ func (ob *Observer) WithLogger(logger Logger) *Observer {
Headers: chainLogger.With().Str(logs.FieldModule, logs.ModNameHeaders).Logger(),
Compliance: logger.Compliance,
}
+
return ob
}
diff --git a/zetaclient/logs/fields.go b/zetaclient/logs/fields.go
index 497690ffa4..78b95fc7e0 100644
--- a/zetaclient/logs/fields.go
+++ b/zetaclient/logs/fields.go
@@ -3,12 +3,13 @@ package logs
// A group of predefined field keys and module names for zetaclient logs
const (
// field keys
- FieldModule = "module"
- FieldMethod = "method"
- FieldChain = "chain"
- FieldNonce = "nonce"
- FieldTx = "tx"
- FieldCctx = "cctx"
+ FieldModule = "module"
+ FieldMethod = "method"
+ FieldChain = "chain"
+ FieldChainNetwork = "chain_network"
+ FieldNonce = "nonce"
+ FieldTx = "tx"
+ FieldCctx = "cctx"
// module names
ModNameInbound = "inbound"
diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go
index 8742368825..09fbebc45c 100644
--- a/zetaclient/orchestrator/bootstrap.go
+++ b/zetaclient/orchestrator/bootstrap.go
@@ -26,6 +26,7 @@ import (
zctx "github.com/zeta-chain/node/zetaclient/context"
"github.com/zeta-chain/node/zetaclient/db"
"github.com/zeta-chain/node/zetaclient/keys"
+ "github.com/zeta-chain/node/zetaclient/logs"
"github.com/zeta-chain/node/zetaclient/metrics"
)
@@ -75,7 +76,7 @@ func syncSignerMap(
presentChainIDs = make([]int64, 0)
onAfterAdd = func(chainID int64, _ interfaces.ChainSigner) {
- logger.Std.Info().Msgf("Added signer for chain %d", chainID)
+ logger.Std.Info().Int64(logs.FieldChain, chainID).Msg("Added signer")
added++
}
@@ -84,7 +85,7 @@ func syncSignerMap(
}
onBeforeRemove = func(chainID int64, _ interfaces.ChainSigner) {
- logger.Std.Info().Msgf("Removing signer for chain %d", chainID)
+ logger.Std.Info().Int64(logs.FieldChain, chainID).Msg("Removing signer")
removed++
}
)
@@ -245,7 +246,8 @@ func syncObserverMap(
presentChainIDs = make([]int64, 0)
- onAfterAdd = func(_ int64, ob interfaces.ChainObserver) {
+ onAfterAdd = func(chainID int64, ob interfaces.ChainObserver) {
+ logger.Std.Info().Int64(logs.FieldChain, chainID).Msg("Added observer")
ob.Start(ctx)
added++
}
@@ -254,7 +256,8 @@ func syncObserverMap(
mapSet[int64, interfaces.ChainObserver](observerMap, chainID, ob, onAfterAdd)
}
- onBeforeRemove = func(_ int64, ob interfaces.ChainObserver) {
+ onBeforeRemove = func(chainID int64, ob interfaces.ChainObserver) {
+ logger.Std.Info().Int64(logs.FieldChain, chainID).Msg("Removing observer")
ob.Stop()
removed++
}
diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go
index 7594ffd763..45156648e1 100644
--- a/zetaclient/orchestrator/orchestrator.go
+++ b/zetaclient/orchestrator/orchestrator.go
@@ -17,6 +17,7 @@ import (
"github.com/zeta-chain/node/pkg/bg"
"github.com/zeta-chain/node/pkg/constant"
zetamath "github.com/zeta-chain/node/pkg/math"
+ "github.com/zeta-chain/node/pkg/ticker"
"github.com/zeta-chain/node/x/crosschain/types"
observertypes "github.com/zeta-chain/node/x/observer/types"
"github.com/zeta-chain/node/zetaclient/chains/base"
@@ -666,28 +667,18 @@ func (oc *Orchestrator) ScheduleCctxSolana(
// runObserverSignerSync runs a blocking ticker that observes chain changes from zetacore
// and optionally (de)provisions respective observers and signers.
func (oc *Orchestrator) runObserverSignerSync(ctx context.Context) error {
- // sync observers and signers right away to speed up zetaclient startup
- if err := oc.syncObserverSigner(ctx); err != nil {
- oc.logger.Error().Err(err).Msg("runObserverSignerSync: syncObserverSigner failed for initial sync")
- }
-
- // sync observer and signer every 10 blocks (approx. 1 minute)
- const cadence = 10 * constant.ZetaBlockTime
-
- ticker := time.NewTicker(cadence)
- defer ticker.Stop()
+ // every other block
+ const cadence = 2 * constant.ZetaBlockTime
- for {
- select {
- case <-oc.stop:
- oc.logger.Warn().Msg("runObserverSignerSync: stopped")
- return nil
- case <-ticker.C:
- if err := oc.syncObserverSigner(ctx); err != nil {
- oc.logger.Error().Err(err).Msg("runObserverSignerSync: syncObserverSigner failed")
- }
+ task := func(ctx context.Context, _ *ticker.Ticker) error {
+ if err := oc.syncObserverSigner(ctx); err != nil {
+ oc.logger.Error().Err(err).Msg("syncObserverSigner failed")
}
+
+ return nil
}
+
+ return ticker.Run(ctx, cadence, task, ticker.WithLogger(oc.logger.Logger, "SyncObserverSigner"))
}
// syncs and provisions observers & signers.
From b19333f86eb2450116ed73445b969112c510c19c Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Wed, 2 Oct 2024 18:07:11 +0200
Subject: [PATCH 21/36] TON deposits E2E wip
---
e2e/e2etests/e2etests.go | 2 +-
e2e/e2etests/test_ton_deposit.go | 93 ++++++++++++++++++-----
pkg/contracts/ton/gateway_op.go | 15 ++--
pkg/contracts/ton/gateway_send.go | 55 ++++++++++++++
zetaclient/chains/base/observer.go | 39 ++++++----
zetaclient/chains/ton/observer/inbound.go | 31 +++++---
6 files changed, 189 insertions(+), 46 deletions(-)
create mode 100644 pkg/contracts/ton/gateway_send.go
diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go
index a33737eb41..1870f3a5a2 100644
--- a/e2e/e2etests/e2etests.go
+++ b/e2e/e2etests/e2etests.go
@@ -436,7 +436,7 @@ var AllE2ETests = []runner.E2ETest{
TestTONDepositName,
"deposit TON into ZEVM",
[]runner.ArgDefinition{
- {Description: "amount in nano tons", DefaultValue: "900000000"}, // 0.9 TON
+ {Description: "amount in nano tons", DefaultValue: "1000000000"}, // 1.0 TON
},
TestTONDeposit,
),
diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go
index a2e8df09d0..bd32e2f654 100644
--- a/e2e/e2etests/test_ton_deposit.go
+++ b/e2e/e2etests/test_ton_deposit.go
@@ -1,42 +1,101 @@
package e2etests
import (
+ "time"
+
"cosmossdk.io/math"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/stretchr/testify/require"
"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/runner/ton"
+ "github.com/zeta-chain/node/pkg/chains"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
+ "github.com/zeta-chain/node/testutil/sample"
+ crosschainTypes "github.com/zeta-chain/node/x/crosschain/types"
)
// TestTONDeposit (!) This boilerplate is a demonstration of E2E capabilities for TON integration
// Actual Deposit test is not implemented yet.
-func TestTONDeposit(r *runner.E2ERunner, _ []string) {
- ctx, deployer := r.Ctx, r.TONDeployer
+func TestTONDeposit(r *runner.E2ERunner, args []string) {
+ require.Len(r, args, 1)
+
+ // Given TON Localnet chain
+ chain := chains.TONLocalnet
// Given deployer
- deployerBalance, err := deployer.GetBalance(ctx)
- require.NoError(r, err, "failed to get deployer balance")
- require.NotZero(r, deployerBalance, "deployer balance is zero")
+ ctx, deployer := r.Ctx, r.TONDeployer
+
+ // Given amount
+ amount := math.NewUintFromBigInt(parseBigInt(r, args[0]))
+
+ // Given TON Gateway
+ gw := toncontracts.NewGateway(r.TONGateway)
// Given sample wallet with a balance of 50 TON
sender, err := deployer.CreateWallet(ctx, ton.TONCoins(50))
require.NoError(r, err)
- // That was funded (again) but the faucet
- _, err = deployer.Fund(ctx, sender.GetAddress(), ton.TONCoins(30))
+ // Given sample EVM address
+ recipient := sample.EthAddress()
+
+ // ACT
+ r.Logger.Print(
+ "Sending deposit of %s TON from %s to zEVM %s",
+ amount.String(),
+ sender.GetAddress().ToRaw(),
+ recipient.Hex(),
+ )
+
+ // we need to include this send mode due to how wallet V5 works
+ // https://github.com/tonkeeper/w5/blob/main/contracts/wallet_v5.fc#L82
+ err = gw.SendDeposit(ctx, sender, amount, recipient, toncontracts.SendFlagIgnoreErrors)
+
+ // ASSERT
require.NoError(r, err)
- // Check sender balance
- sb, err := sender.GetBalance(ctx)
+ // Wait for CCTX mining
+ cctxs := catchPendingCCTX(r, chain.ChainId, time.Minute)
+ require.Len(r, cctxs, 1)
+
+ cctx := cctxs[0]
+
+ // Check cctx props
+ require.NotNil(r, cctx.InboundParams)
+ require.NotNil(r, cctx.InboundParams.Sender)
+ //require.NoError(r, cctx.InboundParams.)
+ //cctx
+
+ // todo validate CCTX
+
+ r.WaitForMinedCCTXFromIndex(cctx.Index)
+
+ // Check sender's balance
+ balance, err := r.TONZRC20.BalanceOf(&bind.CallOpts{}, recipient)
require.NoError(r, err)
- senderBalance := math.NewUint(sb)
+ r.Logger.Print("recipient's zEVM TON balance after deposit: %d", balance)
- // note that it's not exactly 80 TON, but 79.99... due to gas fees
- // We'll tackle gas math later.
- r.Logger.Print(
- "Balance of sender (%s): %s",
- sender.GetAddress().ToHuman(false, true),
- ton.FormatCoins(senderBalance),
- )
+ // todo check balance equals to cctx.amount
+}
+
+// use another method - this returns pending only pending OUTBOUNDS
+func catchPendingCCTX(r *runner.E2ERunner, chainID int64, timeout time.Duration) []*crosschainTypes.CrossChainTx {
+ in := &crosschainTypes.QueryListPendingCctxRequest{ChainId: chainID}
+
+ start := time.Now()
+
+ for time.Since(start) < timeout {
+ res, err := r.CctxClient.ListPendingCctx(r.Ctx, in)
+ if err == nil && len(res.CrossChainTx) > 0 {
+ return res.CrossChainTx
+ }
+
+ time.Sleep(time.Second)
+ }
+
+ r.Logger.Error("Timeout waiting for pending CCTX for chain %d", chainID)
+ r.FailNow()
+
+ return nil
}
diff --git a/pkg/contracts/ton/gateway_op.go b/pkg/contracts/ton/gateway_op.go
index 962ebd7d53..9b77120a65 100644
--- a/pkg/contracts/ton/gateway_op.go
+++ b/pkg/contracts/ton/gateway_op.go
@@ -58,13 +58,8 @@ func (d Deposit) Memo() []byte {
// AsBody casts struct as internal message body.
func (d Deposit) AsBody() (*boc.Cell, error) {
b := boc.NewCell()
- err := ErrCollect(
- b.WriteUint(uint64(OpDeposit), sizeOpCode),
- b.WriteUint(0, sizeQueryID),
- b.WriteBytes(d.Recipient.Bytes()),
- )
- return b, err
+ return b, writeDepositBody(b, d.Recipient)
}
// DepositAndCall represents a deposit and call operation
@@ -101,3 +96,11 @@ func (d DepositAndCall) AsBody() (*boc.Cell, error) {
return b, err
}
+
+func writeDepositBody(b *boc.Cell, recipient eth.Address) error {
+ return ErrCollect(
+ b.WriteUint(uint64(OpDeposit), sizeOpCode),
+ b.WriteUint(0, sizeQueryID),
+ b.WriteBytes(recipient.Bytes()),
+ )
+}
diff --git a/pkg/contracts/ton/gateway_send.go b/pkg/contracts/ton/gateway_send.go
new file mode 100644
index 0000000000..a6b30f2745
--- /dev/null
+++ b/pkg/contracts/ton/gateway_send.go
@@ -0,0 +1,55 @@
+package ton
+
+import (
+ "context"
+
+ "cosmossdk.io/math"
+ eth "github.com/ethereum/go-ethereum/common"
+ "github.com/pkg/errors"
+ "github.com/tonkeeper/tongo/boc"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/wallet"
+)
+
+// Sender TON tx sender.
+type Sender interface {
+ Send(ctx context.Context, messages ...wallet.Sendable) error
+}
+
+const (
+ SendModeDefault = uint8(0)
+)
+
+const (
+ SendFlagIgnoreErrors = uint8(2)
+)
+
+// SendDeposit sends a deposit operation to the gateway on behalf of the sender.
+func (gw *Gateway) SendDeposit(
+ ctx context.Context,
+ s Sender,
+ amount math.Uint,
+ zevmRecipient eth.Address,
+ sendMode uint8,
+) error {
+ body := boc.NewCell()
+
+ if err := writeDepositBody(body, zevmRecipient); err != nil {
+ return errors.Wrap(err, "failed to write deposit body")
+ }
+
+ return gw.send(ctx, s, amount, body, sendMode)
+}
+
+func (gw *Gateway) send(ctx context.Context, s Sender, amount math.Uint, body *boc.Cell, sendMode uint8) error {
+ if body == nil {
+ return errors.New("body is nil")
+ }
+
+ return s.Send(ctx, wallet.Message{
+ Amount: tlb.Coins(amount.Uint64()),
+ Address: gw.accountID,
+ Body: body,
+ Mode: sendMode,
+ })
+}
diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go
index db5232fa30..af8773dfdb 100644
--- a/zetaclient/chains/base/observer.go
+++ b/zetaclient/chains/base/observer.go
@@ -451,22 +451,35 @@ func (ob *Observer) PostVoteInbound(
msg *crosschaintypes.MsgVoteInbound,
retryGasLimit uint64,
) (string, error) {
- txHash := msg.InboundHash
- coinType := msg.CoinType
- chainID := ob.Chain().ChainId
- zetaHash, ballot, err := ob.ZetacoreClient().
- PostVoteInbound(ctx, zetacore.PostVoteInboundGasLimit, retryGasLimit, msg)
- if err != nil {
- ob.logger.Inbound.Err(err).
- Msgf("inbound detected: error posting vote for chain %d token %s inbound %s", chainID, coinType, txHash)
+ const gasLimit = zetacore.PostVoteInboundGasLimit
+
+ var (
+ txHash = msg.InboundHash
+ coinType = msg.CoinType
+ chainID = ob.Chain().ChainId
+ )
+
+ zetaHash, ballot, err := ob.ZetacoreClient().PostVoteInbound(ctx, gasLimit, retryGasLimit, msg)
+
+ lf := map[string]any{
+ "inbound.chain_id": chainID,
+ "inbound.coin_type": coinType.String(),
+ "inbound.external_tx_hash": txHash,
+ "inbound.ballot_index": ballot,
+ "inbound.zeta_tx_hash": zetaHash,
+ }
+
+ switch {
+ case err != nil:
+ ob.logger.Inbound.Error().Err(err).Fields(lf).Msg("inbound detected: error posting vote")
return "", err
- } else if zetaHash != "" {
- ob.logger.Inbound.Info().Msgf("inbound detected: chain %d token %s inbound %s vote %s ballot %s", chainID, coinType, txHash, zetaHash, ballot)
- } else {
- ob.logger.Inbound.Info().Msgf("inbound detected: chain %d token %s inbound %s already voted on ballot %s", chainID, coinType, txHash, ballot)
+ case zetaHash == "":
+ ob.logger.Inbound.Info().Fields(lf).Msg("inbound detected: already voted on ballot")
+ default:
+ ob.logger.Inbound.Info().Fields(lf).Msgf("inbound detected: vote posted")
}
- return ballot, err
+ return ballot, nil
}
// AlertOnRPCLatency prints an alert if the RPC latency exceeds the threshold.
diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go
index f2ab2af77c..7ab5afd43c 100644
--- a/zetaclient/chains/ton/observer/inbound.go
+++ b/zetaclient/chains/ton/observer/inbound.go
@@ -86,11 +86,11 @@ func (ob *Observer) observeInbound(ctx context.Context) error {
return nil
case len(txs) > MaxTransactionsPerTick:
ob.Logger().Inbound.Info().
- Msgf("ObserveInbound: got %d transactions. Taking first %d", len(txs), MaxTransactionsPerTick)
+ Msgf("observeInbound: got %d transactions. Taking first %d", len(txs), MaxTransactionsPerTick)
txs = txs[:MaxTransactionsPerTick]
default:
- ob.Logger().Inbound.Info().Msgf("ObserveInbound: got %d transactions", len(txs))
+ ob.Logger().Inbound.Info().Msgf("observeInbound: got %d transactions", len(txs))
}
for i := range txs {
@@ -102,12 +102,19 @@ func (ob *Observer) observeInbound(ctx context.Context) error {
}
if skip {
+ ob.Logger().Inbound.Info().Fields(txLogFields(&tx)).Msg("observeInbound: skipping tx")
ob.setLastScannedTX(&tx)
+
continue
}
if _, err := ob.voteInbound(ctx, parsedTX); err != nil {
- return errors.Wrapf(err, "unable to vote inbound (hash %s)", parsedTX.Hash().Hex())
+ ob.Logger().Inbound.
+ Error().Err(err).
+ Fields(txLogFields(&tx)).
+ Msg("observeInbound: unable to vote for tx")
+
+ return errors.Wrapf(err, "unable to vote for inbound tx %s", tx.Hash().Hex())
}
ob.setLastScannedTX(&parsedTX.Transaction)
@@ -232,15 +239,21 @@ func (ob *Observer) setLastScannedTX(tx *ton.Transaction) {
if err := ob.WriteLastTxScannedToDB(txHash); err != nil {
ob.Logger().Inbound.Error().
Err(err).
- Uint64("tx.lt", tx.Lt).
- Str("tx.hash", tx.Hash().Hex()).
- Msgf("ObserveInbound: unable to WriteLastTxScannedToDB")
+ Fields(txLogFields(tx)).
+ Msgf("setLastScannedTX: unable to WriteLastTxScannedToDB")
return
}
ob.Logger().Inbound.Info().
- Uint64("tx.lt", tx.Lt).
- Str("tx.hash", tx.Hash().Hex()).
- Msgf("ObserveInbound: WriteLastTxScannedToDB")
+ Fields(txLogFields(tx)).
+ Msg("setLastScannedTX: WriteLastTxScannedToDB")
+}
+
+func txLogFields(tx *ton.Transaction) map[string]any {
+ return map[string]any{
+ "inbound.ton.lt": tx.Lt,
+ "inbound.ton.hash": tx.Hash().Hex(),
+ "inbound.ton.block_id": tx.BlockID.String(),
+ }
}
From a2acaf6e2d0509ddc7589c1571d77ad53a46c7d6 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Wed, 2 Oct 2024 22:52:44 +0200
Subject: [PATCH 22/36] Fix bugs during cctx;
---
pkg/chains/chain.go | 2 ++
pkg/chains/chain_test.go | 6 ++++++
x/crosschain/keeper/evm_deposit.go | 2 +-
zetaclient/chains/ton/observer/inbound.go | 5 +++--
zetaclient/chains/ton/observer/inbound_test.go | 11 +++++++++--
5 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/pkg/chains/chain.go b/pkg/chains/chain.go
index 19ac59830d..3cdb9eb161 100644
--- a/pkg/chains/chain.go
+++ b/pkg/chains/chain.go
@@ -108,6 +108,8 @@ func DecodeAddressFromChainID(chainID int64, addr string, additionalChains []Cha
return []byte(addr), nil
case IsSolanaChain(chainID, additionalChains):
return []byte(addr), nil
+ case IsTONChain(chainID, additionalChains):
+ return []byte(addr), nil
default:
return nil, fmt.Errorf("chain (%d) not supported", chainID)
}
diff --git a/pkg/chains/chain_test.go b/pkg/chains/chain_test.go
index 2b2dfd7e77..68c0f8d979 100644
--- a/pkg/chains/chain_test.go
+++ b/pkg/chains/chain_test.go
@@ -300,6 +300,12 @@ func TestDecodeAddressFromChainID(t *testing.T) {
addr: "DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw",
want: []byte("DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw"),
},
+ {
+ name: "TON",
+ chainID: chains.TONMainnet.ChainId,
+ addr: "0:55798cb7b87168251a7c39f6806b8c202f6caa0f617a76f4070b3fdacfd056a1",
+ want: []byte("0:55798cb7b87168251a7c39f6806b8c202f6caa0f617a76f4070b3fdacfd056a1"),
+ },
{
name: "Non-supported chain",
chainID: 9999,
diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go
index c873e9b636..c672686c1a 100644
--- a/x/crosschain/keeper/evm_deposit.go
+++ b/x/crosschain/keeper/evm_deposit.go
@@ -96,7 +96,7 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo
from, err := chains.DecodeAddressFromChainID(inboundSenderChainID, inboundSender, k.GetAuthorityKeeper().GetAdditionalChainList(ctx))
if err != nil {
- return false, fmt.Errorf("HandleEVMDeposit: unable to decode address: %s", err.Error())
+ return false, fmt.Errorf("HandleEVMDeposit: unable to decode address: %w", err)
}
evmTxResponse, contractCall, err := k.fungibleKeeper.ZRC20DepositAndCallContract(
diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go
index 7ab5afd43c..99b7083dac 100644
--- a/zetaclient/chains/ton/observer/inbound.go
+++ b/zetaclient/chains/ton/observer/inbound.go
@@ -2,6 +2,7 @@ package observer
import (
"context"
+ "encoding/hex"
"fmt"
"slices"
@@ -202,7 +203,7 @@ func (ob *Observer) voteDeposit(
sender,
ob.ZetacoreClient().Chain().ChainId,
amount,
- string(memo),
+ hex.EncodeToString(memo),
inboundHash,
uint64(seqno),
gasLimit,
@@ -254,6 +255,6 @@ func txLogFields(tx *ton.Transaction) map[string]any {
return map[string]any{
"inbound.ton.lt": tx.Lt,
"inbound.ton.hash": tx.Hash().Hex(),
- "inbound.ton.block_id": tx.BlockID.String(),
+ "inbound.ton.block_id": tx.BlockID.BlockID.String(),
}
}
diff --git a/zetaclient/chains/ton/observer/inbound_test.go b/zetaclient/chains/ton/observer/inbound_test.go
index 96f4b93d37..9aee03ddab 100644
--- a/zetaclient/chains/ton/observer/inbound_test.go
+++ b/zetaclient/chains/ton/observer/inbound_test.go
@@ -1,6 +1,7 @@
package observer
import (
+ "encoding/hex"
"testing"
"github.com/pkg/errors"
@@ -157,7 +158,7 @@ func TestInbound(t *testing.T) {
assert.Equal(t, "", cctx.Asset)
assert.Equal(t, deposit.Amount.Uint64(), cctx.Amount.Uint64())
- assert.Equal(t, string(deposit.Recipient.Bytes()), cctx.Message)
+ assert.Equal(t, hex.EncodeToString(deposit.Recipient.Bytes()), cctx.Message)
// Check hash & block height
expectedHash := liteapi.TransactionHashToString(depositTX.Lt, txHash(depositTX))
@@ -219,7 +220,13 @@ func TestInbound(t *testing.T) {
assert.Equal(t, "", cctx.Asset)
assert.Equal(t, depositAndCall.Amount.Uint64(), cctx.Amount.Uint64())
- assert.Equal(t, string(depositAndCall.Recipient.Bytes())+callData, cctx.Message)
+
+ expectedMessage := hex.EncodeToString(append(
+ depositAndCall.Recipient.Bytes(),
+ []byte(callData)...,
+ ))
+
+ assert.Equal(t, expectedMessage, cctx.Message)
// Check hash & block height
expectedHash := liteapi.TransactionHashToString(depositAndCallTX.Lt, txHash(depositAndCallTX))
From 2642694600a3e458a5321f29745d5356d4959544 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Thu, 3 Oct 2024 00:39:21 +0200
Subject: [PATCH 23/36] =?UTF-8?q?TON=20Deposits=20E2E=20=F0=9F=AB=A1?=
=?UTF-8?q?=E2=9C=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
cmd/zetae2e/config/config.go | 1 +
cmd/zetae2e/config/contracts.go | 11 +++++
e2e/config/config.go | 1 +
e2e/e2etests/test_ton_deposit.go | 75 +++++++++++++++++++++----------
e2e/runner/runner.go | 6 +++
e2e/txserver/zeta_tx_server.go | 2 +-
pkg/contracts/ton/gateway_send.go | 6 +--
7 files changed, 73 insertions(+), 29 deletions(-)
diff --git a/cmd/zetae2e/config/config.go b/cmd/zetae2e/config/config.go
index 8e87e63bef..0cea78af39 100644
--- a/cmd/zetae2e/config/config.go
+++ b/cmd/zetae2e/config/config.go
@@ -68,6 +68,7 @@ func ExportContractsFromRunner(r *runner.E2ERunner, conf config.Config) config.C
conf.Contracts.ZEVM.ERC20ZRC20Addr = config.DoubleQuotedString(r.ERC20ZRC20Addr.Hex())
conf.Contracts.ZEVM.BTCZRC20Addr = config.DoubleQuotedString(r.BTCZRC20Addr.Hex())
conf.Contracts.ZEVM.SOLZRC20Addr = config.DoubleQuotedString(r.SOLZRC20Addr.Hex())
+ conf.Contracts.ZEVM.TONZRC20Addr = config.DoubleQuotedString(r.TONZRC20Addr.Hex())
conf.Contracts.ZEVM.UniswapFactoryAddr = config.DoubleQuotedString(r.UniswapV2FactoryAddr.Hex())
conf.Contracts.ZEVM.UniswapRouterAddr = config.DoubleQuotedString(r.UniswapV2RouterAddr.Hex())
conf.Contracts.ZEVM.ConnectorZEVMAddr = config.DoubleQuotedString(r.ConnectorZEVMAddr.Hex())
diff --git a/cmd/zetae2e/config/contracts.go b/cmd/zetae2e/config/contracts.go
index d6cba953a5..9af3ccd812 100644
--- a/cmd/zetae2e/config/contracts.go
+++ b/cmd/zetae2e/config/contracts.go
@@ -135,6 +135,17 @@ func setContractsFromConfig(r *runner.E2ERunner, conf config.Config) error {
}
}
+ if c := conf.Contracts.ZEVM.TONZRC20Addr; c != "" {
+ r.TONZRC20Addr, err = c.AsEVMAddress()
+ if err != nil {
+ return fmt.Errorf("invalid TONZRC20Addr: %w", err)
+ }
+ r.TONZRC20, err = zrc20.NewZRC20(r.TONZRC20Addr, r.ZEVMClient)
+ if err != nil {
+ return err
+ }
+ }
+
if c := conf.Contracts.ZEVM.UniswapFactoryAddr; c != "" {
r.UniswapV2FactoryAddr, err = c.AsEVMAddress()
if err != nil {
diff --git a/e2e/config/config.go b/e2e/config/config.go
index 089ea39b70..32a799c027 100644
--- a/e2e/config/config.go
+++ b/e2e/config/config.go
@@ -141,6 +141,7 @@ type ZEVM struct {
ERC20ZRC20Addr DoubleQuotedString `yaml:"erc20_zrc20"`
BTCZRC20Addr DoubleQuotedString `yaml:"btc_zrc20"`
SOLZRC20Addr DoubleQuotedString `yaml:"sol_zrc20"`
+ TONZRC20Addr DoubleQuotedString `yaml:"ton_zrc20"`
UniswapFactoryAddr DoubleQuotedString `yaml:"uniswap_factory"`
UniswapRouterAddr DoubleQuotedString `yaml:"uniswap_router"`
ConnectorZEVMAddr DoubleQuotedString `yaml:"connector_zevm"`
diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go
index bd32e2f654..8c1cdd037e 100644
--- a/e2e/e2etests/test_ton_deposit.go
+++ b/e2e/e2etests/test_ton_deposit.go
@@ -1,6 +1,7 @@
package e2etests
import (
+ "errors"
"time"
"cosmossdk.io/math"
@@ -15,6 +16,12 @@ import (
crosschainTypes "github.com/zeta-chain/node/x/crosschain/types"
)
+// we need to use this send mode due to how wallet V5 works
+//
+// https://github.com/tonkeeper/w5/blob/main/contracts/wallet_v5.fc#L82
+// https://docs.ton.org/develop/smart-contracts/guidelines/message-modes-cookbook
+const tonDepositSendCode = toncontracts.SendFlagSeparateFees + toncontracts.SendFlagIgnoreErrors
+
// TestTONDeposit (!) This boilerplate is a demonstration of E2E capabilities for TON integration
// Actual Deposit test is not implemented yet.
func TestTONDeposit(r *runner.E2ERunner, args []string) {
@@ -29,6 +36,10 @@ func TestTONDeposit(r *runner.E2ERunner, args []string) {
// Given amount
amount := math.NewUintFromBigInt(parseBigInt(r, args[0]))
+ // https://github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc#L28
+ // (will be optimized & dynamic in the future)
+ depositFee := math.NewUint(10_000_000)
+
// Given TON Gateway
gw := toncontracts.NewGateway(r.TONGateway)
@@ -47,26 +58,28 @@ func TestTONDeposit(r *runner.E2ERunner, args []string) {
recipient.Hex(),
)
- // we need to include this send mode due to how wallet V5 works
- // https://github.com/tonkeeper/w5/blob/main/contracts/wallet_v5.fc#L82
- err = gw.SendDeposit(ctx, sender, amount, recipient, toncontracts.SendFlagIgnoreErrors)
+ err = gw.SendDeposit(ctx, sender, amount, recipient, tonDepositSendCode)
// ASSERT
require.NoError(r, err)
// Wait for CCTX mining
- cctxs := catchPendingCCTX(r, chain.ChainId, time.Minute)
+ filter := func(cctx *crosschainTypes.CrossChainTx) bool {
+ return cctx.InboundParams.SenderChainId == chain.ChainId &&
+ cctx.InboundParams.Sender == sender.GetAddress().ToRaw()
+ }
+
+ cctxs, err := waitForSpecificCCTX(r, filter, time.Minute)
+ require.NoError(r, err)
require.Len(r, cctxs, 1)
+ // Check CCTX
cctx := cctxs[0]
- // Check cctx props
- require.NotNil(r, cctx.InboundParams)
- require.NotNil(r, cctx.InboundParams.Sender)
- //require.NoError(r, cctx.InboundParams.)
- //cctx
+ expectedDeposit := amount.Sub(depositFee)
- // todo validate CCTX
+ require.Equal(r, sender.GetAddress().ToRaw(), cctx.InboundParams.Sender)
+ require.Equal(r, expectedDeposit.Uint64(), cctx.InboundParams.Amount.Uint64())
r.WaitForMinedCCTXFromIndex(cctx.Index)
@@ -74,28 +87,42 @@ func TestTONDeposit(r *runner.E2ERunner, args []string) {
balance, err := r.TONZRC20.BalanceOf(&bind.CallOpts{}, recipient)
require.NoError(r, err)
- r.Logger.Print("recipient's zEVM TON balance after deposit: %d", balance)
+ r.Logger.Print("Recipient's zEVM TON balance after deposit: %d", balance.Uint64())
- // todo check balance equals to cctx.amount
+ require.Equal(r, expectedDeposit.Uint64(), balance.Uint64())
}
-// use another method - this returns pending only pending OUTBOUNDS
-func catchPendingCCTX(r *runner.E2ERunner, chainID int64, timeout time.Duration) []*crosschainTypes.CrossChainTx {
- in := &crosschainTypes.QueryListPendingCctxRequest{ChainId: chainID}
-
- start := time.Now()
+func waitForSpecificCCTX(
+ r *runner.E2ERunner,
+ filter func(*crosschainTypes.CrossChainTx) bool,
+ timeout time.Duration,
+) ([]crosschainTypes.CrossChainTx, error) {
+ var (
+ ctx = r.Ctx
+ start = time.Now()
+ query = &crosschainTypes.QueryAllCctxRequest{}
+ out []crosschainTypes.CrossChainTx
+ )
for time.Since(start) < timeout {
- res, err := r.CctxClient.ListPendingCctx(r.Ctx, in)
- if err == nil && len(res.CrossChainTx) > 0 {
- return res.CrossChainTx
+ res, err := r.CctxClient.CctxAll(ctx, query)
+ if err != nil {
+ return nil, err
+ }
+
+ for i := range res.CrossChainTx {
+ tx := res.CrossChainTx[i]
+ if filter(tx) {
+ out = append(out, *tx)
+ }
+ }
+
+ if len(out) > 0 {
+ return out, nil
}
time.Sleep(time.Second)
}
- r.Logger.Error("Timeout waiting for pending CCTX for chain %d", chainID)
- r.FailNow()
-
- return nil
+ return nil, errors.New("timeout waiting for CCTX")
}
diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go
index 32fcb64b8e..7d67a1e42f 100644
--- a/e2e/runner/runner.go
+++ b/e2e/runner/runner.go
@@ -232,6 +232,7 @@ func (r *E2ERunner) CopyAddressesFrom(other *E2ERunner) (err error) {
r.ETHZRC20Addr = other.ETHZRC20Addr
r.BTCZRC20Addr = other.BTCZRC20Addr
r.SOLZRC20Addr = other.SOLZRC20Addr
+ r.TONZRC20Addr = other.TONZRC20Addr
r.UniswapV2FactoryAddr = other.UniswapV2FactoryAddr
r.UniswapV2RouterAddr = other.UniswapV2RouterAddr
r.ConnectorZEVMAddr = other.ConnectorZEVMAddr
@@ -277,6 +278,10 @@ func (r *E2ERunner) CopyAddressesFrom(other *E2ERunner) (err error) {
if err != nil {
return err
}
+ r.TONZRC20, err = zrc20.NewZRC20(r.TONZRC20Addr, r.ZEVMClient)
+ if err != nil {
+ return err
+ }
r.UniswapV2Factory, err = uniswapv2factory.NewUniswapV2Factory(r.UniswapV2FactoryAddr, r.ZEVMClient)
if err != nil {
return err
@@ -361,6 +366,7 @@ func (r *E2ERunner) PrintContractAddresses() {
r.Logger.Print("ERC20ZRC20: %s", r.ERC20ZRC20Addr.Hex())
r.Logger.Print("BTCZRC20: %s", r.BTCZRC20Addr.Hex())
r.Logger.Print("SOLZRC20: %s", r.SOLZRC20Addr.Hex())
+ r.Logger.Print("TONZRC20: %s", r.TONZRC20Addr.Hex())
r.Logger.Print("UniswapFactory: %s", r.UniswapV2FactoryAddr.Hex())
r.Logger.Print("UniswapRouter: %s", r.UniswapV2RouterAddr.Hex())
r.Logger.Print("ConnectorZEVM: %s", r.ConnectorZEVMAddr.Hex())
diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go
index 0137cb483b..5e5044fbe3 100644
--- a/e2e/txserver/zeta_tx_server.go
+++ b/e2e/txserver/zeta_tx_server.go
@@ -514,7 +514,7 @@ func (zts ZetaTxServer) DeployZRC20s(accountOperational, accountAdmin, erc20Addr
100_000,
))
if err != nil {
- return "", fmt.Errorf("failed to deploy ton zrc20: %s", err.Error())
+ return "", fmt.Errorf("failed to deploy ton zrc20: %w", err)
}
// deploy erc20 zrc20
diff --git a/pkg/contracts/ton/gateway_send.go b/pkg/contracts/ton/gateway_send.go
index a6b30f2745..a9c14583dc 100644
--- a/pkg/contracts/ton/gateway_send.go
+++ b/pkg/contracts/ton/gateway_send.go
@@ -16,11 +16,9 @@ type Sender interface {
Send(ctx context.Context, messages ...wallet.Sendable) error
}
+// see https://docs.ton.org/develop/smart-contracts/messages#message-modes
const (
- SendModeDefault = uint8(0)
-)
-
-const (
+ SendFlagSeparateFees = uint8(1)
SendFlagIgnoreErrors = uint8(2)
)
From 0a3fbad4713c83027b867af2eb97f29bfc77175d Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Thu, 3 Oct 2024 14:56:29 +0200
Subject: [PATCH 24/36] TON Deposit And Call E2E
---
cmd/zetae2e/local/local.go | 1 +
e2e/e2etests/e2etests.go | 11 ++-
e2e/e2etests/helpers.go | 5 ++
e2e/e2etests/test_ton_deposit.go | 58 ++--------------
e2e/e2etests/test_ton_deposit_and_call.go | 84 +++++++++++++++++++++++
e2e/runner/zeta.go | 39 ++++++++++-
pkg/contracts/ton/gateway_op.go | 33 +++++----
pkg/contracts/ton/gateway_send.go | 19 +++++
8 files changed, 185 insertions(+), 65 deletions(-)
create mode 100644 e2e/e2etests/test_ton_deposit_and_call.go
diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go
index 7e31d4a407..954bade116 100644
--- a/cmd/zetae2e/local/local.go
+++ b/cmd/zetae2e/local/local.go
@@ -395,6 +395,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
tonTests := []string{
e2etests.TestTONDepositName,
+ e2etests.TestTONDepositAndCallName,
}
eg.Go(tonTestRoutine(conf, deployerRunner, verbose, tonTests...))
diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go
index 1870f3a5a2..a41444c7de 100644
--- a/e2e/e2etests/e2etests.go
+++ b/e2e/e2etests/e2etests.go
@@ -65,7 +65,8 @@ const (
/**
* TON tests
*/
- TestTONDepositName = "ton_deposit"
+ TestTONDepositName = "ton_deposit"
+ TestTONDepositAndCallName = "ton_deposit_and_call"
/*
Bitcoin tests
@@ -440,6 +441,14 @@ var AllE2ETests = []runner.E2ETest{
},
TestTONDeposit,
),
+ runner.NewE2ETest(
+ TestTONDepositAndCallName,
+ "deposit TON into ZEVM and call a contract",
+ []runner.ArgDefinition{
+ {Description: "amount in nano tons", DefaultValue: "1000000000"}, // 1.0 TON
+ },
+ TestTONDepositAndCall,
+ ),
/*
Bitcoin tests
*/
diff --git a/e2e/e2etests/helpers.go b/e2e/e2etests/helpers.go
index a27bf23799..3389cd0475 100644
--- a/e2e/e2etests/helpers.go
+++ b/e2e/e2etests/helpers.go
@@ -4,6 +4,7 @@ import (
"math/big"
"strconv"
+ "cosmossdk.io/math"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil"
@@ -144,6 +145,10 @@ func parseBigInt(t require.TestingT, s string) *big.Int {
return v
}
+func parseUint(t require.TestingT, s string) math.Uint {
+ return math.NewUintFromBigInt(parseBigInt(t, s))
+}
+
// bigIntFromFloat64 takes float64 (e.g. 0.001) that represents btc amount
// and converts it to big.Int for downstream usage.
func btcAmountFromFloat64(t require.TestingT, amount float64) *big.Int {
diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go
index 8c1cdd037e..bc4b5c970d 100644
--- a/e2e/e2etests/test_ton_deposit.go
+++ b/e2e/e2etests/test_ton_deposit.go
@@ -1,7 +1,6 @@
package e2etests
import (
- "errors"
"time"
"cosmossdk.io/math"
@@ -22,19 +21,14 @@ import (
// https://docs.ton.org/develop/smart-contracts/guidelines/message-modes-cookbook
const tonDepositSendCode = toncontracts.SendFlagSeparateFees + toncontracts.SendFlagIgnoreErrors
-// TestTONDeposit (!) This boilerplate is a demonstration of E2E capabilities for TON integration
-// Actual Deposit test is not implemented yet.
func TestTONDeposit(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)
- // Given TON Localnet chain
- chain := chains.TONLocalnet
-
// Given deployer
- ctx, deployer := r.Ctx, r.TONDeployer
+ ctx, deployer, chain := r.Ctx, r.TONDeployer, chains.TONLocalnet
// Given amount
- amount := math.NewUintFromBigInt(parseBigInt(r, args[0]))
+ amount := parseUint(r, args[0])
// https://github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc#L28
// (will be optimized & dynamic in the future)
@@ -51,7 +45,7 @@ func TestTONDeposit(r *runner.E2ERunner, args []string) {
recipient := sample.EthAddress()
// ACT
- r.Logger.Print(
+ r.Logger.Info(
"Sending deposit of %s TON from %s to zEVM %s",
amount.String(),
sender.GetAddress().ToRaw(),
@@ -69,60 +63,22 @@ func TestTONDeposit(r *runner.E2ERunner, args []string) {
cctx.InboundParams.Sender == sender.GetAddress().ToRaw()
}
- cctxs, err := waitForSpecificCCTX(r, filter, time.Minute)
- require.NoError(r, err)
+ cctxs := r.WaitForSpecificCCTX(filter, time.Minute)
require.Len(r, cctxs, 1)
- // Check CCTX
- cctx := cctxs[0]
+ cctx := r.WaitForMinedCCTXFromIndex(cctxs[0].Index)
+ // Check CCTX
expectedDeposit := amount.Sub(depositFee)
require.Equal(r, sender.GetAddress().ToRaw(), cctx.InboundParams.Sender)
require.Equal(r, expectedDeposit.Uint64(), cctx.InboundParams.Amount.Uint64())
- r.WaitForMinedCCTXFromIndex(cctx.Index)
-
// Check sender's balance
balance, err := r.TONZRC20.BalanceOf(&bind.CallOpts{}, recipient)
require.NoError(r, err)
- r.Logger.Print("Recipient's zEVM TON balance after deposit: %d", balance.Uint64())
+ r.Logger.Info("Recipient's zEVM TON balance after deposit: %d", balance.Uint64())
require.Equal(r, expectedDeposit.Uint64(), balance.Uint64())
}
-
-func waitForSpecificCCTX(
- r *runner.E2ERunner,
- filter func(*crosschainTypes.CrossChainTx) bool,
- timeout time.Duration,
-) ([]crosschainTypes.CrossChainTx, error) {
- var (
- ctx = r.Ctx
- start = time.Now()
- query = &crosschainTypes.QueryAllCctxRequest{}
- out []crosschainTypes.CrossChainTx
- )
-
- for time.Since(start) < timeout {
- res, err := r.CctxClient.CctxAll(ctx, query)
- if err != nil {
- return nil, err
- }
-
- for i := range res.CrossChainTx {
- tx := res.CrossChainTx[i]
- if filter(tx) {
- out = append(out, *tx)
- }
- }
-
- if len(out) > 0 {
- return out, nil
- }
-
- time.Sleep(time.Second)
- }
-
- return nil, errors.New("timeout waiting for CCTX")
-}
diff --git a/e2e/e2etests/test_ton_deposit_and_call.go b/e2e/e2etests/test_ton_deposit_and_call.go
new file mode 100644
index 0000000000..a612794e9a
--- /dev/null
+++ b/e2e/e2etests/test_ton_deposit_and_call.go
@@ -0,0 +1,84 @@
+package e2etests
+
+import (
+ "time"
+
+ "cosmossdk.io/math"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/stretchr/testify/require"
+
+ "github.com/zeta-chain/node/e2e/runner"
+ "github.com/zeta-chain/node/e2e/runner/ton"
+ "github.com/zeta-chain/node/e2e/utils"
+ "github.com/zeta-chain/node/pkg/chains"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
+ testcontract "github.com/zeta-chain/node/testutil/contracts"
+ crosschainTypes "github.com/zeta-chain/node/x/crosschain/types"
+)
+
+func TestTONDepositAndCall(r *runner.E2ERunner, args []string) {
+ require.Len(r, args, 1)
+
+ // Given deployer
+ ctx, deployer, chain := r.Ctx, r.TONDeployer, chains.TONLocalnet
+
+ // Given amount
+ amount := parseUint(r, args[0])
+
+ // https://github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc#L28
+ // (will be optimized & dynamic in the future)
+ depositFee := math.NewUint(10_000_000)
+
+ // Given TON Gateway
+ gw := toncontracts.NewGateway(r.TONGateway)
+
+ // Given sample wallet with a balance of 50 TON
+ sender, err := deployer.CreateWallet(ctx, ton.TONCoins(50))
+ require.NoError(r, err)
+
+ // Given sample zEVM contract
+ contractAddr, _, contract, err := testcontract.DeployExample(r.ZEVMAuth, r.ZEVMClient)
+ require.NoError(r, err)
+ r.Logger.Info("Example zevm contract deployed at: %s", contractAddr.String())
+
+ // Given call data
+ callData := []byte("hello from TON!")
+
+ // ACT
+ r.Logger.Info(
+ "Sending deposit of %s TON from %s to zEVM %s and calling contract with %q",
+ amount.String(),
+ sender.GetAddress().ToRaw(),
+ contractAddr.Hex(),
+ string(callData),
+ )
+
+ err = gw.SendDepositAndCall(ctx, sender, amount, contractAddr, callData, tonDepositSendCode)
+
+ // ASSERT
+ require.NoError(r, err)
+
+ // Wait for CCTX mining
+ filter := func(cctx *crosschainTypes.CrossChainTx) bool {
+ return cctx.InboundParams.SenderChainId == chain.ChainId &&
+ cctx.InboundParams.Sender == sender.GetAddress().ToRaw()
+ }
+
+ cctxs := r.WaitForSpecificCCTX(filter, time.Minute)
+ require.Len(r, cctxs, 1)
+
+ r.WaitForMinedCCTXFromIndex(cctxs[0].Index)
+
+ expectedDeposit := amount.Sub(depositFee)
+
+ // check if example contract has been called, bar value should be set to amount
+ utils.MustHaveCalledExampleContract(r, contract, expectedDeposit.BigInt())
+
+ // Check sender's balance
+ balance, err := r.TONZRC20.BalanceOf(&bind.CallOpts{}, contractAddr)
+ require.NoError(r, err)
+
+ r.Logger.Info("Contract's zEVM TON balance after deposit: %d", balance.Uint64())
+
+ require.Equal(r, expectedDeposit.Uint64(), balance.Uint64())
+}
diff --git a/e2e/runner/zeta.go b/e2e/runner/zeta.go
index cec59219cd..981a63ae33 100644
--- a/e2e/runner/zeta.go
+++ b/e2e/runner/zeta.go
@@ -67,12 +67,49 @@ func (r *E2ERunner) WaitForMinedCCTX(txHash ethcommon.Hash) {
}
// WaitForMinedCCTXFromIndex waits for a cctx to be mined from its index
-func (r *E2ERunner) WaitForMinedCCTXFromIndex(index string) {
+func (r *E2ERunner) WaitForMinedCCTXFromIndex(index string) *types.CrossChainTx {
r.Lock()
defer r.Unlock()
cctx := utils.WaitCCTXMinedByIndex(r.Ctx, index, r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, types.CctxStatus_OutboundMined)
+
+ return cctx
+}
+
+func (r *E2ERunner) WaitForSpecificCCTX(
+ filter func(*types.CrossChainTx) bool,
+ timeout time.Duration,
+) []types.CrossChainTx {
+ var (
+ ctx = r.Ctx
+ start = time.Now()
+ query = &types.QueryAllCctxRequest{}
+ out []types.CrossChainTx
+ )
+
+ for time.Since(start) < timeout {
+ res, err := r.CctxClient.CctxAll(ctx, query)
+ require.NoError(r, err)
+
+ for i := range res.CrossChainTx {
+ tx := res.CrossChainTx[i]
+ if filter(tx) {
+ out = append(out, *tx)
+ }
+ }
+
+ if len(out) > 0 {
+ return out
+ }
+
+ time.Sleep(time.Second)
+ }
+
+ r.Logger.Error("WaitForSpecificCCTX: No CCTX found. Timed out")
+ r.FailNow()
+
+ return nil
}
// SendZetaOnEvm sends ZETA to an address on EVM
diff --git a/pkg/contracts/ton/gateway_op.go b/pkg/contracts/ton/gateway_op.go
index 9b77120a65..7d711ab89c 100644
--- a/pkg/contracts/ton/gateway_op.go
+++ b/pkg/contracts/ton/gateway_op.go
@@ -1,6 +1,8 @@
package ton
import (
+ "errors"
+
"cosmossdk.io/math"
eth "github.com/ethereum/go-ethereum/common"
"github.com/tonkeeper/tongo/boc"
@@ -81,20 +83,9 @@ func (d DepositAndCall) Memo() []byte {
// AsBody casts struct to internal message body.
func (d DepositAndCall) AsBody() (*boc.Cell, error) {
- callDataCell, err := MarshalSnakeCell(d.CallData)
- if err != nil {
- return nil, err
- }
-
b := boc.NewCell()
- err = ErrCollect(
- b.WriteUint(uint64(OpDepositAndCall), sizeOpCode),
- b.WriteUint(0, sizeQueryID),
- b.WriteBytes(d.Recipient.Bytes()),
- b.AddRef(callDataCell),
- )
- return b, err
+ return b, writeDepositAndCallBody(b, d.Recipient, d.CallData)
}
func writeDepositBody(b *boc.Cell, recipient eth.Address) error {
@@ -104,3 +95,21 @@ func writeDepositBody(b *boc.Cell, recipient eth.Address) error {
b.WriteBytes(recipient.Bytes()),
)
}
+
+func writeDepositAndCallBody(b *boc.Cell, recipient eth.Address, callData []byte) error {
+ if len(callData) == 0 {
+ return errors.New("call data is empty")
+ }
+
+ callDataCell, err := MarshalSnakeCell(callData)
+ if err != nil {
+ return err
+ }
+
+ return ErrCollect(
+ b.WriteUint(uint64(OpDepositAndCall), sizeOpCode),
+ b.WriteUint(0, sizeQueryID),
+ b.WriteBytes(recipient.Bytes()),
+ b.AddRef(callDataCell),
+ )
+}
diff --git a/pkg/contracts/ton/gateway_send.go b/pkg/contracts/ton/gateway_send.go
index a9c14583dc..5dd9c21340 100644
--- a/pkg/contracts/ton/gateway_send.go
+++ b/pkg/contracts/ton/gateway_send.go
@@ -39,6 +39,25 @@ func (gw *Gateway) SendDeposit(
return gw.send(ctx, s, amount, body, sendMode)
}
+// SendDepositAndCall sends a deposit operation to the gateway on behalf of the sender
+// with a callData to the recipient.
+func (gw *Gateway) SendDepositAndCall(
+ ctx context.Context,
+ s Sender,
+ amount math.Uint,
+ zevmRecipient eth.Address,
+ callData []byte,
+ sendMode uint8,
+) error {
+ body := boc.NewCell()
+
+ if err := writeDepositAndCallBody(body, zevmRecipient, callData); err != nil {
+ return errors.Wrap(err, "failed to write depositAndCall body")
+ }
+
+ return gw.send(ctx, s, amount, body, sendMode)
+}
+
func (gw *Gateway) send(ctx context.Context, s Sender, amount math.Uint, body *boc.Cell, sendMode uint8) error {
if body == nil {
return errors.New("body is nil")
From 6eda977d5132c0a3d2dfacd3271650d7164e732e Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Thu, 3 Oct 2024 15:12:08 +0200
Subject: [PATCH 25/36] Merge fixes
---
pkg/ticker/ticker_test.go | 4 ++--
zetaclient/config/config_chain.go | 7 +++++++
zetaclient/config/types_test.go | 4 +++-
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/pkg/ticker/ticker_test.go b/pkg/ticker/ticker_test.go
index 3d28dbd8a3..60d6c74dc8 100644
--- a/pkg/ticker/ticker_test.go
+++ b/pkg/ticker/ticker_test.go
@@ -151,7 +151,7 @@ func TestTicker(t *testing.T) {
// ASSERT
assert.ErrorContains(t, err, "panic during ticker run: oops")
// assert that we get error with the correct line number
- assert.ErrorContains(t, err, "ticker_test.go:142")
+ assert.ErrorContains(t, err, "ticker_test.go:145")
})
t.Run("Nil panic", func(t *testing.T) {
@@ -176,7 +176,7 @@ func TestTicker(t *testing.T) {
"panic during ticker run: runtime error: invalid memory address or nil pointer dereference",
)
// assert that we get error with the correct line number
- assert.ErrorContains(t, err, "ticker_test.go:162")
+ assert.ErrorContains(t, err, "ticker_test.go:165")
})
t.Run("Run as a single call", func(t *testing.T) {
diff --git a/zetaclient/config/config_chain.go b/zetaclient/config/config_chain.go
index d7af8eee4c..6f17153b52 100644
--- a/zetaclient/config/config_chain.go
+++ b/zetaclient/config/config_chain.go
@@ -88,3 +88,10 @@ func evmChainsConfigs() map[int64]EVMConfig {
},
}
}
+
+// btcChainsConfigs contains BTC chain configs
+func btcChainsConfigs() map[int64]BTCConfig {
+ return map[int64]BTCConfig{
+ chains.BitcoinRegtest.ChainId: bitcoinConfigRegnet(),
+ }
+}
diff --git a/zetaclient/config/types_test.go b/zetaclient/config/types_test.go
index c57fd002e0..02f7eb5a6f 100644
--- a/zetaclient/config/types_test.go
+++ b/zetaclient/config/types_test.go
@@ -128,6 +128,8 @@ func Test_StringMasked(t *testing.T) {
// create config with defaults
cfg := config.New(true)
+ cfg.SolanaConfig.Endpoint += "?api-key=123"
+
// mask the config JSON string
masked := cfg.StringMasked()
require.NotEmpty(t, masked)
@@ -137,5 +139,5 @@ func Test_StringMasked(t *testing.T) {
require.Contains(t, masked, "BTCChainConfigs")
// should not contain endpoint
- require.NotContains(t, masked, "http")
+ require.NotContains(t, masked, "?api-key=123")
}
From 7b2f232438f1cfbaa1890198436b96065e2f7b35 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Thu, 3 Oct 2024 15:26:14 +0200
Subject: [PATCH 26/36] Update changelog
---
changelog.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/changelog.md b/changelog.md
index dc83cb839b..f76255c14f 100644
--- a/changelog.md
+++ b/changelog.md
@@ -16,6 +16,7 @@
* [2911](https://github.com/zeta-chain/node/pull/2911) - add chain static information for btc testnet4
* [2904](https://github.com/zeta-chain/node/pull/2904) - integrate authenticated calls smart contract functionality into protocol
* [2919](https://github.com/zeta-chain/node/pull/2919) - add inbound sender to revert context
+* [2896](https://github.com/zeta-chain/node/pull/2896) - add TON inbound observation
### Refactor
From 142778b594ea3bbffd0972a4fe0ce4f06973bd96 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Thu, 3 Oct 2024 15:35:23 +0200
Subject: [PATCH 27/36] gosec
---
pkg/contracts/ton/gateway.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go
index 201e8abb69..4a85925b9e 100644
--- a/pkg/contracts/ton/gateway.go
+++ b/pkg/contracts/ton/gateway.go
@@ -126,10 +126,12 @@ func (gw *Gateway) parseInbound(tx ton.Transaction) (*Transaction, error) {
case OpDepositAndCall:
content, errContent = parseDepositAndCall(tx, sender, body)
default:
+ // #nosec G115 always in range
return nil, errors.Wrapf(ErrUnknownOp, "op code %d", int64(op))
}
if errContent != nil {
+ // #nosec G115 always in range
return nil, errors.Wrapf(ErrParse, "unable to parse content for op code %d: %s", int64(op), errContent.Error())
}
From 3f8f98958819c72911d6a78660c10490c14e72c6 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Thu, 3 Oct 2024 20:32:17 +0200
Subject: [PATCH 28/36] Improve testing
---
pkg/chains/chains_test.go | 5 ++
zetaclient/chains/interfaces/interfaces.go | 8 ++
.../chains/ton/liteapi/client_live_test.go | 52 ++++++++----
zetaclient/chains/ton/liteapi/client_test.go | 82 +++++++++++++++++++
4 files changed, 129 insertions(+), 18 deletions(-)
diff --git a/pkg/chains/chains_test.go b/pkg/chains/chains_test.go
index 270a26b045..73c3521ffa 100644
--- a/pkg/chains/chains_test.go
+++ b/pkg/chains/chains_test.go
@@ -135,6 +135,11 @@ func TestChainListByNetwork(t *testing.T) {
chains.Network_solana,
[]chains.Chain{chains.SolanaMainnet, chains.SolanaDevnet, chains.SolanaLocalnet},
},
+ {
+ "TON",
+ chains.Network_ton,
+ []chains.Chain{chains.TONMainnet, chains.TONTestnet, chains.TONLocalnet},
+ },
}
for _, lt := range listTests {
diff --git a/zetaclient/chains/interfaces/interfaces.go b/zetaclient/chains/interfaces/interfaces.go
index 0e1b938c64..c89a77e4b8 100644
--- a/zetaclient/chains/interfaces/interfaces.go
+++ b/zetaclient/chains/interfaces/interfaces.go
@@ -39,12 +39,20 @@ const (
// ChainObserver is the interface for chain observer
type ChainObserver interface {
+ // Start starts the observer
Start(ctx context.Context)
+
+ // Stop stops the observer
Stop()
+ // ChainParams returns observer chain params (might be out of date with zetacore)
ChainParams() observertypes.ChainParams
+
+ // SetChainParams sets observer chain params
SetChainParams(observertypes.ChainParams)
+ // VoteOutboundIfConfirmed checks outbound status and returns (continueKeySign, error)
+ // todo we should make this simpler.
VoteOutboundIfConfirmed(ctx context.Context, cctx *crosschaintypes.CrossChainTx) (bool, error)
}
diff --git a/zetaclient/chains/ton/liteapi/client_live_test.go b/zetaclient/chains/ton/liteapi/client_live_test.go
index 40c37dce71..acf211736b 100644
--- a/zetaclient/chains/ton/liteapi/client_live_test.go
+++ b/zetaclient/chains/ton/liteapi/client_live_test.go
@@ -28,32 +28,48 @@ func TestClient(t *testing.T) {
)
t.Run("GetFirstTransaction", func(t *testing.T) {
- // ARRANGE
- // Given sample account id (a dev wallet)
- // https://tonviewer.com/UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr?section=transactions
- accountID, err := ton.ParseAccountID("UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr")
- require.NoError(t, err)
+ t.Run("Account doesn't exist", func(t *testing.T) {
+ // ARRANGE
+ accountID, err := ton.ParseAccountID("0:55798cb7b87168251a7c39f6806b8c202f6caa0f617a76f4070b3fdacfd056a2")
+ require.NoError(t, err)
- // Given expected hash for the first tx
- const expect = "b73df4853ca02a040df46f56635d6b8f49b554d5f556881ab389111bbfce4498"
+ // ACT
+ tx, scrolled, err := client.GetFirstTransaction(ctx, accountID)
- // as of 2024-09-18
- const expectedTransactions = 23
+ // ASSERT
+ require.ErrorContains(t, err, "account is not active")
+ require.Zero(t, scrolled)
+ require.Nil(t, tx)
+ })
- start := time.Now()
+ t.Run("All good", func(t *testing.T) {
+ // ARRANGE
+ // Given sample account id (a dev wallet)
+ // https://tonviewer.com/UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr?section=transactions
+ accountID, err := ton.ParseAccountID("UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr")
+ require.NoError(t, err)
- // ACT
- tx, scrolled, err := client.GetFirstTransaction(ctx, accountID)
+ // Given expected hash for the first tx
+ const expect = "b73df4853ca02a040df46f56635d6b8f49b554d5f556881ab389111bbfce4498"
- finish := time.Since(start)
+ // as of 2024-09-18
+ const expectedTransactions = 23
- // ASSERT
- require.NoError(t, err)
+ start := time.Now()
+
+ // ACT
+ tx, scrolled, err := client.GetFirstTransaction(ctx, accountID)
+
+ finish := time.Since(start)
+
+ // ASSERT
+ require.NoError(t, err)
- assert.GreaterOrEqual(t, scrolled, expectedTransactions)
- assert.Equal(t, expect, tx.Hash().Hex())
+ assert.GreaterOrEqual(t, scrolled, expectedTransactions)
+ assert.Equal(t, expect, tx.Hash().Hex())
- t.Logf("Time taken %s; transactions scanned: %d", finish.String(), scrolled)
+ t.Logf("Time taken %s; transactions scanned: %d", finish.String(), scrolled)
+ })
})
t.Run("GetTransactionsUntil", func(t *testing.T) {
diff --git a/zetaclient/chains/ton/liteapi/client_test.go b/zetaclient/chains/ton/liteapi/client_test.go
index 3ab56422a5..a1148540be 100644
--- a/zetaclient/chains/ton/liteapi/client_test.go
+++ b/zetaclient/chains/ton/liteapi/client_test.go
@@ -16,3 +16,85 @@ func TestHashes(t *testing.T) {
require.Equal(t, "e02b8c7cec103e08175ade8106619a8908707623c31451df2a68497c7d23d15a", hash.Hex())
require.Equal(t, sample, TransactionHashToString(lt, hash))
}
+
+func TestTransactionHashFromString(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ raw string
+ error bool
+ lt uint64
+ hash string
+ }{
+ {
+ name: "real example",
+ raw: "163000003:d0415f655644db6ee1260b1fa48e9f478e938823e8b293054fbae1f3511b77c5",
+ lt: 163000003,
+ hash: "d0415f655644db6ee1260b1fa48e9f478e938823e8b293054fbae1f3511b77c5",
+ },
+ {
+ name: "zero lt",
+ raw: "0:0000000000000000000000000000000000000000000000000000000000000000",
+ lt: 0,
+ hash: "0000000000000000000000000000000000000000000000000000000000000000",
+ },
+ {
+ name: "big lt",
+ raw: "999999999999:fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0",
+ lt: 999_999_999_999,
+ hash: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0",
+ },
+ {
+ name: "missing colon",
+ raw: "123456abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef",
+ error: true,
+ },
+ {
+ name: "missing logical time",
+ raw: ":abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef",
+ error: true,
+ },
+ {
+ name: "hash length",
+ raw: "123456:abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcde",
+ error: true,
+ },
+ {
+ name: "non-numeric logical time",
+ raw: "notanumber:abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef",
+ error: true,
+ },
+ {
+ name: "non-hex hash",
+ raw: "123456:xyz123xyz123xyz123xyz123xyz123xyz123xyz123xyz123xyz123xyz123xyz123",
+ error: true,
+ },
+ {
+ name: "empty string",
+ raw: "",
+ error: true,
+ },
+ {
+ name: "Invalid - only logical time, no hash",
+ raw: "123456:",
+ error: true,
+ },
+ {
+ name: "Invalid - too many parts (extra colon)",
+ raw: "123456:abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef:extra",
+ error: true,
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ lt, hash, err := TransactionHashFromString(tt.raw)
+
+ if tt.error {
+ require.Error(t, err)
+ return
+ }
+
+ require.NoError(t, err)
+ require.Equal(t, tt.lt, lt)
+ require.Equal(t, hash.Hex(), tt.hash)
+ })
+ }
+}
From f5a4701dcbfec24408d727fc9cb64895ac25907f Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Thu, 3 Oct 2024 20:55:58 +0200
Subject: [PATCH 29/36] Simplify ticker.Stop(). Leverage ctx.cancel()
---
pkg/ticker/ticker.go | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/pkg/ticker/ticker.go b/pkg/ticker/ticker.go
index ff2bd7284c..2a5a7edff1 100644
--- a/pkg/ticker/ticker.go
+++ b/pkg/ticker/ticker.go
@@ -43,10 +43,9 @@ type Task func(ctx context.Context, t *Ticker) error
// Ticker represents a ticker that will run a function periodically.
// It also invokes BEFORE ticker starts.
type Ticker struct {
- interval time.Duration
- ticker *time.Ticker
- task Task
- internalStopChan chan struct{}
+ interval time.Duration
+ ticker *time.Ticker
+ task Task
// runnerMu is a mutex to prevent double run
runnerMu sync.Mutex
@@ -54,7 +53,8 @@ type Ticker struct {
// stateMu is a mutex to prevent concurrent SetInterval calls
stateMu sync.Mutex
- stopped bool
+ stopped bool
+ ctxCancel context.CancelFunc
externalStopChan <-chan struct{}
logger zerolog.Logger
@@ -120,8 +120,8 @@ func (t *Ticker) Run(ctx context.Context) (err error) {
defer t.runnerMu.Unlock()
// setup
+ ctx, t.ctxCancel = context.WithCancel(ctx)
t.ticker = time.NewTicker(t.interval)
- t.internalStopChan = make(chan struct{})
t.stopped = false
// initial run
@@ -133,16 +133,21 @@ func (t *Ticker) Run(ctx context.Context) (err error) {
for {
select {
case <-ctx.Done():
+ // if task is finished (i.e. last tick completed BEFORE ticker.Stop(),
+ // then we need to return nil)
+ if t.stopped {
+ return nil
+ }
return ctx.Err()
case <-t.ticker.C:
+ // If another goroutine calls ticker.Stop() while the current tick is running,
+ // Then it's okay to return ctx error
if err := t.task(ctx, t); err != nil {
return fmt.Errorf("ticker task failed: %w", err)
}
case <-t.externalStopChan:
t.Stop()
return nil
- case <-t.internalStopChan:
- return nil
}
}
}
@@ -167,11 +172,11 @@ func (t *Ticker) Stop() {
defer t.stateMu.Unlock()
// noop
- if t.stopped || t.internalStopChan == nil {
+ if t.stopped {
return
}
- close(t.internalStopChan)
+ t.ctxCancel()
t.stopped = true
t.ticker.Stop()
From 2de851dc020b68814a952b1c60a533cc93593676 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 4 Oct 2024 14:55:30 +0200
Subject: [PATCH 30/36] Simplify E2E
---
e2e/e2etests/test_ton_deposit.go | 28 ++--------
e2e/e2etests/test_ton_deposit_and_call.go | 23 ++-------
e2e/runner/runner.go | 4 +-
e2e/runner/setup_ton.go | 3 +-
e2e/runner/ton.go | 59 ++++++++++++++++++++++
e2e/runner/zeta.go | 21 ++++----
x/observer/keeper/msg_server_vote_blame.go | 6 +--
7 files changed, 83 insertions(+), 61 deletions(-)
create mode 100644 e2e/runner/ton.go
diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go
index bc4b5c970d..b4c7a32168 100644
--- a/e2e/e2etests/test_ton_deposit.go
+++ b/e2e/e2etests/test_ton_deposit.go
@@ -10,17 +10,10 @@ import (
"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/runner/ton"
"github.com/zeta-chain/node/pkg/chains"
- toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
"github.com/zeta-chain/node/testutil/sample"
- crosschainTypes "github.com/zeta-chain/node/x/crosschain/types"
+ cctypes "github.com/zeta-chain/node/x/crosschain/types"
)
-// we need to use this send mode due to how wallet V5 works
-//
-// https://github.com/tonkeeper/w5/blob/main/contracts/wallet_v5.fc#L82
-// https://docs.ton.org/develop/smart-contracts/guidelines/message-modes-cookbook
-const tonDepositSendCode = toncontracts.SendFlagSeparateFees + toncontracts.SendFlagIgnoreErrors
-
func TestTONDeposit(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)
@@ -34,9 +27,6 @@ func TestTONDeposit(r *runner.E2ERunner, args []string) {
// (will be optimized & dynamic in the future)
depositFee := math.NewUint(10_000_000)
- // Given TON Gateway
- gw := toncontracts.NewGateway(r.TONGateway)
-
// Given sample wallet with a balance of 50 TON
sender, err := deployer.CreateWallet(ctx, ton.TONCoins(50))
require.NoError(r, err)
@@ -45,28 +35,18 @@ func TestTONDeposit(r *runner.E2ERunner, args []string) {
recipient := sample.EthAddress()
// ACT
- r.Logger.Info(
- "Sending deposit of %s TON from %s to zEVM %s",
- amount.String(),
- sender.GetAddress().ToRaw(),
- recipient.Hex(),
- )
-
- err = gw.SendDeposit(ctx, sender, amount, recipient, tonDepositSendCode)
+ err = r.TONDeposit(sender, amount, recipient)
// ASSERT
require.NoError(r, err)
// Wait for CCTX mining
- filter := func(cctx *crosschainTypes.CrossChainTx) bool {
+ filter := func(cctx *cctypes.CrossChainTx) bool {
return cctx.InboundParams.SenderChainId == chain.ChainId &&
cctx.InboundParams.Sender == sender.GetAddress().ToRaw()
}
- cctxs := r.WaitForSpecificCCTX(filter, time.Minute)
- require.Len(r, cctxs, 1)
-
- cctx := r.WaitForMinedCCTXFromIndex(cctxs[0].Index)
+ cctx := r.WaitForSpecificCCTX(filter, time.Minute)
// Check CCTX
expectedDeposit := amount.Sub(depositFee)
diff --git a/e2e/e2etests/test_ton_deposit_and_call.go b/e2e/e2etests/test_ton_deposit_and_call.go
index a612794e9a..b385b19f49 100644
--- a/e2e/e2etests/test_ton_deposit_and_call.go
+++ b/e2e/e2etests/test_ton_deposit_and_call.go
@@ -11,9 +11,8 @@ import (
"github.com/zeta-chain/node/e2e/runner/ton"
"github.com/zeta-chain/node/e2e/utils"
"github.com/zeta-chain/node/pkg/chains"
- toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
testcontract "github.com/zeta-chain/node/testutil/contracts"
- crosschainTypes "github.com/zeta-chain/node/x/crosschain/types"
+ cctypes "github.com/zeta-chain/node/x/crosschain/types"
)
func TestTONDepositAndCall(r *runner.E2ERunner, args []string) {
@@ -29,9 +28,6 @@ func TestTONDepositAndCall(r *runner.E2ERunner, args []string) {
// (will be optimized & dynamic in the future)
depositFee := math.NewUint(10_000_000)
- // Given TON Gateway
- gw := toncontracts.NewGateway(r.TONGateway)
-
// Given sample wallet with a balance of 50 TON
sender, err := deployer.CreateWallet(ctx, ton.TONCoins(50))
require.NoError(r, err)
@@ -45,29 +41,18 @@ func TestTONDepositAndCall(r *runner.E2ERunner, args []string) {
callData := []byte("hello from TON!")
// ACT
- r.Logger.Info(
- "Sending deposit of %s TON from %s to zEVM %s and calling contract with %q",
- amount.String(),
- sender.GetAddress().ToRaw(),
- contractAddr.Hex(),
- string(callData),
- )
-
- err = gw.SendDepositAndCall(ctx, sender, amount, contractAddr, callData, tonDepositSendCode)
+ err = r.TONDepositAndCall(sender, amount, contractAddr, callData)
// ASSERT
require.NoError(r, err)
// Wait for CCTX mining
- filter := func(cctx *crosschainTypes.CrossChainTx) bool {
+ filter := func(cctx *cctypes.CrossChainTx) bool {
return cctx.InboundParams.SenderChainId == chain.ChainId &&
cctx.InboundParams.Sender == sender.GetAddress().ToRaw()
}
- cctxs := r.WaitForSpecificCCTX(filter, time.Minute)
- require.Len(r, cctxs, 1)
-
- r.WaitForMinedCCTXFromIndex(cctxs[0].Index)
+ r.WaitForSpecificCCTX(filter, time.Minute)
expectedDeposit := amount.Sub(depositFee)
diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go
index ca3e633a55..03cafb6fc4 100644
--- a/e2e/runner/runner.go
+++ b/e2e/runner/runner.go
@@ -18,7 +18,6 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
- "github.com/tonkeeper/tongo/ton"
"github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol"
zetaeth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zeta.eth.sol"
zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol"
@@ -40,6 +39,7 @@ import (
"github.com/zeta-chain/node/e2e/txserver"
"github.com/zeta-chain/node/e2e/utils"
"github.com/zeta-chain/node/pkg/contracts/testdappv2"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
authoritytypes "github.com/zeta-chain/node/x/authority/types"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
fungibletypes "github.com/zeta-chain/node/x/fungible/types"
@@ -73,7 +73,7 @@ type E2ERunner struct {
BTCDeployerAddress *btcutil.AddressWitnessPubKeyHash
SolanaDeployerAddress solana.PublicKey
TONDeployer *tonrunner.Deployer
- TONGateway ton.AccountID
+ TONGateway *toncontracts.Gateway
// all clients.
// a reference to this type is required to enable creating a new E2ERunner.
diff --git a/e2e/runner/setup_ton.go b/e2e/runner/setup_ton.go
index 19e91d2608..62d89c3329 100644
--- a/e2e/runner/setup_ton.go
+++ b/e2e/runner/setup_ton.go
@@ -10,6 +10,7 @@ import (
"github.com/zeta-chain/node/e2e/utils"
"github.com/zeta-chain/node/pkg/chains"
"github.com/zeta-chain/node/pkg/constant"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
observertypes "github.com/zeta-chain/node/x/observer/types"
)
@@ -65,7 +66,7 @@ func (r *E2ERunner) SetupTON() error {
}
r.TONDeployer = deployer
- r.TONGateway = gwAccount.ID
+ r.TONGateway = toncontracts.NewGateway(gwAccount.ID)
return r.ensureTONChainParams(gwAccount)
}
diff --git a/e2e/runner/ton.go b/e2e/runner/ton.go
new file mode 100644
index 0000000000..8746e25977
--- /dev/null
+++ b/e2e/runner/ton.go
@@ -0,0 +1,59 @@
+package runner
+
+import (
+ "cosmossdk.io/math"
+ eth "github.com/ethereum/go-ethereum/common"
+ "github.com/stretchr/testify/require"
+ "github.com/tonkeeper/tongo/wallet"
+
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
+)
+
+// we need to use this send mode due to how wallet V5 works
+//
+// https://github.com/tonkeeper/w5/blob/main/contracts/wallet_v5.fc#L82
+// https://docs.ton.org/develop/smart-contracts/guidelines/message-modes-cookbook
+const tonDepositSendCode = toncontracts.SendFlagSeparateFees + toncontracts.SendFlagIgnoreErrors
+
+// TONDeposit deposit TON to Gateway contract
+func (r *E2ERunner) TONDeposit(sender *wallet.Wallet, amount math.Uint, zevmRecipient eth.Address) error {
+ require.NotNil(r, r.TONGateway, "TON Gateway is not initialized")
+
+ require.NotNil(r, sender, "Sender wallet is nil")
+ require.False(r, amount.IsZero())
+ require.NotEqual(r, (eth.Address{}).String(), zevmRecipient.String())
+
+ r.Logger.Info(
+ "Sending deposit of %s TON from %s to zEVM %s",
+ amount.String(),
+ sender.GetAddress().ToRaw(),
+ zevmRecipient.Hex(),
+ )
+
+ return r.TONGateway.SendDeposit(r.Ctx, sender, amount, zevmRecipient, tonDepositSendCode)
+}
+
+// TONDepositAndCall deposit TON to Gateway contract with call data.
+func (r *E2ERunner) TONDepositAndCall(
+ sender *wallet.Wallet,
+ amount math.Uint,
+ zevmRecipient eth.Address,
+ callData []byte,
+) error {
+ require.NotNil(r, r.TONGateway, "TON Gateway is not initialized")
+
+ require.NotNil(r, sender, "Sender wallet is nil")
+ require.False(r, amount.IsZero())
+ require.NotEqual(r, (eth.Address{}).String(), zevmRecipient.String())
+ require.NotEmpty(r, callData)
+
+ r.Logger.Info(
+ "Sending deposit of %s TON from %s to zEVM %s and calling contract with %q",
+ amount.String(),
+ sender.GetAddress().ToRaw(),
+ zevmRecipient.Hex(),
+ string(callData),
+ )
+
+ return r.TONGateway.SendDepositAndCall(r.Ctx, sender, amount, zevmRecipient, callData, tonDepositSendCode)
+}
diff --git a/e2e/runner/zeta.go b/e2e/runner/zeta.go
index 126f718ef3..5b977a9a31 100644
--- a/e2e/runner/zeta.go
+++ b/e2e/runner/zeta.go
@@ -6,6 +6,7 @@ import (
"time"
"github.com/cenkalti/backoff/v4"
+ query "github.com/cosmos/cosmos-sdk/types/query"
ethcommon "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
@@ -106,32 +107,30 @@ func (r *E2ERunner) WaitForMinedCCTXFromIndex(index string) *types.CrossChainTx
return cctx
}
+// WaitForSpecificCCTX scans for cctx by filters and ensures it's mined
func (r *E2ERunner) WaitForSpecificCCTX(
filter func(*types.CrossChainTx) bool,
timeout time.Duration,
-) []types.CrossChainTx {
+) *types.CrossChainTx {
var (
- ctx = r.Ctx
- start = time.Now()
- query = &types.QueryAllCctxRequest{}
- out []types.CrossChainTx
+ ctx = r.Ctx
+ start = time.Now()
+ reqQuery = &types.QueryAllCctxRequest{
+ Pagination: &query.PageRequest{Reverse: true},
+ }
)
for time.Since(start) < timeout {
- res, err := r.CctxClient.CctxAll(ctx, query)
+ res, err := r.CctxClient.CctxAll(ctx, reqQuery)
require.NoError(r, err)
for i := range res.CrossChainTx {
tx := res.CrossChainTx[i]
if filter(tx) {
- out = append(out, *tx)
+ return r.WaitForMinedCCTXFromIndex(tx.Index)
}
}
- if len(out) > 0 {
- return out
- }
-
time.Sleep(time.Second)
}
diff --git a/x/observer/keeper/msg_server_vote_blame.go b/x/observer/keeper/msg_server_vote_blame.go
index 74a3654657..d320ff54db 100644
--- a/x/observer/keeper/msg_server_vote_blame.go
+++ b/x/observer/keeper/msg_server_vote_blame.go
@@ -6,7 +6,7 @@ import (
sdkerrors "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
- crosschainTypes "github.com/zeta-chain/node/x/crosschain/types"
+ cctypes "github.com/zeta-chain/node/x/crosschain/types"
"github.com/zeta-chain/node/x/observer/types"
)
@@ -21,9 +21,7 @@ func (k msgServer) VoteBlame(
// GetChainFromChainID makes sure we are getting only supported chains , if a chain support has been turned on using gov proposal, this function returns nil
observationChain, found := k.GetSupportedChainFromChainID(ctx, msg.ChainId)
if !found {
- return nil, sdkerrors.Wrapf(
- crosschainTypes.ErrUnsupportedChain,
- "%s, ChainID %d", voteBlameID, msg.ChainId)
+ return nil, sdkerrors.Wrapf(cctypes.ErrUnsupportedChain, "%s, ChainID %d", voteBlameID, msg.ChainId)
}
if ok := k.IsNonTombstonedObserver(ctx, msg.Creator); !ok {
From d25a79a4d3e500a5050109736f00731626a46f69 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 4 Oct 2024 15:32:46 +0200
Subject: [PATCH 31/36] Simplify liteapi semantics
---
pkg/chains/chain.go | 2 +-
zetaclient/chains/ton/config.go | 3 +-
zetaclient/chains/ton/liteapi/client.go | 35 +++++++++++--------
.../chains/ton/liteapi/client_live_test.go | 2 +-
zetaclient/chains/ton/observer/inbound.go | 7 ++--
.../chains/ton/observer/inbound_test.go | 10 +++---
zetaclient/chains/ton/observer/observer.go | 2 +-
.../chains/ton/observer/observer_test.go | 4 +--
zetaclient/orchestrator/bootstrap.go | 2 +-
zetaclient/testutils/mocks/ton_liteclient.go | 6 ++--
10 files changed, 39 insertions(+), 34 deletions(-)
diff --git a/pkg/chains/chain.go b/pkg/chains/chain.go
index 819e9ba191..3cb8650714 100644
--- a/pkg/chains/chain.go
+++ b/pkg/chains/chain.go
@@ -94,7 +94,7 @@ func (chain Chain) IsBitcoinChain() bool {
}
func (chain Chain) IsTONChain() bool {
- return IsTONChain(chain.ChainId, []Chain{chain})
+ return chain.Consensus == Consensus_catchain_consensus
}
// DecodeAddressFromChainID decode the address string to bytes
diff --git a/zetaclient/chains/ton/config.go b/zetaclient/chains/ton/config.go
index a8578936a3..731287756e 100644
--- a/zetaclient/chains/ton/config.go
+++ b/zetaclient/chains/ton/config.go
@@ -44,7 +44,8 @@ func ConfigFromPath(path string) (*GlobalConfigurationFile, error) {
return config.ParseConfigFile(path)
}
-func ConfigFromAny(ctx context.Context, urlOrPath string) (*GlobalConfigurationFile, error) {
+// ConfigFromSource returns a parsed configuration file from a URL or a file path.
+func ConfigFromSource(ctx context.Context, urlOrPath string) (*GlobalConfigurationFile, error) {
if u, err := url.Parse(urlOrPath); err == nil {
return ConfigFromURL(ctx, u.String())
}
diff --git a/zetaclient/chains/ton/liteapi/client.go b/zetaclient/chains/ton/liteapi/client.go
index ca79f462d9..25b0efcf39 100644
--- a/zetaclient/chains/ton/liteapi/client.go
+++ b/zetaclient/chains/ton/liteapi/client.go
@@ -3,6 +3,7 @@ package liteapi
import (
"context"
"fmt"
+ "slices"
"strconv"
"strings"
@@ -34,9 +35,9 @@ func New(client *liteapi.Client) *Client {
return &Client{Client: client, blockCache: blockCache}
}
-// NewFromAny creates a new client from a URL or a file path.
-func NewFromAny(ctx context.Context, urlOrPath string) (*Client, error) {
- cfg, err := zetaton.ConfigFromAny(ctx, urlOrPath)
+// NewFromSource creates a new client from a URL or a file path.
+func NewFromSource(ctx context.Context, urlOrPath string) (*Client, error) {
+ cfg, err := zetaton.ConfigFromSource(ctx, urlOrPath)
if err != nil {
return nil, errors.Wrap(err, "unable to get config")
}
@@ -94,7 +95,7 @@ func (c *Client) setBlockHeaderCache(blockID ton.BlockIDExt, header tlb.BlockInf
// Note that it might fail w/o using an archival node. Also returns the number of
// scrolled transactions for this account i.e. total transactions
func (c *Client) GetFirstTransaction(ctx context.Context, acc ton.AccountID) (*ton.Transaction, int, error) {
- lt, hash, err := c.getLastTranHash(ctx, acc)
+ lt, hash, err := c.getLastTransactionHash(ctx, acc)
if err != nil {
return nil, 0, err
}
@@ -132,16 +133,15 @@ func (c *Client) GetFirstTransaction(ctx context.Context, acc ton.AccountID) (*t
return tx, scrolled, nil
}
-// GetTransactionsUntil returns all transactions in a range of (from,to] where from is "lt && hash".
-// - oldestLT && oldestHash tx is EXCLUDED from the result.
-// - ordered by DESC
-func (c *Client) GetTransactionsUntil(
+// GetTransactionsSince returns all account transactions since the given logicalTime and hash (exclusive).
+// The result is ordered from oldest to newest. Used to detect new txs to observe.
+func (c *Client) GetTransactionsSince(
ctx context.Context,
acc ton.AccountID,
oldestLT uint64,
oldestHash ton.Bits256,
) ([]ton.Transaction, error) {
- lt, hash, err := c.getLastTranHash(ctx, acc)
+ lt, hash, err := c.getLastTransactionHash(ctx, acc)
if err != nil {
return nil, err
}
@@ -151,6 +151,8 @@ func (c *Client) GetTransactionsUntil(
for {
hashBits := ton.Bits256(hash)
+ // note that ton liteapi works in the reverse order.
+ // Here we go from the LATEST txs to the oldest at N txs per page
txs, err := c.GetTransactions(ctx, pageSize, acc, lt, hashBits)
if err != nil {
return nil, errors.Wrapf(err, "unable to get transactions [lt %d, hash %s]", lt, hashBits.Hex())
@@ -181,11 +183,14 @@ func (c *Client) GetTransactionsUntil(
lt, hash = txs[oldestIndex].PrevTransLt, txs[oldestIndex].PrevTransHash
}
+ // reverse the result to get the oldest tx first
+ slices.Reverse(result)
+
return result, nil
}
-// getLastTranHash returns logical time and hash of the last transaction
-func (c *Client) getLastTranHash(ctx context.Context, acc ton.AccountID) (uint64, tlb.Bits256, error) {
+// getLastTransactionHash returns logical time and hash of the last transaction
+func (c *Client) getLastTransactionHash(ctx context.Context, acc ton.AccountID) (uint64, tlb.Bits256, error) {
state, err := c.GetAccountState(ctx, acc)
if err != nil {
return 0, tlb.Bits256{}, errors.Wrap(err, "unable to get account state")
@@ -198,14 +203,16 @@ func (c *Client) getLastTranHash(ctx context.Context, acc ton.AccountID) (uint64
return state.LastTransLt, state.LastTransHash, nil
}
+// TransactionHashToString converts logicalTime and hash to string
func TransactionHashToString(lt uint64, hash ton.Bits256) string {
return fmt.Sprintf("%d:%s", lt, hash.Hex())
}
-func TransactionHashFromString(hash string) (uint64, ton.Bits256, error) {
- parts := strings.Split(hash, ":")
+// TransactionHashFromString parses encoded string into logicalTime and hash
+func TransactionHashFromString(encoded string) (uint64, ton.Bits256, error) {
+ parts := strings.Split(encoded, ":")
if len(parts) != 2 {
- return 0, ton.Bits256{}, fmt.Errorf("invalid hash string format")
+ return 0, ton.Bits256{}, fmt.Errorf("invalid encoded string format")
}
lt, err := strconv.ParseUint(parts[0], 10, 64)
diff --git a/zetaclient/chains/ton/liteapi/client_live_test.go b/zetaclient/chains/ton/liteapi/client_live_test.go
index acf211736b..ed3c850dd8 100644
--- a/zetaclient/chains/ton/liteapi/client_live_test.go
+++ b/zetaclient/chains/ton/liteapi/client_live_test.go
@@ -92,7 +92,7 @@ func TestClient(t *testing.T) {
// ACT
// https://tonviewer.com/UQCVlMcZ7EyV9maDsvscoLCd5KQfb7CHukyNJluWpMzlD0vr?section=transactions
- txs, err := client.GetTransactionsUntil(ctx, accountID, getUntilLT, hash)
+ txs, err := client.GetTransactionsSince(ctx, accountID, getUntilLT, hash)
finish := time.Since(start)
diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go
index 99b7083dac..4436b02369 100644
--- a/zetaclient/chains/ton/observer/inbound.go
+++ b/zetaclient/chains/ton/observer/inbound.go
@@ -4,7 +4,6 @@ import (
"context"
"encoding/hex"
"fmt"
- "slices"
"cosmossdk.io/math"
"github.com/pkg/errors"
@@ -68,19 +67,17 @@ func (ob *Observer) observeInbound(ctx context.Context) error {
return errors.Wrap(err, "unable to ensure last scanned tx")
}
+ // extract logicalTime and tx hash from last scanned tx
lt, hashBits, err := liteapi.TransactionHashFromString(ob.LastTxScanned())
if err != nil {
return errors.Wrapf(err, "unable to parse last scanned tx %q", ob.LastTxScanned())
}
- txs, err := ob.client.GetTransactionsUntil(ctx, ob.gateway.AccountID(), lt, hashBits)
+ txs, err := ob.client.GetTransactionsSince(ctx, ob.gateway.AccountID(), lt, hashBits)
if err != nil {
return errors.Wrap(err, "unable to get transactions")
}
- // Process from oldest to latest (ASC)
- slices.Reverse(txs)
-
switch {
case len(txs) == 0:
// noop
diff --git a/zetaclient/chains/ton/observer/inbound_test.go b/zetaclient/chains/ton/observer/inbound_test.go
index 9aee03ddab..e6208756ac 100644
--- a/zetaclient/chains/ton/observer/inbound_test.go
+++ b/zetaclient/chains/ton/observer/inbound_test.go
@@ -51,7 +51,7 @@ func TestInbound(t *testing.T) {
})
ts.OnGetFirstTransaction(gw.AccountID(), &firstTX, 0, nil).Once()
- ts.OnGetTransactionsUntil(gw.AccountID(), firstTX.Lt, txHash(firstTX), nil, nil).Once()
+ ts.OnGetTransactionsSince(gw.AccountID(), firstTX.Lt, txHash(firstTX), nil, nil).Once()
// Given observer
ob, err := New(ts.baseObserver, ts.liteClient, gw)
@@ -95,7 +95,7 @@ func TestInbound(t *testing.T) {
txs := []ton.Transaction{donation}
ts.
- OnGetTransactionsUntil(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
+ OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
Once()
// ACT
@@ -133,7 +133,7 @@ func TestInbound(t *testing.T) {
txs := []ton.Transaction{depositTX}
ts.
- OnGetTransactionsUntil(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
+ OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
Once()
ts.MockGetBlockHeader(depositTX.BlockID)
@@ -195,7 +195,7 @@ func TestInbound(t *testing.T) {
txs := []ton.Transaction{depositAndCallTX}
ts.
- OnGetTransactionsUntil(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
+ OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
Once()
ts.MockGetBlockHeader(depositAndCallTX.BlockID)
@@ -281,7 +281,7 @@ func TestInbound(t *testing.T) {
}
ts.
- OnGetTransactionsUntil(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
+ OnGetTransactionsSince(gw.AccountID(), lastScanned.Lt, txHash(lastScanned), txs, nil).
Once()
for _, tx := range txs {
diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go
index acdda42b46..c5fd26aad3 100644
--- a/zetaclient/chains/ton/observer/observer.go
+++ b/zetaclient/chains/ton/observer/observer.go
@@ -27,7 +27,7 @@ type Observer struct {
//go:generate mockery --name LiteClient --filename ton_liteclient.go --case underscore --output ../../../testutils/mocks
type LiteClient interface {
GetBlockHeader(ctx context.Context, blockID ton.BlockIDExt, mode uint32) (tlb.BlockInfo, error)
- GetTransactionsUntil(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error)
+ GetTransactionsSince(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error)
GetFirstTransaction(ctx context.Context, id ton.AccountID) (*ton.Transaction, int, error)
}
diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go
index 3237833920..8bfcf2bfab 100644
--- a/zetaclient/chains/ton/observer/observer_test.go
+++ b/zetaclient/chains/ton/observer/observer_test.go
@@ -119,7 +119,7 @@ func (ts *testSuite) OnGetFirstTransaction(acc ton.AccountID, tx *ton.Transactio
Return(tx, scanned, err)
}
-func (ts *testSuite) OnGetTransactionsUntil(
+func (ts *testSuite) OnGetTransactionsSince(
acc ton.AccountID,
lt uint64,
hash ton.Bits256,
@@ -127,7 +127,7 @@ func (ts *testSuite) OnGetTransactionsUntil(
err error,
) *mock.Call {
return ts.liteClient.
- On("GetTransactionsUntil", mock.Anything, acc, lt, hash).
+ On("GetTransactionsSince", mock.Anything, acc, lt, hash).
Return(txs, err)
}
diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go
index 497d5587d5..34c94bf77d 100644
--- a/zetaclient/orchestrator/bootstrap.go
+++ b/zetaclient/orchestrator/bootstrap.go
@@ -435,7 +435,7 @@ func syncObserverMap(
continue
}
- tonClient, err := liteapi.NewFromAny(ctx, cfg.LiteClientConfigURL)
+ tonClient, err := liteapi.NewFromSource(ctx, cfg.LiteClientConfigURL)
if err != nil {
logger.Std.Error().Err(err).Msgf("Unable to create TON liteapi for chain %d", chainID)
continue
diff --git a/zetaclient/testutils/mocks/ton_liteclient.go b/zetaclient/testutils/mocks/ton_liteclient.go
index 573d2d600e..f11ccaf24c 100644
--- a/zetaclient/testutils/mocks/ton_liteclient.go
+++ b/zetaclient/testutils/mocks/ton_liteclient.go
@@ -82,12 +82,12 @@ func (_m *LiteClient) GetFirstTransaction(ctx context.Context, id ton.AccountID)
return r0, r1, r2
}
-// GetTransactionsUntil provides a mock function with given fields: ctx, acc, lt, bits
-func (_m *LiteClient) GetTransactionsUntil(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error) {
+// GetTransactionsSince provides a mock function with given fields: ctx, acc, lt, bits
+func (_m *LiteClient) GetTransactionsSince(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error) {
ret := _m.Called(ctx, acc, lt, bits)
if len(ret) == 0 {
- panic("no return value specified for GetTransactionsUntil")
+ panic("no return value specified for GetTransactionsSince")
}
var r0 []ton.Transaction
From b313ded4b5ec2c8631aea15331f76e484de55449 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 4 Oct 2024 15:41:31 +0200
Subject: [PATCH 32/36] Fix comments
---
zetaclient/chains/ton/observer/inbound.go | 8 ++++++--
zetaclient/chains/ton/observer/observer.go | 10 ++++------
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go
index 4436b02369..36f08af46e 100644
--- a/zetaclient/chains/ton/observer/inbound.go
+++ b/zetaclient/chains/ton/observer/inbound.go
@@ -132,8 +132,8 @@ func (ob *Observer) voteInbound(ctx context.Context, tx *toncontracts.Transactio
return "", nil
}
- // todo add compliance check
- // https://github.com/zeta-chain/node/issues/2916
+ // TODO: Add compliance check
+ // https://github.com/zeta-chain/node/issues/2916
blockHeader, err := ob.client.GetBlockHeader(ctx, tx.BlockID, 0)
if err != nil {
@@ -150,6 +150,7 @@ func (ob *Observer) voteInbound(ctx context.Context, tx *toncontracts.Transactio
return ob.voteDeposit(ctx, tx, sender, amount, memo, seqno)
}
+// extractInboundData parses Gateway tx into deposit (TON sender, amount, memo)
func extractInboundData(tx *toncontracts.Transaction) (string, math.Uint, []byte, error) {
if tx.Operation == toncontracts.OpDeposit {
d, err := tx.Deposit()
@@ -193,6 +194,9 @@ func (ob *Observer) voteDeposit(
inboundHash = liteapi.TransactionHashToString(tx.Lt, ton.Bits256(tx.Hash()))
)
+ // TODO: use protocol contract v2 for deposit
+ // https://github.com/zeta-chain/node/issues/2967
+
msg := zetacore.GetInboundVoteMessage(
sender,
ob.Chain().ChainId,
diff --git a/zetaclient/chains/ton/observer/observer.go b/zetaclient/chains/ton/observer/observer.go
index c5fd26aad3..e20742116a 100644
--- a/zetaclient/chains/ton/observer/observer.go
+++ b/zetaclient/chains/ton/observer/observer.go
@@ -68,13 +68,11 @@ func (ob *Observer) Start(ctx context.Context) {
// watch for incoming txs and post votes to zetacore
bg.Work(ctx, ob.watchInbound, bg.WithName("WatchInbound"), bg.WithLogger(ob.Logger().Inbound))
- // todo
- // watchInboundTracker https://github.com/zeta-chain/node/issues/2935
+ // TODO: watchInboundTracker
+ // https://github.com/zeta-chain/node/issues/2935
- // todo outbounds/withdrawals https://github.com/zeta-chain/node/issues/2807
- // watchOutbound
- // watchGasPrice
- // watchRPCStatus
+ // TODO: outbounds/withdrawals: (watchOutbound, watchGasPrice, watchRPCStatus)
+ // https://github.com/zeta-chain/node/issues/2807
}
func (ob *Observer) VoteOutboundIfConfirmed(_ context.Context, _ *types.CrossChainTx) (bool, error) {
From e0218c80455976e31869fc03123666e3f3629b46 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Fri, 4 Oct 2024 15:43:57 +0200
Subject: [PATCH 33/36] Update zetaclient/chains/base/observer.go
Co-authored-by: Francisco de Borja Aranda Castillejo
---
zetaclient/chains/base/observer.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go
index 83ecbc4b72..f089f26815 100644
--- a/zetaclient/chains/base/observer.go
+++ b/zetaclient/chains/base/observer.go
@@ -135,7 +135,7 @@ func NewObserver(
return &ob, nil
}
-// Start starts the observer. Returns false started (noop).
+// Start starts the observer. Returns false if it's already started (noop).
func (ob *Observer) Start() bool {
ob.mu.Lock()
defer ob.Mu().Unlock()
From cfd4728737839f31b68201a19e4888ba956ea718 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Mon, 7 Oct 2024 15:23:27 +0200
Subject: [PATCH 34/36] Add gw explanatory comments; address PR comments
---
e2e/e2etests/test_ton_deposit.go | 2 +-
e2e/e2etests/test_ton_deposit_and_call.go | 2 +-
pkg/contracts/ton/gateway.go | 15 ++++++++++++++-
zetaclient/chains/ton/observer/inbound.go | 11 +++++------
zetaclient/chains/ton/observer/inbound_test.go | 7 +++++++
5 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go
index b4c7a32168..73860629df 100644
--- a/e2e/e2etests/test_ton_deposit.go
+++ b/e2e/e2etests/test_ton_deposit.go
@@ -54,7 +54,7 @@ func TestTONDeposit(r *runner.E2ERunner, args []string) {
require.Equal(r, sender.GetAddress().ToRaw(), cctx.InboundParams.Sender)
require.Equal(r, expectedDeposit.Uint64(), cctx.InboundParams.Amount.Uint64())
- // Check sender's balance
+ // Check receiver's balance
balance, err := r.TONZRC20.BalanceOf(&bind.CallOpts{}, recipient)
require.NoError(r, err)
diff --git a/e2e/e2etests/test_ton_deposit_and_call.go b/e2e/e2etests/test_ton_deposit_and_call.go
index b385b19f49..43e5dcc4e0 100644
--- a/e2e/e2etests/test_ton_deposit_and_call.go
+++ b/e2e/e2etests/test_ton_deposit_and_call.go
@@ -59,7 +59,7 @@ func TestTONDepositAndCall(r *runner.E2ERunner, args []string) {
// check if example contract has been called, bar value should be set to amount
utils.MustHaveCalledExampleContract(r, contract, expectedDeposit.BigInt())
- // Check sender's balance
+ // Check receiver's balance
balance, err := r.TONZRC20.BalanceOf(&bind.CallOpts{}, contractAddr)
require.NoError(r, err)
diff --git a/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go
index 4a85925b9e..33ca2c7977 100644
--- a/pkg/contracts/ton/gateway.go
+++ b/pkg/contracts/ton/gateway.go
@@ -1,3 +1,4 @@
+// Package ton provider bindings for TON blockchain including Gateway contract wrapper.
package ton
import (
@@ -8,7 +9,17 @@ import (
"github.com/tonkeeper/tongo/ton"
)
-// Gateway wrapper around zeta gateway contract on TON
+// Gateway represents bindings for Zeta Gateway contract on TON
+//
+// Gateway.ParseTransaction parses Gateway transaction.
+// The parser reads tx body cell and decodes it based on Operation code (op)
+// - inbound transactions: deposit, donate, depositAndCall
+// - outbound transactions: not implemented yet
+// - errors for all other transactions
+//
+// `Send*` methods work the same way by constructing (& signing) tx body cell that is expected by the contract
+//
+// @see https://github.com/zeta-chain/protocol-contracts-ton/blob/main/contracts/gateway.fc
type Gateway struct {
accountID ton.AccountID
}
@@ -24,10 +35,12 @@ var (
ErrCast = errors.New("unable to cast tx content")
)
+// NewGateway Gateway constructor
func NewGateway(accountID ton.AccountID) *Gateway {
return &Gateway{accountID}
}
+// AccountID returns gateway address
func (gw *Gateway) AccountID() ton.AccountID {
return gw.accountID
}
diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go
index 36f08af46e..95f9a510d7 100644
--- a/zetaclient/chains/ton/observer/inbound.go
+++ b/zetaclient/chains/ton/observer/inbound.go
@@ -152,25 +152,24 @@ func (ob *Observer) voteInbound(ctx context.Context, tx *toncontracts.Transactio
// extractInboundData parses Gateway tx into deposit (TON sender, amount, memo)
func extractInboundData(tx *toncontracts.Transaction) (string, math.Uint, []byte, error) {
- if tx.Operation == toncontracts.OpDeposit {
+ switch tx.Operation {
+ case toncontracts.OpDeposit:
d, err := tx.Deposit()
if err != nil {
return "", math.NewUint(0), nil, err
}
return d.Sender.ToRaw(), d.Amount, d.Memo(), nil
- }
-
- if tx.Operation == toncontracts.OpDepositAndCall {
+ case toncontracts.OpDepositAndCall:
d, err := tx.DepositAndCall()
if err != nil {
return "", math.NewUint(0), nil, err
}
return d.Sender.ToRaw(), d.Amount, d.Memo(), nil
+ default:
+ return "", math.NewUint(0), nil, fmt.Errorf("unknown operation %d", tx.Operation)
}
-
- return "", math.NewUint(0), nil, fmt.Errorf("unknown operation %d", tx.Operation)
}
func (ob *Observer) voteDeposit(
diff --git a/zetaclient/chains/ton/observer/inbound_test.go b/zetaclient/chains/ton/observer/inbound_test.go
index e6208756ac..281db44f79 100644
--- a/zetaclient/chains/ton/observer/inbound_test.go
+++ b/zetaclient/chains/ton/observer/inbound_test.go
@@ -19,6 +19,13 @@ func TestInbound(t *testing.T) {
ton.MustParseAccountID("0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b"),
)
+ t.Run("No gateway provided", func(t *testing.T) {
+ ts := newTestSuite(t)
+
+ _, err := New(ts.baseObserver, ts.liteClient, nil)
+ require.Error(t, err)
+ })
+
t.Run("Ensure last scanned tx", func(t *testing.T) {
t.Run("Unable to get first tx", func(t *testing.T) {
// ARRANGE
From c43ae26492099eb3997bcd1ea02f31a29ce0cb8d Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Mon, 7 Oct 2024 15:52:48 +0200
Subject: [PATCH 35/36] Apply fixes for AI suggestions
---
pkg/chains/chain.go | 9 ++++++++-
pkg/chains/chain_test.go | 7 +++++++
pkg/contracts/ton/testdata/readme.md | 2 +-
pkg/contracts/ton/testdata/scraper.go | 6 +++---
zetaclient/chains/ton/observer/observer_test.go | 4 +---
5 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/pkg/chains/chain.go b/pkg/chains/chain.go
index 3cb8650714..665251eb39 100644
--- a/pkg/chains/chain.go
+++ b/pkg/chains/chain.go
@@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
ethcommon "github.com/ethereum/go-ethereum/common"
+ "github.com/tonkeeper/tongo/ton"
)
// Validate checks whether the chain is valid
@@ -109,7 +110,13 @@ func DecodeAddressFromChainID(chainID int64, addr string, additionalChains []Cha
case IsSolanaChain(chainID, additionalChains):
return []byte(addr), nil
case IsTONChain(chainID, additionalChains):
- return []byte(addr), nil
+ // e.g. `0:55798cb7b87168251a7c39f6806b8c202f6caa0f617a76f4070b3fdacfd056a1`
+ acc, err := ton.ParseAccountID(addr)
+ if err != nil {
+ return nil, fmt.Errorf("invalid TON address %q: %w", addr, err)
+ }
+
+ return []byte(acc.ToRaw()), nil
default:
return nil, fmt.Errorf("chain (%d) not supported", chainID)
}
diff --git a/pkg/chains/chain_test.go b/pkg/chains/chain_test.go
index c7cfc80d89..4da81c7eee 100644
--- a/pkg/chains/chain_test.go
+++ b/pkg/chains/chain_test.go
@@ -307,6 +307,13 @@ func TestDecodeAddressFromChainID(t *testing.T) {
addr: "0:55798cb7b87168251a7c39f6806b8c202f6caa0f617a76f4070b3fdacfd056a1",
want: []byte("0:55798cb7b87168251a7c39f6806b8c202f6caa0f617a76f4070b3fdacfd056a1"),
},
+ {
+ name: "TON",
+ chainID: chains.TONMainnet.ChainId,
+ // human friendly address should be always represented in raw format
+ addr: "EQB3ncyBUTjZUA5EnFKR5_EnOMI9V1tTEAAPaiU71gc4TiUt",
+ want: []byte("0:779dcc815138d9500e449c5291e7f12738c23d575b5310000f6a253bd607384e"),
+ },
{
name: "Non-supported chain",
chainID: 9999,
diff --git a/pkg/contracts/ton/testdata/readme.md b/pkg/contracts/ton/testdata/readme.md
index af8a878904..8695f06982 100644
--- a/pkg/contracts/ton/testdata/readme.md
+++ b/pkg/contracts/ton/testdata/readme.md
@@ -20,7 +20,7 @@ Returns
{
"account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b",
"boc": "b5ee9c7201020a0100023d0003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017ab22a3b3031b48df020aa3647a59163a25772d81991916a2bf523b771d89deec9e5be15d58000017ab20f8740366ead1e30003465b1d0080102030201e004050082723e346a6461f48fab691e87d9fd5954595eb15351fa1c536486a863d9708b79c313c7e41677dcade29f2b424cdad8712132fbd5465b37df2e1763369e2fb12da0021904222490ee6b28018646dca110080900f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d0ee6b28000608235a00002f5645476604cdd5a3c60000003280000000000000006c6d35f934b257cebf76cf01f29a0ae9bd54b022c00101df06015de004cbec44e40ad75610e23fc357071c1c1f7e1e1a31833ac17b131d6a462d0a42d800002f5645476608cdd5a3c6c007008b0000006500000000000000008012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1e876046701b1b4d7e4d2c95f3afddb3c07ca682ba6f552c08b009e42d5ac3d090000000000000000007e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98510184c2880c0000000000002000000000003da7f47a5d1898330bd18801617ae23a388cbaf527c312921718ef36ce9cf8c4e40901d04",
- "description": "todo",
+ "description": "Lorem Ipsum",
"hash": "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf",
"logicalTime": 26023788000003,
"test": true
diff --git a/pkg/contracts/ton/testdata/scraper.go b/pkg/contracts/ton/testdata/scraper.go
index e75f1bba23..efe76ca3aa 100644
--- a/pkg/contracts/ton/testdata/scraper.go
+++ b/pkg/contracts/ton/testdata/scraper.go
@@ -21,7 +21,7 @@ func main() {
flag.Parse()
if len(flag.Args()) < 3 {
- log.Fatalf("Usage: go run scrape.go [-testnet] ")
+ log.Fatalf("Usage: go run scraper.go [-testnet] ")
}
// Parse account
@@ -53,7 +53,7 @@ func main() {
case len(txs) == 0:
fail("Not found")
case len(txs) > 1:
- fail("invalid tx list length (got %d, want 1); lt %d, hash %s", len(txs), hash.Hex())
+ fail("invalid tx list length (got %d, want 1); lt %d, hash %s", len(txs), lt, hash.Hex())
}
// Print the transaction
@@ -83,7 +83,7 @@ func getClient(testnet bool) *liteapi.Client {
return c
}
- c, err := liteapi.NewClientWithDefaultTestnet()
+ c, err := liteapi.NewClientWithDefaultMainnet()
must(err, "unable to create mainnet lite client")
return c
diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go
index 8bfcf2bfab..38c032eb4a 100644
--- a/zetaclient/chains/ton/observer/observer_test.go
+++ b/zetaclient/chains/ton/observer/observer_test.go
@@ -160,9 +160,7 @@ func setupVotesBag(ts *testSuite) {
catcher := func(args mock.Arguments) {
vote := args.Get(3)
cctx, ok := vote.(*cctxtypes.MsgVoteInbound)
- if !ok {
- panic("unexpected cctx type")
- }
+ require.True(ts.t, ok, "unexpected cctx type")
ts.votesBag = append(ts.votesBag, cctx)
}
From 126f96622b85cefa9028ec579de9db51aec5b6f4 Mon Sep 17 00:00:00 2001
From: Dmitry S <11892559+swift1337@users.noreply.github.com>
Date: Wed, 9 Oct 2024 19:03:33 +0200
Subject: [PATCH 36/36] Fix upgrade tests
---
e2e/runner/setup_zeta.go | 13 +++++++++----
e2e/runner/zeta.go | 12 ++++++++++++
e2e/txserver/zeta_tx_server.go | 10 +++++++++-
3 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/e2e/runner/setup_zeta.go b/e2e/runner/setup_zeta.go
index 24c4918e13..1d1db47108 100644
--- a/e2e/runner/setup_zeta.go
+++ b/e2e/runner/setup_zeta.go
@@ -179,6 +179,7 @@ func (r *E2ERunner) SetZEVMZRC20s() {
e2eutils.OperationalPolicyName,
e2eutils.AdminPolicyName,
r.ERC20Addr.Hex(),
+ r.skipChainOperations,
)
require.NoError(r, err)
@@ -245,10 +246,14 @@ func (r *E2ERunner) SetupSOLZRC20() {
// SetupTONZRC20 sets up the TON ZRC20 in the runner from the values queried from the chain
func (r *E2ERunner) SetupTONZRC20() {
- TONZRC20Addr, err := r.SystemContract.GasCoinZRC20ByChainId(
- &bind.CallOpts{},
- big.NewInt(chains.TONLocalnet.ChainId),
- )
+ chainID := chains.TONLocalnet.ChainId
+
+ // noop
+ if r.skipChainOperations(chainID) {
+ return
+ }
+
+ TONZRC20Addr, err := r.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(chainID))
require.NoError(r, err)
r.TONZRC20Addr = TONZRC20Addr
diff --git a/e2e/runner/zeta.go b/e2e/runner/zeta.go
index 5b977a9a31..1df7e676e3 100644
--- a/e2e/runner/zeta.go
+++ b/e2e/runner/zeta.go
@@ -14,6 +14,7 @@ import (
connectorzevm "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/zetaconnectorzevm.sol"
"github.com/zeta-chain/node/e2e/utils"
+ "github.com/zeta-chain/node/pkg/chains"
"github.com/zeta-chain/node/pkg/retry"
"github.com/zeta-chain/node/x/crosschain/types"
observertypes "github.com/zeta-chain/node/x/observer/types"
@@ -315,3 +316,14 @@ func (r *E2ERunner) WithdrawERC20(amount *big.Int) *ethtypes.Transaction {
return tx
}
+
+// skipChainOperations checks if the chain operations should be skipped for E2E
+func (r *E2ERunner) skipChainOperations(chainID int64) bool {
+ skip := r.IsRunningUpgrade() && chains.IsTONChain(chainID, nil)
+
+ if skip {
+ r.Logger.Print("Skipping chain operations for chain %d", chainID)
+ }
+
+ return skip
+}
diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go
index 47f7cf37e6..a79c5af098 100644
--- a/e2e/txserver/zeta_tx_server.go
+++ b/e2e/txserver/zeta_tx_server.go
@@ -406,7 +406,10 @@ func (zts ZetaTxServer) DeploySystemContracts(
// DeployZRC20s deploys the ZRC20 contracts
// returns the addresses of erc20 zrc20
-func (zts ZetaTxServer) DeployZRC20s(accountOperational, accountAdmin, erc20Addr string) (string, error) {
+func (zts ZetaTxServer) DeployZRC20s(
+ accountOperational, accountAdmin, erc20Addr string,
+ skipChain func(chainID int64) bool,
+) (string, error) {
// retrieve account
accOperational, err := zts.clientCtx.Keyring.Key(accountOperational)
if err != nil {
@@ -440,6 +443,11 @@ func (zts ZetaTxServer) DeployZRC20s(accountOperational, accountAdmin, erc20Addr
}
deploy := func(msg *fungibletypes.MsgDeployFungibleCoinZRC20) (string, error) {
+ // noop
+ if skipChain(msg.ForeignChainId) {
+ return "", nil
+ }
+
res, err := zts.BroadcastTx(deployerAccount, msg)
if err != nil {
return "", fmt.Errorf("failed to deploy eth zrc20: %w", err)