diff --git a/Makefile b/Makefile
index 36e414f012..7b01eafb41 100644
--- a/Makefile
+++ b/Makefile
@@ -296,15 +296,24 @@ start-v2-test: zetanode
###############################################################################
# build from source only if requested
+# NODE_VERSION and NODE_COMMIT must be set as old-runtime depends on lastest-runtime
ifdef UPGRADE_TEST_FROM_SOURCE
zetanode-upgrade: zetanode
@echo "Building zetanode-upgrade from source"
- $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime-source --build-arg OLD_VERSION='release/v20' .
+ $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime-source \
+ --build-arg OLD_VERSION='release/v20' \
+ --build-arg NODE_VERSION=$(NODE_VERSION) \
+ --build-arg NODE_COMMIT=$(NODE_COMMIT)
+ .
.PHONY: zetanode-upgrade
else
zetanode-upgrade: zetanode
@echo "Building zetanode-upgrade from binaries"
- $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime --build-arg OLD_VERSION='https://github.com/zeta-chain/node/releases/download/v20.0.2' .
+ $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime \
+ --build-arg OLD_VERSION='https://github.com/zeta-chain/node/releases/download/v20.0.2' \
+ --build-arg NODE_VERSION=$(NODE_VERSION) \
+ --build-arg NODE_COMMIT=$(NODE_COMMIT) \
+ .
.PHONY: zetanode-upgrade
endif
diff --git a/changelog.md b/changelog.md
index 1f49f12ecb..b18609a3a9 100644
--- a/changelog.md
+++ b/changelog.md
@@ -17,6 +17,7 @@
* [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
* [2957](https://github.com/zeta-chain/node/pull/2957) - enable Bitcoin inscription support on testnet
+* [2896](https://github.com/zeta-chain/node/pull/2896) - add TON inbound observation
* [2979](https://github.com/zeta-chain/node/pull/2979) - add fungible keeper ability to lock/unlock ZRC20 tokens
### Refactor
@@ -27,6 +28,7 @@
* [2826](https://github.com/zeta-chain/node/pull/2826) - remove unused code from emissions module and add new parameter for fixed block reward amount
* [2890](https://github.com/zeta-chain/node/pull/2890) - refactor `MsgUpdateChainInfo` to accept a single chain, and add `MsgRemoveChainInfo` to remove a chain
* [2899](https://github.com/zeta-chain/node/pull/2899) - remove btc deposit fee v1 and improve unit tests
+* [2952](https://github.com/zeta-chain/node/pull/2952) - add error_message to cctx.status
### Tests
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/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go
index 4a7fbef39c..23a7f6b866 100644
--- a/cmd/zetae2e/local/local.go
+++ b/cmd/zetae2e/local/local.go
@@ -407,6 +407,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
tonTests := []string{
e2etests.TestTONDepositName,
+ e2etests.TestTONDepositAndCallName,
}
eg.Go(tonTestRoutine(conf, deployerRunner, verbose, tonTests...))
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/contrib/localnet/bitcoin-sidecar/Dockerfile b/contrib/localnet/bitcoin-sidecar/Dockerfile
deleted file mode 100644
index aef54cf56d..0000000000
--- a/contrib/localnet/bitcoin-sidecar/Dockerfile
+++ /dev/null
@@ -1,14 +0,0 @@
-FROM node:18.20.4 as builder
-
-WORKDIR /home/zeta/node
-
-COPY bitcoin-sidecar/js/* .
-
-RUN npm install && npm install typescript -g && tsc
-
-FROM node:alpine
-
-COPY --from=builder /home/zeta/node/dist ./dist
-COPY --from=builder /home/zeta/node/node_modules ./node_modules
-
-CMD ["node", "dist/index.js"]
\ No newline at end of file
diff --git a/contrib/localnet/bitcoin-sidecar/js/package.json b/contrib/localnet/bitcoin-sidecar/js/package.json
deleted file mode 100644
index 1a4dd4b90a..0000000000
--- a/contrib/localnet/bitcoin-sidecar/js/package.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "zeta-btc-client",
- "version": "0.0.1",
- "description": "The Zetachain BTC client",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "author": "",
- "license": "ISC",
- "dependencies": {
- "bip32": "^4.0.0",
- "bitcoinjs-lib": "^6.1.6",
- "ecpair": "^2.1.0",
- "express": "^4.19.2",
- "randombytes": "^2.1.0",
- "tiny-secp256k1": "^2.2.3"
- },
- "devDependencies": {
- "@types/node": "^20.14.11",
- "typescript": "^5.5.3"
- }
-}
\ No newline at end of file
diff --git a/contrib/localnet/bitcoin-sidecar/js/src/client.ts b/contrib/localnet/bitcoin-sidecar/js/src/client.ts
deleted file mode 100644
index 678b90f6da..0000000000
--- a/contrib/localnet/bitcoin-sidecar/js/src/client.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-import { initEccLib, payments, Psbt } from "bitcoinjs-lib";
-import { bitcoin, Network, regtest } from "bitcoinjs-lib/src/networks";
-import BIP32Factory, { BIP32Interface } from 'bip32';
-import * as ecc from 'tiny-secp256k1';
-import randomBytes from "randombytes";
-import { ScriptBuilder } from "./script";
-import { Taptree } from "bitcoinjs-lib/src/types";
-import { toXOnly } from "./util";
-
-const LEAF_VERSION_TAPSCRIPT = 0xc0;
-
-initEccLib(ecc);
-const bip32 = BIP32Factory(ecc);
-const rng = randomBytes;
-
-/// The evm address type, a 20 bytes hex string
-export type Address = String;
-export type BtcAddress = String;
-
-/// The BTC transaction hash returned
-export type BtcTxnHash = String;
-export interface BtcInput {
- txn: BtcTxnHash,
- idx: number,
-}
-
-/**
- * The example client for interacting with ZetaChain in BTC. There are currently two ways
- * of calling a smart contract on ZetaChain from BTC:
- *
- * - Using OP_RETURN
- * - Using Witness
- *
- * The method used is now based on the data size. Within 80 bytes, `OP_RETURN` is used, else
- * the data is written to Witness.
- *
- * This class handles only the case where data is more than 80 bytes.
- */
-export class ZetaBtcClient {
- /** The BTC network interracting with */
- readonly network: Network;
-
- private reveal: RevealTxnBuilder | null;
-
- private constructor(network: Network) {
- this.network = network;
- }
-
- public static regtest(): ZetaBtcClient {
- return new ZetaBtcClient(regtest);
- }
-
- public static mainnet(): ZetaBtcClient {
- return new ZetaBtcClient(bitcoin);
- }
-
- /**
- * Call a target address and passing the data call.
- *
- * @param address The target zetachain evm address
- * @param calldata The calldata that will be invoked on Zetachain
- */
- public call(
- address: Address,
- calldata: Buffer,
- ): Address {
- if (calldata.length <= 80) {
- throw Error("Use op return instead");
- }
-
- if (address.startsWith("0x")) {
- address = address.substring(2);
- }
-
- return this.callWithWitness(Buffer.concat([Buffer.from(address, "hex"), calldata]));
- }
-
- private callWithWitness(
- data: Buffer,
- ): Address {
- const internalKey = bip32.fromSeed(rng(64), this.network);
-
- const leafScript = this.genLeafScript(internalKey.publicKey, data);
-
- const scriptTree: Taptree = { output: leafScript };
-
- const { address: commitAddress } = payments.p2tr({
- internalPubkey: toXOnly(internalKey.publicKey),
- scriptTree,
- network: this.network,
- });
-
- this.reveal = new RevealTxnBuilder(internalKey, leafScript, this.network);
-
- return commitAddress;
- }
-
- public buildRevealTxn(to: string, commitTxn: BtcInput, commitAmount: number, feeRate: number): Buffer {
- if (this.reveal === null) {
- throw new Error("commit txn not built yet");
- }
-
- this.reveal.with_commit_tx(to, commitTxn, commitAmount, feeRate);
- return this.reveal.dump();
- }
-
- private genLeafScript(publicKey: Buffer, data: Buffer,): Buffer {
- const builder = ScriptBuilder.new(publicKey);
- builder.pushData(data);
- return builder.build();
- }
-}
-
-class RevealTxnBuilder {
- private psbt: Psbt;
- private key: BIP32Interface;
- private leafScript: Buffer;
- private network: Network
-
- constructor(key: BIP32Interface, leafScript: Buffer, network: Network) {
- this.psbt = new Psbt({ network });;
- this.key = key;
- this.leafScript = leafScript;
- this.network = network;
- }
-
- public with_commit_tx(to: string, commitTxn: BtcInput, commitAmount: number, feeRate: number): RevealTxnBuilder {
- const scriptTree: Taptree = { output: this.leafScript };
-
- const { output, witness } = payments.p2tr({
- internalPubkey: toXOnly(this.key.publicKey),
- scriptTree,
- redeem: {
- output: this.leafScript,
- redeemVersion: LEAF_VERSION_TAPSCRIPT,
- },
- network: this.network,
- });
-
- this.psbt.addInput({
- hash: commitTxn.txn.toString(),
- index: commitTxn.idx,
- witnessUtxo: { value: commitAmount, script: output! },
- tapLeafScript: [
- {
- leafVersion: LEAF_VERSION_TAPSCRIPT,
- script: this.leafScript,
- controlBlock: witness![witness!.length - 1],
- },
- ],
- });
-
- this.psbt.addOutput({
- value: commitAmount - this.estimateFee(to, commitAmount, feeRate),
- address: to,
- });
-
- this.psbt.signAllInputs(this.key);
- this.psbt.finalizeAllInputs();
-
- return this;
- }
-
- public dump(): Buffer {
- return this.psbt.extractTransaction(true).toBuffer();
- }
-
- private estimateFee(to: string, amount: number, feeRate: number): number {
- const cloned = this.psbt.clone();
-
- cloned.addOutput({
- value: amount,
- address: to,
- });
-
- // should have a way to avoid signing but just providing mocked signautre
- cloned.signAllInputs(this.key);
- cloned.finalizeAllInputs();
-
- const size = cloned.extractTransaction().virtualSize();
- return size * feeRate;
- }
-}
\ No newline at end of file
diff --git a/contrib/localnet/bitcoin-sidecar/js/src/index.ts b/contrib/localnet/bitcoin-sidecar/js/src/index.ts
deleted file mode 100644
index 5164a6f148..0000000000
--- a/contrib/localnet/bitcoin-sidecar/js/src/index.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { ZetaBtcClient } from "./client";
-import express, { Request, Response } from 'express';
-
-const app = express();
-const PORT = process.env.PORT || 3000;
-let zetaClient = ZetaBtcClient.regtest();
-
-app.use(express.json());
-
-// Middleware to parse URL-encoded bodies
-app.use(express.urlencoded({ extended: true }));
-
-// Route to handle JSON POST requests
-app.post('/commit', (req: Request, res: Response) => {
- const memo: string = req.body.memo;
- const address = zetaClient.call("", Buffer.from(memo, "hex"));
- res.json({ address });
-});
-
-// Route to handle URL-encoded POST requests
-app.post('/reveal', (req: Request, res: Response) => {
- const { txn, idx, amount, feeRate, to } = req.body;
- console.log(txn, idx, amount, feeRate);
-
- const rawHex = zetaClient.buildRevealTxn(to,{ txn, idx }, Number(amount), feeRate).toString("hex");
- zetaClient = ZetaBtcClient.regtest();
- res.json({ rawHex });
-});
-
-// Start the server
-app.listen(PORT, () => {
- console.log(`Server is running on http://localhost:${PORT}`);
-});
-
-/**
- * curl --request POST --header "Content-Type: application/json" --data '{"memo":"72f080c854647755d0d9e6f6821f6931f855b9acffd53d87433395672756d58822fd143360762109ab898626556b1c3b8d3096d2361f1297df4a41c1b429471a9aa2fc9be5f27c13b3863d6ac269e4b587d8389f8fd9649859935b0d48dea88cdb40f20c"}' http://localhost:3000/commit
- * curl --request POST --header "Content-Type: application/json" --data '{"txn": "7a57f987a3cb605896a5909d9ef2bf7afbf0c78f21e4118b85d00d9e4cce0c2c", "idx": 0, "amount": 1000, "feeRate": 10}' http://localhost:3000/reveal
- */
\ No newline at end of file
diff --git a/contrib/localnet/bitcoin-sidecar/js/src/script.ts b/contrib/localnet/bitcoin-sidecar/js/src/script.ts
deleted file mode 100644
index f282e39f01..0000000000
--- a/contrib/localnet/bitcoin-sidecar/js/src/script.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { opcodes, script, Stack } from "bitcoinjs-lib";
-import { toXOnly } from "./util";
-
-const MAX_SCRIPT_ELEMENT_SIZE = 520;
-
-/** The tapscript builder for zetaclient spending script */
-export class ScriptBuilder {
- private script: Stack;
-
- private constructor(initialScript: Stack) {
- this.script = initialScript;
- }
-
- public static new(publicKey: Buffer): ScriptBuilder {
- const stack = [
- toXOnly(publicKey),
- opcodes.OP_CHECKSIG,
- ];
- return new ScriptBuilder(stack);
- }
-
- public pushData(data: Buffer) {
- if (data.length <= 80) {
- throw new Error("data length should be more than 80 bytes");
- }
-
- this.script.push(
- opcodes.OP_FALSE,
- opcodes.OP_IF
- );
-
- const chunks = chunkBuffer(data, MAX_SCRIPT_ELEMENT_SIZE);
- for (const chunk of chunks) {
- this.script.push(chunk);
- }
-
- this.script.push(opcodes.OP_ENDIF);
- }
-
- public build(): Buffer {
- return script.compile(this.script);
- }
-}
-
-function chunkBuffer(buffer: Buffer, chunkSize: number): Buffer[] {
- const chunks = [];
- for (let i = 0; i < buffer.length; i += chunkSize) {
- const chunk = buffer.slice(i, i + chunkSize);
- chunks.push(chunk);
- }
- return chunks;
-}
\ No newline at end of file
diff --git a/contrib/localnet/bitcoin-sidecar/js/src/tsconfig.json b/contrib/localnet/bitcoin-sidecar/js/src/tsconfig.json
deleted file mode 100644
index 4033670b3d..0000000000
--- a/contrib/localnet/bitcoin-sidecar/js/src/tsconfig.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "compilerOptions": {
- "module": "commonjs",
- "esModuleInterop": true,
- "target": "es6",
- "moduleResolution": "node",
- "sourceMap": true,
- "outDir": "dist"
- },
- "lib": ["es2015"]
-}
\ No newline at end of file
diff --git a/contrib/localnet/bitcoin-sidecar/js/src/util.ts b/contrib/localnet/bitcoin-sidecar/js/src/util.ts
deleted file mode 100644
index 87c4d36d0f..0000000000
--- a/contrib/localnet/bitcoin-sidecar/js/src/util.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33));
\ No newline at end of file
diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml
index 7044390647..363f5e1f6d 100644
--- a/contrib/localnet/docker-compose.yml
+++ b/contrib/localnet/docker-compose.yml
@@ -228,8 +228,7 @@ services:
-txindex=1
bitcoin-node-sidecar:
- build:
- dockerfile: ./bitcoin-sidecar/Dockerfile
+ image: ghcr.io/zeta-chain/node-localnet-bitcoin-sidecar:e0205d7
container_name: bitcoin-node-sidecar
hostname: bitcoin-node-sidecar
networks:
diff --git a/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml
index a8c0a2516e..dfa9349794 100644
--- a/docs/openapi/openapi.swagger.yaml
+++ b/docs/openapi/openapi.swagger.yaml
@@ -56998,7 +56998,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
@@ -57014,6 +57016,7 @@ definitions:
- optimism
- base
- solana
+ - ton
default: eth
title: |-
Network represents the network of the chain
@@ -57048,6 +57051,7 @@ definitions:
- no_vm
- evm
- svm
+ - tvm
default: no_vm
title: |-
Vm represents the virtual machine type of the chain to support smart
@@ -58446,6 +58450,14 @@ definitions:
$ref: '#/definitions/crosschainCctxStatus'
status_message:
type: string
+ description: |-
+ status_message carries information about the status transitions:
+ why they were triggered, old and new status.
+ error_message:
+ type: string
+ description: |-
+ error_message carries information about the error that caused the tx
+ to be PendingRevert, Reverted or Aborted.
lastUpdate_timestamp:
type: string
format: int64
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/e2etests.go b/e2e/e2etests/e2etests.go
index 004271c696..2d6eea998f 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
@@ -445,10 +446,18 @@ 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,
),
+ 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 e3ab963354..64f0920c2a 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/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
@@ -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_eth_deposit_call.go b/e2e/e2etests/test_eth_deposit_call.go
index 570e7b05eb..9484888cf3 100644
--- a/e2e/e2etests/test_eth_deposit_call.go
+++ b/e2e/e2etests/test_eth_deposit_call.go
@@ -87,6 +87,11 @@ func TestEtherDepositAndCall(r *runner.E2ERunner, args []string) {
r.Logger.Info("Cross-chain call to reverter reverted")
- // check the status message contains revert error hash in case of revert
- require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevertFoo)
+ // Check the error carries the revert executed.
+ // tolerate the error in both the ErrorMessage field and the StatusMessage field
+ if cctx.CctxStatus.ErrorMessage != "" {
+ require.Contains(r, cctx.CctxStatus.ErrorMessage, "revert executed")
+ } else {
+ require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevertFoo)
+ }
}
diff --git a/e2e/e2etests/test_solana_deposit_refund.go b/e2e/e2etests/test_solana_deposit_refund.go
index e9155c9ddd..0a62b70ac6 100644
--- a/e2e/e2etests/test_solana_deposit_refund.go
+++ b/e2e/e2etests/test_solana_deposit_refund.go
@@ -31,6 +31,11 @@ func TestSolanaDepositAndCallRefund(r *runner.E2ERunner, args []string) {
r.Logger.CCTX(*cctx, "solana_deposit_and_refund")
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_Reverted)
- // check the status message contains revert error hash in case of revert
- require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevertFoo)
+ // Check the error carries the revert executed.
+ // tolerate the error in both the ErrorMessage field and the StatusMessage field
+ if cctx.CctxStatus.ErrorMessage != "" {
+ require.Contains(r, cctx.CctxStatus.ErrorMessage, "revert executed")
+ } else {
+ require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevertFoo)
+ }
}
diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go
index a2e8df09d0..73860629df 100644
--- a/e2e/e2etests/test_ton_deposit.go
+++ b/e2e/e2etests/test_ton_deposit.go
@@ -1,42 +1,64 @@
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"
+ "github.com/zeta-chain/node/testutil/sample"
+ cctypes "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 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, 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 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
+ err = r.TONDeposit(sender, amount, recipient)
+
+ // ASSERT
require.NoError(r, err)
- // Check sender balance
- sb, err := sender.GetBalance(ctx)
+ // Wait for CCTX mining
+ filter := func(cctx *cctypes.CrossChainTx) bool {
+ return cctx.InboundParams.SenderChainId == chain.ChainId &&
+ cctx.InboundParams.Sender == sender.GetAddress().ToRaw()
+ }
+
+ cctx := r.WaitForSpecificCCTX(filter, time.Minute)
+
+ // Check CCTX
+ expectedDeposit := amount.Sub(depositFee)
+
+ require.Equal(r, sender.GetAddress().ToRaw(), cctx.InboundParams.Sender)
+ require.Equal(r, expectedDeposit.Uint64(), cctx.InboundParams.Amount.Uint64())
+
+ // Check receiver's balance
+ balance, err := r.TONZRC20.BalanceOf(&bind.CallOpts{}, recipient)
require.NoError(r, err)
- senderBalance := math.NewUint(sb)
+ r.Logger.Info("Recipient's zEVM TON balance after deposit: %d", balance.Uint64())
- // 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),
- )
+ require.Equal(r, expectedDeposit.Uint64(), balance.Uint64())
}
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..43e5dcc4e0
--- /dev/null
+++ b/e2e/e2etests/test_ton_deposit_and_call.go
@@ -0,0 +1,69 @@
+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"
+ testcontract "github.com/zeta-chain/node/testutil/contracts"
+ cctypes "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 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
+ err = r.TONDepositAndCall(sender, amount, contractAddr, callData)
+
+ // ASSERT
+ require.NoError(r, err)
+
+ // Wait for CCTX mining
+ filter := func(cctx *cctypes.CrossChainTx) bool {
+ return cctx.InboundParams.SenderChainId == chain.ChainId &&
+ cctx.InboundParams.Sender == sender.GetAddress().ToRaw()
+ }
+
+ r.WaitForSpecificCCTX(filter, time.Minute)
+
+ 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 receiver'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/runner.go b/e2e/runner/runner.go
index 57e5ab9a24..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.
@@ -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
@@ -230,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
@@ -275,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
@@ -359,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/runner/setup_ton.go b/e2e/runner/setup_ton.go
index 9b744401ad..62d89c3329 100644
--- a/e2e/runner/setup_ton.go
+++ b/e2e/runner/setup_ton.go
@@ -2,10 +2,16 @@ 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"
+ toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
+ observertypes "github.com/zeta-chain/node/x/observer/types"
)
// SetupTON setups TON deployer and deploys Gateway contract
@@ -42,7 +48,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)
@@ -55,7 +66,58 @@ func (r *E2ERunner) SetupTON() error {
}
r.TONDeployer = deployer
- r.TONGateway = gwAccount.ID
+ r.TONGateway = toncontracts.NewGateway(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/e2e/runner/setup_zeta.go b/e2e/runner/setup_zeta.go
index ffab5db811..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)
@@ -191,6 +192,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 +244,27 @@ 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() {
+ 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
+ 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/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/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/e2e/runner/zeta.go b/e2e/runner/zeta.go
index bdada4763c..1df7e676e3 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"
@@ -13,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"
@@ -96,12 +98,47 @@ 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
+}
+
+// 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 {
+ var (
+ 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, reqQuery)
+ require.NoError(r, err)
+
+ for i := range res.CrossChainTx {
+ tx := res.CrossChainTx[i]
+ if filter(tx) {
+ return r.WaitForMinedCCTXFromIndex(tx.Index)
+ }
+ }
+
+ 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
@@ -279,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 9ba7e5315a..a79c5af098 100644
--- a/e2e/txserver/zeta_tx_server.go
+++ b/e2e/txserver/zeta_tx_server.go
@@ -408,6 +408,7 @@ func (zts ZetaTxServer) DeploySystemContracts(
// returns the addresses of erc20 zrc20
func (zts ZetaTxServer) DeployZRC20s(
accountOperational, accountAdmin, erc20Addr string,
+ skipChain func(chainID int64) bool,
) (string, error) {
// retrieve account
accOperational, err := zts.clientCtx.Keyring.Key(accountOperational)
@@ -441,8 +442,31 @@ func (zts ZetaTxServer) DeployZRC20s(
deployerAddr = addrOperational.String()
}
+ 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)
+ }
+
+ 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 +479,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 +494,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 +509,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: %w", err)
}
// deploy erc20 zrc20
- res, err = zts.BroadcastTx(deployerAccount, fungibletypes.NewMsgDeployFungibleCoinZRC20(
+ erc20zrc20Addr, err := deploy(fungibletypes.NewMsgDeployFungibleCoinZRC20(
deployerAddr,
erc20Addr,
chains.GoerliLocalnet.ChainId,
@@ -522,15 +540,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
}
diff --git a/go.mod b/go.mod
index 7625fc2047..eb87031b7e 100644
--- a/go.mod
+++ b/go.mod
@@ -16,20 +16,20 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0
github.com/cockroachdb/errors v1.11.1
github.com/coinbase/rosetta-sdk-go v0.7.9
- github.com/cometbft/cometbft v0.37.4
+ github.com/cometbft/cometbft v0.37.5
github.com/cometbft/cometbft-db v0.12.0
github.com/cosmos/btcutil v1.0.5
- github.com/cosmos/cosmos-sdk v0.47.10
- github.com/cosmos/gogoproto v1.4.10
+ github.com/cosmos/cosmos-sdk v0.47.14
+ github.com/cosmos/gogoproto v1.7.0
github.com/cosmos/ibc-go/v7 v7.4.0
- github.com/davecgh/go-spew v1.1.1
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/emicklei/proto v1.11.1
github.com/ethereum/go-ethereum v1.10.26
- github.com/fatih/color v1.13.0
+ github.com/fatih/color v1.14.1
github.com/frumioj/crypto11 v1.2.5-0.20210823151709-946ce662cc0e
github.com/gagliardetto/solana-go v1.10.0
github.com/golang/mock v1.6.0
- github.com/golang/protobuf v1.5.3
+ github.com/golang/protobuf v1.5.4
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
@@ -49,26 +49,26 @@ require (
github.com/prometheus/client_golang v1.14.0
github.com/rakyll/statik v0.1.7
github.com/rs/cors v1.8.3
- github.com/rs/zerolog v1.32.0
+ github.com/rs/zerolog v1.33.0
github.com/samber/lo v1.46.0
github.com/spf13/afero v1.11.0
- github.com/spf13/cast v1.5.1
+ github.com/spf13/cast v1.6.0
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
- github.com/spf13/viper v1.16.0
+ github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.9.0
- github.com/zeta-chain/ethermint v0.0.0-20240927155358-f34e2a4a86f1
+ github.com/zeta-chain/ethermint v0.0.0-20241010181243-044e22bdb7e7
github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138
github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240924201108-3a274ce7bad0
gitlab.com/thorchain/tss/go-tss v1.6.5
go.nhat.io/grpcmock v0.25.0
golang.org/x/crypto v0.23.0
- golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb
+ golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/net v0.25.0
golang.org/x/sync v0.7.0
- google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0
- google.golang.org/grpc v1.60.1
- google.golang.org/protobuf v1.32.0
+ google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80
+ google.golang.org/grpc v1.62.1
+ google.golang.org/protobuf v1.33.0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
@@ -77,15 +77,15 @@ require (
)
require (
- cloud.google.com/go v0.111.0 // indirect
+ cloud.google.com/go v0.112.0 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
- cloud.google.com/go/storage v1.35.1 // indirect
+ cloud.google.com/go/storage v1.36.0 // indirect
cosmossdk.io/api v0.3.1 // indirect
cosmossdk.io/core v0.5.1 // indirect
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
- cosmossdk.io/log v1.3.1 // indirect
+ cosmossdk.io/log v1.4.1 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect
@@ -116,7 +116,7 @@ require (
github.com/confio/ics23/go v0.9.0 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
- github.com/cosmos/cosmos-proto v1.0.0-beta.4 // indirect
+ github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v0.20.1 // indirect
@@ -140,9 +140,9 @@ require (
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
- github.com/felixge/httpsnoop v1.0.2 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/flynn/noise v1.0.0 // indirect
- github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gagliardetto/binary v0.8.0 // indirect
github.com/gagliardetto/treeout v0.1.4 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect
@@ -150,7 +150,7 @@ require (
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
- github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
@@ -159,7 +159,7 @@ require (
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.3 // indirect
- github.com/golang/glog v1.1.2 // indirect
+ github.com/golang/glog v1.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
@@ -202,7 +202,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/klauspost/compress v1.16.7 // indirect
+ github.com/klauspost/compress v1.17.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/kr/pretty v0.3.1 // indirect
@@ -258,9 +258,9 @@ require (
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/otiai10/primes v0.0.0-20180210170552-f6d2a1ba97c4 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
- github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+ github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polydawn/refmt v0.89.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
@@ -275,11 +275,10 @@ require (
github.com/sergi/go-diff v1.3.1 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
- github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/status-im/keycard-go v0.2.0 // indirect
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect
github.com/stretchr/objx v0.5.2 // indirect
- github.com/subosito/gotenv v1.4.2 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
github.com/swaggest/assertjson v1.9.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/btcd v0.1.1 // indirect
@@ -304,9 +303,9 @@ require (
go.nhat.io/matcher/v2 v2.0.0 // indirect
go.nhat.io/wait v0.1.0 // indirect
go.opencensus.io v0.24.0 // indirect
- go.opentelemetry.io/otel v1.19.0 // indirect
- go.opentelemetry.io/otel/metric v1.19.0 // indirect
- go.opentelemetry.io/otel/trace v1.19.0 // indirect
+ go.opentelemetry.io/otel v1.21.0 // indirect
+ go.opentelemetry.io/otel/metric v1.21.0 // indirect
+ go.opentelemetry.io/otel/trace v1.21.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/dig v1.17.0 // indirect
go.uber.org/fx v1.19.2 // indirect
@@ -314,17 +313,17 @@ require (
go.uber.org/ratelimit v0.2.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/mod v0.17.0 // indirect
- golang.org/x/oauth2 v0.15.0 // indirect
- golang.org/x/sys v0.20.0 // indirect
+ golang.org/x/oauth2 v0.16.0 // indirect
+ golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gonum.org/v1/gonum v0.13.0 // indirect
- google.golang.org/api v0.152.0 // indirect
+ google.golang.org/api v0.155.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
- google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
+ google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
lukechampine.com/blake3 v1.2.1 // indirect
@@ -342,7 +341,12 @@ require (
require (
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect
+ github.com/sagikazarmark/locafero v0.4.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/snksoft/crc v1.1.0 // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
)
replace (
diff --git a/go.sum b/go.sum
index dd2ce7f425..14bfa8eb35 100644
--- a/go.sum
+++ b/go.sum
@@ -57,8 +57,9 @@ cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5x
cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk=
cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM=
cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
-cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
+cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
+cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@@ -1057,8 +1058,8 @@ cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E=
-cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
-cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
+cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
+cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=
@@ -1213,8 +1214,9 @@ cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vM
cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE=
cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0=
cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U=
-cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI=
cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM=
+cosmossdk.io/log v1.4.1 h1:wKdjfDRbDyZRuWa8M+9nuvpVYxrEOwbD/CA8hvhU8QM=
+cosmossdk.io/log v1.4.1/go.mod h1:k08v0Pyq+gCP6phvdI6RCGhLf/r425UT6Rk/m+o74rU=
cosmossdk.io/math v1.0.0-beta.3/go.mod h1:3LYasri3Zna4XpbrTNdKsWmD5fHHkaNAod/mNT9XdE4=
cosmossdk.io/math v1.0.0-beta.4/go.mod h1:An0MllWJY6PxibUpnwGk8jOm+a/qIxlKmL5Zyp9NnaM=
cosmossdk.io/math v1.0.0-beta.6/go.mod h1:gUVtWwIzfSXqcOT+lBVz2jyjfua8DoBdzRsIyaUAT/8=
@@ -1363,8 +1365,9 @@ github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOp
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
-github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
+github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
+github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
@@ -1695,6 +1698,8 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
+github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
github.com/cockroachdb/apd/v3 v3.1.0/go.mod h1:6qgPBMXjATAdD/VefbRP9NoSLKjbB4LCoA7gN4LpHs4=
@@ -1733,8 +1738,9 @@ github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZ
github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ8yMsUZTG7FXCR2M=
github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c=
github.com/cometbft/cometbft v0.34.27-alpha.1/go.mod h1:hct3hasQ2hIF3HoD7foVw4RaqTNSSeJ/lgcrVK6uDvs=
-github.com/cometbft/cometbft v0.37.4 h1:xyvvEqlyfK8MgNIIKVJaMsuIp03wxOcFmVkT26+Ikpg=
github.com/cometbft/cometbft v0.37.4/go.mod h1:Cmg5Hp4sNpapm7j+x0xRyt2g0juQfmB752ous+pA0G8=
+github.com/cometbft/cometbft v0.37.5 h1:/U/TlgMh4NdnXNo+YU9T2NMCWyhXNDF34Mx582jlvq0=
+github.com/cometbft/cometbft v0.37.5/go.mod h1:QC+mU0lBhKn8r9qvmnq53Dmf3DWBt4VtkcKw2C81wxY=
github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0=
github.com/cometbft/cometbft-db v0.12.0 h1:v77/z0VyfSU7k682IzZeZPFZrQAKiQwkqGN0QzAjMi0=
github.com/cometbft/cometbft-db v0.12.0/go.mod h1:aX2NbCrjNVd2ZajYxt1BsiFf/Z+TQ2MN0VxdicheYuw=
@@ -1891,12 +1897,14 @@ github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32/go.mod h1:kwMlEC4
github.com/cosmos/cosmos-proto v1.0.0-alpha7/go.mod h1:dosO4pSAbJF8zWCzCoTWP7nNsjcvSUBQmniFxDg5daw=
github.com/cosmos/cosmos-proto v1.0.0-alpha8/go.mod h1:6/p+Bc4O8JKeZqe0VqUGTX31eoYqemTT4C1hLCWsO7I=
github.com/cosmos/cosmos-proto v1.0.0-beta.1/go.mod h1:8k2GNZghi5sDRFw/scPL8gMSowT1vDA+5ouxL8GjaUE=
-github.com/cosmos/cosmos-proto v1.0.0-beta.4 h1:aEL7tU/rLOmxZQ9z4i7mzxcLbSCY48OdY7lIWTLG7oU=
github.com/cosmos/cosmos-proto v1.0.0-beta.4/go.mod h1:oeB+FyVzG3XrQJbJng0EnV8Vljfk9XvTIpGILNU/9Co=
+github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
+github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20221207001918-ed5124f932fd/go.mod h1:dmCp0cYz6/S5KWKJ9QzePRwWNEbcSu+jbBTRgnzPnPo=
github.com/cosmos/cosmos-sdk v0.47.0-rc2.0.20230220103612-f094a0c33410/go.mod h1:SNeHakoKi9YlfhI53+ijEZZIHp90NrB1By4NgWN0KZ0=
-github.com/cosmos/cosmos-sdk v0.47.10 h1:Wxf5yEN3jZbG4fftxAMKB6rpd8ME0mxuCVihpz65dt0=
github.com/cosmos/cosmos-sdk v0.47.10/go.mod h1:UWpgWkhcsBIATS68uUC0del7IiBN4hPv/vqg8Zz23uw=
+github.com/cosmos/cosmos-sdk v0.47.14 h1:vD9JyIdlbVaXMOE/BLamViQvylfUq0E0FpqdPVv/fWw=
+github.com/cosmos/cosmos-sdk v0.47.14/go.mod h1:GrDj/zd9Tiuy8ZpG9PbUbhghCVU7lwyH0GS7CpxHpyM=
github.com/cosmos/cosmos-sdk/db v1.0.0-beta.1.0.20220726092710-f848e4300a8a/go.mod h1:c8IO23vgNxueCCJlSI9awQtcxsvc+buzaeThB85qfBU=
github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 h1:iKclrn3YEOwk4jQHT2ulgzuXyxmzmPczUalMwW4XH9k=
github.com/cosmos/cosmos-sdk/ics23/go v0.8.0/go.mod h1:2a4dBq88TUoqoWAU5eu0lGvpFP3wWDPgdHPargtyw30=
@@ -1909,8 +1917,9 @@ github.com/cosmos/gogoproto v1.4.1/go.mod h1:Ac9lzL4vFpBMcptJROQ6dQ4M3pOEK5Z/l0Q
github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU=
github.com/cosmos/gogoproto v1.4.3/go.mod h1:0hLIG5TR7IvV1fme1HCFKjfzW9X2x0Mo+RooWXCnOWU=
github.com/cosmos/gogoproto v1.4.4/go.mod h1:/yl6/nLwsZcZ2JY3OrqjRqvqCG9InUMcXRfRjQiF9DU=
-github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI=
github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek=
+github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro=
+github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0=
github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw=
github.com/cosmos/iavl v0.19.4/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw=
github.com/cosmos/iavl v0.20.0-alpha4/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A=
@@ -1963,8 +1972,9 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
@@ -2108,6 +2118,8 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni
github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs=
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
+github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
+github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
@@ -2125,13 +2137,15 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
-github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
+github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/firefart/nonamedreturns v1.0.1/go.mod h1:D3dpIBojGGNh5UfElmwPu73SwDCm+VKhHYqwlNOk2uQ=
github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
@@ -2155,16 +2169,17 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
-github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
-github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/frumioj/crypto11 v1.2.5-0.20210823151709-946ce662cc0e h1:HRagc2sBsKLDvVVXQMaCEU8ueRFAl3txucwykhQPbGc=
github.com/frumioj/crypto11 v1.2.5-0.20210823151709-946ce662cc0e/go.mod h1:/1u7qgWwAI7wja4BdNu5Vd5gqMtmtoiACHlhl46uY1E=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
github.com/fzipp/gocyclo v0.5.1/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
@@ -2255,8 +2270,9 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
+github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
@@ -2381,8 +2397,9 @@ github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgR
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
-github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
+github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
+github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -2421,8 +2438,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -2993,8 +3011,9 @@ github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrD
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
-github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
+github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
@@ -3555,8 +3574,8 @@ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZO
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
-github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
-github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -3593,8 +3612,9 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pointlander/compress v1.1.1-0.20190518213731-ff44bd196cc3/go.mod h1:q5NXNGzqj5uPnVuhGkZfmgHqNUhf15VLi6L9kW0VEc0=
github.com/pointlander/jetset v1.0.1-0.20190518214125-eee7eff80bd4/go.mod h1:RdR1j20Aj5pB6+fw6Y9Ur7lMHpegTEjY1vc19hEZL40=
github.com/pointlander/peg v1.0.1/go.mod h1:5hsGDQR2oZI4QoWz0/Kdg3VSVEC31iJw/b7WjqCBGRI=
@@ -3735,8 +3755,9 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
-github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
+github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
+github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
@@ -3758,6 +3779,10 @@ github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43
github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA=
github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
github.com/sagikazarmark/crypt v0.8.0/go.mod h1:TmKwZAo97S4Fy4sfMH/HX/cQP5D+ijra2NyLpNNmttY=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ=
github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
@@ -3845,6 +3870,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak=
github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
@@ -3863,8 +3890,8 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
-github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
-github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
@@ -3880,7 +3907,6 @@ github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUq
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -3899,8 +3925,8 @@ github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUs
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
-github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
-github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
@@ -3951,8 +3977,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
-github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
-github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
@@ -4172,8 +4198,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPS
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
-github.com/zeta-chain/ethermint v0.0.0-20240927155358-f34e2a4a86f1 h1:o0Sh6Y2PKcG634hWqRWmWqBteSuoQUDxIR04OX9Llr8=
-github.com/zeta-chain/ethermint v0.0.0-20240927155358-f34e2a4a86f1/go.mod h1:NeQEwcKBpKAUxIsii2F+jfyOD94jN/3fzPMv/1kVF9M=
+github.com/zeta-chain/ethermint v0.0.0-20241010181243-044e22bdb7e7 h1:eW5aAW9Ag4GDMa7qzsQm6EWC6SENQUokHUpCdS+WSSg=
+github.com/zeta-chain/ethermint v0.0.0-20241010181243-044e22bdb7e7/go.mod h1:bY9wUmkSjTJ65U7LF3e9Pc2737NqxCXGN+b/U2Rm5rU=
github.com/zeta-chain/go-ethereum v1.10.26-spc h1:NvY4rR9yw52wfxWt7YoFsWbaIwVMyOtTsWKqGAXk+sE=
github.com/zeta-chain/go-ethereum v1.10.26-spc/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo=
github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4 h1:FmO3HfVdZ7LzxBUfg6sVzV7ilKElQU2DZm8PxJ7KcYI=
@@ -4260,17 +4286,22 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.2
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0/go.mod h1:LsankqVDx4W+RhZNA5uWarULII/MBhF5qwCYxTuyXjs=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.33.0/go.mod h1:y/SlJpJQPd2UzfBCj0E9Flk9FDCtTyqUmaCB41qFrWI=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.3/go.mod h1:Dts42MGkzZne2yCru741+bFiTMWkIj/LLRizad7b9tw=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0/go.mod h1:vHItvsnJtp7ES++nFLLFBzUWny7fJQSvTlxFcqQGUr4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0/go.mod h1:tLYsuf2v8fZreBVwp9gVMhefZlLFZaUiNVSq8QxXRII=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk=
go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4=
go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM=
go.opentelemetry.io/otel v1.11.0/go.mod h1:H2KtuEphyMvlhZ+F7tg9GRhAOe60moNx61Ex+WmiKkk=
-go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
+go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
+go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/exporters/jaeger v1.4.1/go.mod h1:ZW7vkOu9nC1CxsD8bHNHCia5JUbwP39vxgd1q4Z5rCI=
go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
@@ -4285,14 +4316,16 @@ go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/on
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g=
go.opentelemetry.io/otel/metric v0.32.3/go.mod h1:pgiGmKohxHyTPHGOff+vrtIH39/R9fiO/WoenUQ3kcc=
-go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
+go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
+go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE=
-go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
+go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
+go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
@@ -4301,8 +4334,9 @@ go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+
go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc=
go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4=
go.opentelemetry.io/otel/trace v1.11.0/go.mod h1:nyYjis9jy0gytE9LXGU+/m1sHTKbRY0fX0hulNNDP1U=
-go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
+go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
+go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ=
go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ=
@@ -4452,8 +4486,9 @@ golang.org/x/exp v0.0.0-20221019170559-20944726eadf/go.mod h1:cyybsKvd6eL0RnXn6p
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
-golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us=
golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
@@ -4672,8 +4707,8 @@ golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
-golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
-golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
+golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
+golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -4905,8 +4940,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -5226,8 +5261,8 @@ google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvy
google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750=
google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk=
google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI=
-google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY=
-google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
+google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA=
+google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -5428,8 +5463,9 @@ google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJ
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY=
google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic=
-google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg=
google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0=
+google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
+google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
@@ -5448,8 +5484,9 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.
google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg=
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI=
-google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c=
+google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU=
+google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw=
@@ -5473,8 +5510,9 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@@ -5539,8 +5577,9 @@ google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSs
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
-google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
+google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
+google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -5565,8 +5604,9 @@ google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/chains/chain.go b/pkg/chains/chain.go
index f5f6ee605d..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
@@ -93,6 +94,10 @@ func (chain Chain) IsBitcoinChain() bool {
return chain.Consensus == Consensus_bitcoin
}
+func (chain Chain) IsTONChain() bool {
+ return chain.Consensus == Consensus_catchain_consensus
+}
+
// 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
@@ -104,6 +109,14 @@ func DecodeAddressFromChainID(chainID int64, addr string, additionalChains []Cha
return []byte(addr), nil
case IsSolanaChain(chainID, additionalChains):
return []byte(addr), nil
+ case IsTONChain(chainID, additionalChains):
+ // 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)
}
@@ -132,6 +145,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
diff --git a/pkg/chains/chain_test.go b/pkg/chains/chain_test.go
index 349651e7b9..4da81c7eee 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",
@@ -301,6 +301,19 @@ func TestDecodeAddressFromChainID(t *testing.T) {
addr: "DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw",
want: []byte("DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw"),
},
+ {
+ name: "TON",
+ chainID: chains.TONMainnet.ChainId,
+ 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/chains/chains.go b/pkg/chains/chains.go
index addd6a14df..70692ccc9c 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
*/
@@ -249,6 +261,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
*/
@@ -325,6 +348,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
*/
@@ -392,6 +426,9 @@ func DefaultChainsList() []Chain {
SolanaMainnet,
SolanaDevnet,
SolanaLocalnet,
+ TONMainnet,
+ TONTestnet,
+ TONLocalnet,
}
}
@@ -449,17 +486,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 90545bee1c..73c3521ffa 100644
--- a/pkg/chains/chains_test.go
+++ b/pkg/chains/chains_test.go
@@ -36,6 +36,7 @@ func TestChainListByNetworkType(t *testing.T) {
chains.OptimismMainnet,
chains.BaseMainnet,
chains.SolanaMainnet,
+ chains.TONMainnet,
},
},
{
@@ -54,6 +55,7 @@ func TestChainListByNetworkType(t *testing.T) {
chains.OptimismSepolia,
chains.BaseSepolia,
chains.SolanaDevnet,
+ chains.TONTestnet,
},
},
{
@@ -64,6 +66,7 @@ func TestChainListByNetworkType(t *testing.T) {
chains.BitcoinRegtest,
chains.GoerliLocalnet,
chains.SolanaLocalnet,
+ chains.TONLocalnet,
},
},
}
@@ -132,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 {
@@ -168,6 +176,9 @@ func TestDefaultChainList(t *testing.T) {
chains.SolanaMainnet,
chains.SolanaDevnet,
chains.SolanaLocalnet,
+ chains.TONMainnet,
+ chains.TONTestnet,
+ chains.TONLocalnet,
}, chains.DefaultChainsList())
}
@@ -202,6 +213,9 @@ func TestChainListByGateway(t *testing.T) {
chains.SolanaMainnet,
chains.SolanaDevnet,
chains.SolanaLocalnet,
+ chains.TONMainnet,
+ chains.TONTestnet,
+ chains.TONLocalnet,
},
},
{
@@ -246,6 +260,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/pkg/contracts/ton/gateway.go b/pkg/contracts/ton/gateway.go
new file mode 100644
index 0000000000..33ca2c7977
--- /dev/null
+++ b/pkg/contracts/ton/gateway.go
@@ -0,0 +1,258 @@
+// Package ton provider bindings for TON blockchain including Gateway contract wrapper.
+package ton
+
+import (
+ "cosmossdk.io/math"
+ "github.com/pkg/errors"
+ "github.com/tonkeeper/tongo/boc"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/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
+}
+
+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")
+)
+
+// 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
+}
+
+// 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
+ return nil, errors.Wrapf(ErrParse, "tx %s is not successful (exit code %d)", tx.Hash().Hex(), exitCode)
+ }
+
+ 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
+}
+
+// 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
+}
+
+// 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)
+ 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: GramsToUint(amount)}
+ case OpDeposit:
+ content, errContent = parseDeposit(tx, sender, body)
+ 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())
+ }
+
+ 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 := UnmarshalEVMAddress(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();
+
+ 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 {
+ 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: GramsToUint(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")
+ }
+
+ callData, err := UnmarshalSnakeCell(callDataCell)
+ if err != nil {
+ return DepositAndCall{}, errors.Wrap(err, "unable to unmarshal call data")
+ }
+
+ return DepositAndCall{Deposit: deposit, CallData: callData}, 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")
+ }
+
+ var (
+ inMsg = tx.Msgs.InMsg.Value.Value
+ body = boc.Cell(inMsg.Body.Value)
+ )
+
+ return &body, nil
+}
diff --git a/pkg/contracts/ton/gateway_op.go b/pkg/contracts/ton/gateway_op.go
new file mode 100644
index 0000000000..7d711ab89c
--- /dev/null
+++ b/pkg/contracts/ton/gateway_op.go
@@ -0,0 +1,115 @@
+package ton
+
+import (
+ "errors"
+
+ "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()
+
+ return b, writeDepositBody(b, d.Recipient)
+}
+
+// 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) {
+ b := boc.NewCell()
+
+ return b, writeDepositAndCallBody(b, d.Recipient, d.CallData)
+}
+
+func writeDepositBody(b *boc.Cell, recipient eth.Address) error {
+ return ErrCollect(
+ b.WriteUint(uint64(OpDeposit), sizeOpCode),
+ b.WriteUint(0, sizeQueryID),
+ 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
new file mode 100644
index 0000000000..5dd9c21340
--- /dev/null
+++ b/pkg/contracts/ton/gateway_send.go
@@ -0,0 +1,72 @@
+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
+}
+
+// see https://docs.ton.org/develop/smart-contracts/messages#message-modes
+const (
+ SendFlagSeparateFees = uint8(1)
+ 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)
+}
+
+// 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")
+ }
+
+ return s.Send(ctx, wallet.Message{
+ Amount: tlb.Coins(amount.Uint64()),
+ Address: gw.accountID,
+ Body: body,
+ Mode: sendMode,
+ })
+}
diff --git a/pkg/contracts/ton/gateway_test.go b/pkg/contracts/ton/gateway_test.go
new file mode 100644
index 0000000000..dc680761ff
--- /dev/null
+++ b/pkg/contracts/ton/gateway_test.go
@@ -0,0 +1,313 @@
+package ton
+
+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"
+ "github.com/tonkeeper/tongo/tlb"
+ "github.com/tonkeeper/tongo/ton"
+)
+
+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
+ 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()))
+
+ // 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) {
+ // 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
+ 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)
+
+ // 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) {
+ // 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)
+
+ // 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) {
+ 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)
+
+ // 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 TestFiltering(t *testing.T) {
+ t.Run("Inbound", func(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ skip bool
+ error bool
+ }{
+ // Should be parsed and filtered
+ {"00-donation", false, false},
+ {"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, FilterInbounds)
+
+ 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")
+
+ // ASSERT
+ require.Equal(t, uint64(26023788000003), tx.Lt)
+ 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 := MarshalSnakeCell(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
+
+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, fixture) {
+ t.Helper()
+
+ var (
+ filename = fmt.Sprintf("testdata/%s.json", name)
+ b = readFixtureFile(t, filename)
+ )
+
+ // bag of cells
+ var fx fixture
+
+ require.NoError(t, json.Unmarshal(b, &fx))
+
+ cells, err := boc.DeserializeBocHex(fx.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{}))
+
+ t.Logf("Loaded fixture %s\n%s", filename, fx.Description)
+
+ 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/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-deposit.json b/pkg/contracts/ton/testdata/01-deposit.json
new file mode 100644
index 0000000000..d22170ddf0
--- /dev/null
+++ b/pkg/contracts/ton/testdata/01-deposit.json
@@ -0,0 +1,8 @@
+{
+ "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b",
+ "boc": "b5ee9c7201020a0100023d0003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017ab22a3b3031b48df020aa3647a59163a25772d81991916a2bf523b771d89deec9e5be15d58000017ab20f8740366ead1e30003465b1d0080102030201e004050082723e346a6461f48fab691e87d9fd5954595eb15351fa1c536486a863d9708b79c313c7e41677dcade29f2b424cdad8712132fbd5465b37df2e1763369e2fb12da0021904222490ee6b28018646dca110080900f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d0ee6b28000608235a00002f5645476604cdd5a3c60000003280000000000000006c6d35f934b257cebf76cf01f29a0ae9bd54b022c00101df06015de004cbec44e40ad75610e23fc357071c1c1f7e1e1a31833ac17b131d6a462d0a42d800002f5645476608cdd5a3c6c007008b0000006500000000000000008012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1e876046701b1b4d7e4d2c95f3afddb3c07ca682ba6f552c08b009e42d5ac3d090000000000000000007e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98510184c2880c0000000000002000000000003da7f47a5d1898330bd18801617ae23a388cbaf527c312921718ef36ce9cf8c4e40901d04",
+ "description": "Sample deposit to gw contract. https://testnet.tonviewer.com/transaction/cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf",
+ "hash": "cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf",
+ "logicalTime": 26023788000003,
+ "test": true
+}
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
diff --git a/pkg/contracts/ton/testdata/readme.md b/pkg/contracts/ton/testdata/readme.md
new file mode 100644
index 0000000000..8695f06982
--- /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 -testnet \
+ kQCZfYicgVrqwhxH-Grg44OD78PDRjBnWC9iY61IxaFIW77M \
+ 26023788000003 \
+ cbd6e2261334d08120e2fef428ecbb4e7773606ced878d0e6da204f2b4bf42bf | jq
+```
+
+Returns
+
+```json
+{
+ "account": "0:997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b",
+ "boc": "b5ee9c7201020a0100023d0003b57997d889c815aeac21c47f86ae0e38383efc3c3463067582f6263ad48c5a1485b000017ab22a3b3031b48df020aa3647a59163a25772d81991916a2bf523b771d89deec9e5be15d58000017ab20f8740366ead1e30003465b1d0080102030201e004050082723e346a6461f48fab691e87d9fd5954595eb15351fa1c536486a863d9708b79c313c7e41677dcade29f2b424cdad8712132fbd5465b37df2e1763369e2fb12da0021904222490ee6b28018646dca110080900f168012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1f00265f62272056bab08711fe1ab838e0e0fbf0f0d18c19d60bd898eb5231685216d0ee6b28000608235a00002f5645476604cdd5a3c60000003280000000000000006c6d35f934b257cebf76cf01f29a0ae9bd54b022c00101df06015de004cbec44e40ad75610e23fc357071c1c1f7e1e1a31833ac17b131d6a462d0a42d800002f5645476608cdd5a3c6c007008b0000006500000000000000008012b298e33d8992beccd0765f63941613bc9483edf610f74991a4cb72d4999ca1e876046701b1b4d7e4d2c95f3afddb3c07ca682ba6f552c08b009e42d5ac3d090000000000000000007e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98510184c2880c0000000000002000000000003da7f47a5d1898330bd18801617ae23a388cbaf527c312921718ef36ce9cf8c4e40901d04",
+ "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
new file mode 100644
index 0000000000..efe76ca3aa
--- /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 scraper.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), lt, 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.NewClientWithDefaultMainnet()
+ 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")
+}
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
+}
diff --git a/pkg/ticker/ticker.go b/pkg/ticker/ticker.go
index 94fc9ab2fb..2a5a7edff1 100644
--- a/pkg/ticker/ticker.go
+++ b/pkg/ticker/ticker.go
@@ -34,16 +34,18 @@ 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
// runnerMu is a mutex to prevent double run
runnerMu sync.Mutex
@@ -51,25 +53,47 @@ 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
}
-// 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.
@@ -96,24 +120,33 @@ 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.signalChan = 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 {
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 errors.Wrap(err, "ticker task failed")
+ return fmt.Errorf("ticker task failed: %w", err)
}
- case <-t.signalChan:
+ case <-t.externalStopChan:
+ t.Stop()
return nil
}
}
@@ -139,11 +172,18 @@ func (t *Ticker) Stop() {
defer t.stateMu.Unlock()
// noop
- if t.stopped || t.signalChan == nil {
+ if t.stopped {
return
}
- close(t.signalChan)
+ t.ctxCancel()
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 4d890bf051..60d6c74dc8 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) {
@@ -148,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) {
@@ -173,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) {
@@ -197,4 +200,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/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto b/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto
index 0ca1f56f7d..9b634a6378 100644
--- a/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto
+++ b/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto
@@ -17,7 +17,7 @@ enum CctxStatus {
Reverted = 5; // inbound reverted.
Aborted =
6; // inbound tx error or invalid paramters and cannot revert; just abort.
- // But the amount can be refunded to zetachain using and admin proposal
+ // But the amount can be refunded to zetachain using and admin proposal
}
enum TxFinalizationStatus {
@@ -94,7 +94,12 @@ message OutboundParams {
message Status {
CctxStatus status = 1;
+ // status_message carries information about the status transitions:
+ // why they were triggered, old and new status.
string status_message = 2;
+ // error_message carries information about the error that caused the tx
+ // to be PendingRevert, Reverted or Aborted.
+ string error_message = 6;
int64 lastUpdate_timestamp = 3;
bool isAbortRefunded = 4;
// when the CCTX was created. only populated on new transactions.
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/testutil/sample/crosschain.go b/testutil/sample/crosschain.go
index 8f09271e23..ff17525734 100644
--- a/testutil/sample/crosschain.go
+++ b/testutil/sample/crosschain.go
@@ -191,6 +191,7 @@ func Status(t *testing.T, index string) *types.Status {
return &types.Status{
Status: types.CctxStatus(r.Intn(100)),
StatusMessage: String(),
+ ErrorMessage: String(),
CreatedTimestamp: createdAt,
LastUpdateTimestamp: createdAt,
}
diff --git a/testutil/sample/sample_ton.go b/testutil/sample/sample_ton.go
new file mode 100644
index 0000000000..01ce8724c5
--- /dev/null
+++ b/testutil/sample/sample_ton.go
@@ -0,0 +1,272 @@
+package sample
+
+import (
+ "crypto/rand"
+ "reflect"
+ "testing"
+ "time"
+ "unsafe"
+
+ "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 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)
+
+ 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 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)
+
+ 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 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)
+
+ 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]]
+ }
+
+ tx := 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},
+ },
+ }
+
+ setTXHash(&tx.Transaction, Hash())
+
+ return tx
+}
+
+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
+}
+
+// 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/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())
+ })
+
+}
diff --git a/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts b/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts
index 1ffa04eaca..e5295ee133 100644
--- a/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts
+++ b/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts
@@ -355,10 +355,21 @@ export declare class Status extends Message {
status: CctxStatus;
/**
+ * status_message carries information about the status transitions:
+ * why they were triggered, old and new status.
+ *
* @generated from field: string status_message = 2;
*/
statusMessage: string;
+ /**
+ * error_message carries information about the error that caused the tx
+ * to be PendingRevert, Reverted or Aborted.
+ *
+ * @generated from field: string error_message = 6;
+ */
+ errorMessage: string;
+
/**
* @generated from field: int64 lastUpdate_timestamp = 3;
*/
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,
}
/**
diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go
index 39a33f4edc..9576bcfe32 100644
--- a/x/crosschain/keeper/cctx_gateway_observers.go
+++ b/x/crosschain/keeper/cctx_gateway_observers.go
@@ -75,7 +75,7 @@ func (c CCTXGatewayObservers) InitiateOutbound(
}()
if err != nil {
// do not commit anything here as the CCTX should be aborted
- config.CCTX.SetAbort(err.Error())
+ config.CCTX.SetAbort("internal error", err.Error())
return types.CctxStatus_Aborted, err
}
commit()
diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go
index 81366d6af3..fc247ad7a4 100644
--- a/x/crosschain/keeper/cctx_gateway_zevm.go
+++ b/x/crosschain/keeper/cctx_gateway_zevm.go
@@ -28,7 +28,9 @@ func (c CCTXGatewayZEVM) InitiateOutbound(
if err != nil && !isContractReverted {
// exceptional case; internal error; should abort CCTX
- config.CCTX.SetAbort(err.Error())
+ config.CCTX.SetAbort(
+ "error during deposit that is not smart contract revert",
+ err.Error())
return types.CctxStatus_Aborted, err
}
diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go
index 17c848613a..2f3acdd744 100644
--- a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go
+++ b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go
@@ -53,7 +53,9 @@ func (k Keeper) ValidateOutboundZEVM(
cctx.InboundParams.Amount,
)
if err != nil {
- cctx.SetAbort(fmt.Sprintf("%s : %s", depositErr, err.Error()))
+ cctx.SetAbort(
+ "revert failed",
+ fmt.Sprintf("deposit error: %s, processing error: %s", depositErr.Error(), err.Error()))
return types.CctxStatus_Aborted
}
@@ -122,7 +124,7 @@ func (k Keeper) processFailedOutboundObservers(ctx sdk.Context, cctx *types.Cros
if cctx.InboundParams.CoinType == coin.CoinType_Cmd {
// if the cctx is of coin type cmd or the sender chain is zeta chain, then we do not revert, the cctx is aborted
cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed
- cctx.SetAbort("Outbound failed, cmd cctx reverted")
+ cctx.SetAbort("", "outbound failed for admin tx")
} else if chains.IsZetaChain(cctx.InboundParams.SenderChainId, k.GetAuthorityKeeper().GetAdditionalChainList(ctx)) {
switch cctx.InboundParams.CoinType {
// Try revert if the coin-type is ZETA
@@ -137,7 +139,7 @@ func (k Keeper) processFailedOutboundObservers(ctx sdk.Context, cctx *types.Cros
default:
{
cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed
- cctx.SetAbort("Outbound failed for non-ZETA cctx")
+ cctx.SetAbort("", "outbound failed for non-ZETA cctx")
}
}
} else {
@@ -195,10 +197,10 @@ func (k Keeper) processFailedOutboundOnExternalChain(
return err
}
// Not setting the finalization status here, the required changes have been made while creating the revert tx
- cctx.SetPendingRevert(revertMsg)
+ cctx.SetPendingRevert("", revertMsg)
case types.CctxStatus_PendingRevert:
cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed
- cctx.SetAbort("Outbound failed: revert failed; abort TX")
+ cctx.SetAbort("aborted while processing failed outbound", "outbound and revert failed")
}
return nil
}
@@ -225,9 +227,9 @@ func (k Keeper) processSuccessfulOutbound(
oldStatus := cctx.CctxStatus.Status
switch oldStatus {
case types.CctxStatus_PendingRevert:
- cctx.SetReverted("Outbound succeeded, revert executed")
+ cctx.SetReverted("", "revert executed")
case types.CctxStatus_PendingOutbound:
- cctx.SetOutboundMined("Outbound succeeded, mined")
+ cctx.SetOutboundMined("")
default:
return
}
@@ -256,7 +258,7 @@ func (k Keeper) processFailedZETAOutboundOnZEVM(ctx sdk.Context, cctx *types.Cro
}
// Trying to revert the transaction this would get set to a finalized status in the same block as this does not need a TSS singing
- cctx.SetPendingRevert("Outbound failed, trying revert")
+ cctx.SetPendingRevert("", "outbound failed")
data, err := base64.StdEncoding.DecodeString(cctx.RelayedMessage)
if err != nil {
return fmt.Errorf("failed decoding relayed message: %s", err.Error())
@@ -290,7 +292,7 @@ func (k Keeper) processFailedZETAOutboundOnZEVM(ctx sdk.Context, cctx *types.Cro
return fmt.Errorf("failed ZETARevertAndCallContract: %s", err.Error())
}
- cctx.SetReverted("Outbound failed, revert executed")
+ cctx.SetReverted("", "outbound failed")
if len(ctx.TxBytes()) > 0 {
// add event for tendermint transaction hash format
hash := tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash())
@@ -336,7 +338,7 @@ func (k Keeper) processFailedOutboundV2(ctx sdk.Context, cctx *types.CrossChainT
}
// update status
- cctx.SetPendingRevert("Outbound failed, trying revert")
+ cctx.SetPendingRevert("", "outbound failed")
// process the revert on ZEVM
if err := k.fungibleKeeper.ProcessV2RevertDeposit(
@@ -354,7 +356,7 @@ func (k Keeper) processFailedOutboundV2(ctx sdk.Context, cctx *types.CrossChainT
}
// tx is reverted
- cctx.SetReverted("Outbound failed, revert executed")
+ cctx.SetReverted("", "outbound failed")
// add event for tendermint transaction hash format
if len(ctx.TxBytes()) > 0 {
@@ -367,7 +369,7 @@ func (k Keeper) processFailedOutboundV2(ctx sdk.Context, cctx *types.CrossChainT
cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed
case types.CctxStatus_PendingRevert:
cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed
- cctx.SetAbort("Outbound failed: revert failed; abort TX")
+ cctx.SetAbort("aborted while processing failed outbound", "outbound and revert failed")
}
return nil
}
diff --git a/x/crosschain/keeper/cctx_test.go b/x/crosschain/keeper/cctx_test.go
index 8a51b4cf2a..d1e3f6fafc 100644
--- a/x/crosschain/keeper/cctx_test.go
+++ b/x/crosschain/keeper/cctx_test.go
@@ -170,7 +170,6 @@ func TestCCTXs(t *testing.T) {
require.True(t, found)
require.Equal(t, s, send)
}
-
})
}
}
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/x/crosschain/keeper/initiate_outbound_test.go b/x/crosschain/keeper/initiate_outbound_test.go
index 55ca3e36f8..204179bea5 100644
--- a/x/crosschain/keeper/initiate_outbound_test.go
+++ b/x/crosschain/keeper/initiate_outbound_test.go
@@ -76,7 +76,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) {
require.ErrorContains(t, err, "deposit error")
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(t, types.CctxStatus_Aborted, newStatus)
- require.Equal(t, "deposit error", cctx.CctxStatus.StatusMessage)
+ require.Equal(t, "deposit error", cctx.CctxStatus.ErrorMessage)
})
t.Run(
@@ -111,7 +111,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) {
require.Equal(t, types.CctxStatus_Aborted, newStatus)
require.Contains(
t,
- cctx.CctxStatus.StatusMessage,
+ cctx.CctxStatus.ErrorMessage,
"chain not supported",
)
},
@@ -151,7 +151,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) {
require.Equal(t, types.CctxStatus_Aborted, newStatus)
require.Contains(
t,
- cctx.CctxStatus.StatusMessage,
+ cctx.CctxStatus.ErrorMessage,
"GetRevertGasLimit: foreign coin not found for sender chain",
)
})
@@ -194,7 +194,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) {
require.Equal(t, types.CctxStatus_Aborted, newStatus)
require.Contains(
t,
- cctx.CctxStatus.StatusMessage,
+ cctx.CctxStatus.ErrorMessage,
"chain not supported",
)
},
@@ -239,7 +239,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) {
require.Equal(t, types.CctxStatus_Aborted, newStatus)
require.Contains(
t,
- cctx.CctxStatus.StatusMessage,
+ cctx.CctxStatus.ErrorMessage,
"chain not supported",
)
},
@@ -284,7 +284,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) {
require.NoError(t, err)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(t, types.CctxStatus_Aborted, newStatus)
- require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce")
+ require.Contains(t, cctx.CctxStatus.ErrorMessage, "cannot find receiver chain nonce")
})
t.Run("unable to process zevm deposit HandleEVMDeposit revert successfully", func(t *testing.T) {
@@ -321,7 +321,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) {
require.NoError(t, err)
require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status)
require.Equal(t, types.CctxStatus_PendingRevert, newStatus)
- require.Equal(t, errDeposit.Error(), cctx.CctxStatus.StatusMessage)
+ require.Equal(t, errDeposit.Error(), cctx.CctxStatus.ErrorMessage)
require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce)
})
@@ -361,7 +361,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) {
require.Equal(t, types.CctxStatus_Aborted, newStatus)
require.Contains(
t,
- cctx.CctxStatus.StatusMessage,
+ cctx.CctxStatus.ErrorMessage,
"cannot revert a revert tx",
)
},
@@ -427,7 +427,7 @@ func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) {
require.ErrorIs(t, err, observertypes.ErrSupportedChains)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(t, types.CctxStatus_Aborted, newStatus)
- require.Equal(t, observertypes.ErrSupportedChains.Error(), cctx.CctxStatus.StatusMessage)
+ require.Equal(t, observertypes.ErrSupportedChains.Error(), cctx.CctxStatus.ErrorMessage)
})
t.Run("unable to process crosschain msg passing UpdateNonce fails", func(t *testing.T) {
@@ -456,7 +456,7 @@ func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) {
require.ErrorContains(t, err, "cannot find receiver chain nonce")
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(t, types.CctxStatus_Aborted, newStatus)
- require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce")
+ require.Contains(t, cctx.CctxStatus.ErrorMessage, "cannot find receiver chain nonce")
})
}
diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds.go b/x/crosschain/keeper/msg_server_migrate_tss_funds.go
index c285033f02..8ecc4c47ff 100644
--- a/x/crosschain/keeper/msg_server_migrate_tss_funds.go
+++ b/x/crosschain/keeper/msg_server_migrate_tss_funds.go
@@ -9,7 +9,6 @@ import (
tmbytes "github.com/cometbft/cometbft/libs/bytes"
tmtypes "github.com/cometbft/cometbft/types"
sdk "github.com/cosmos/cosmos-sdk/types"
- "github.com/cosmos/cosmos-sdk/types/errors"
authoritytypes "github.com/zeta-chain/node/x/authority/types"
"github.com/zeta-chain/node/x/crosschain/types"
@@ -26,7 +25,7 @@ func (k msgServer) MigrateTssFunds(
// check if authorized
err := k.GetAuthorityKeeper().CheckAuthorization(ctx, msg)
if err != nil {
- return nil, errors.Wrap(authoritytypes.ErrUnauthorized, err.Error())
+ return nil, errorsmod.Wrap(authoritytypes.ErrUnauthorized, err.Error())
}
if k.zetaObserverKeeper.IsInboundEnabled(ctx) {
diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go
index e824a01707..5aa70be3bd 100644
--- a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go
+++ b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go
@@ -265,7 +265,7 @@ func TestKeeper_VoteInbound(t *testing.T) {
})
}
-func TestStatus_ChangeStatus(t *testing.T) {
+func TestStatus_UpdateCctxStatus(t *testing.T) {
tt := []struct {
Name string
Status types.Status
@@ -302,7 +302,7 @@ func TestStatus_ChangeStatus(t *testing.T) {
for _, test := range tt {
test := test
t.Run(test.Name, func(t *testing.T) {
- test.Status.ChangeStatus(test.NonErrStatus, test.Msg)
+ test.Status.UpdateStatusAndErrorMessages(test.NonErrStatus, test.Msg, "")
if test.IsErr {
require.Equal(t, test.ErrStatus, test.Status.Status)
} else {
diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx.go b/x/crosschain/keeper/msg_server_vote_outbound_tx.go
index 6682c2c705..4bb65a3c8f 100644
--- a/x/crosschain/keeper/msg_server_vote_outbound_tx.go
+++ b/x/crosschain/keeper/msg_server_vote_outbound_tx.go
@@ -185,7 +185,7 @@ SaveFailedOutbound saves a failed outbound transaction.It does the following thi
*/
func (k Keeper) SaveFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, errMessage string, tssPubkey string) {
- cctx.SetAbort(errMessage)
+ cctx.SetAbort("", errMessage)
ctx.Logger().Error(errMessage)
k.SaveOutbound(ctx, cctx, tssPubkey)
}
diff --git a/x/crosschain/migrations/v5/migrate.go b/x/crosschain/migrations/v5/migrate.go
index aeeb22451f..0f21d8646b 100644
--- a/x/crosschain/migrations/v5/migrate.go
+++ b/x/crosschain/migrations/v5/migrate.go
@@ -137,6 +137,7 @@ func SetZetaAccounting(
return nil
}
+
func GetAbortedAmount(cctx types.CrossChainTx) sdkmath.Uint {
if cctx.OutboundParams != nil && !cctx.GetCurrentOutboundParam().Amount.IsZero() {
return cctx.GetCurrentOutboundParam().Amount
diff --git a/x/crosschain/types/cctx.go b/x/crosschain/types/cctx.go
index 0a235dffae..91004511d4 100644
--- a/x/crosschain/types/cctx.go
+++ b/x/crosschain/types/cctx.go
@@ -171,28 +171,28 @@ func (m *CrossChainTx) AddOutbound(
}
// SetAbort sets the CCTX status to Aborted with the given error message.
-func (m CrossChainTx) SetAbort(message string) {
- m.CctxStatus.ChangeStatus(CctxStatus_Aborted, message)
+func (m CrossChainTx) SetAbort(statusMsg, errorMsg string) {
+ m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_Aborted, statusMsg, errorMsg)
}
// SetPendingRevert sets the CCTX status to PendingRevert with the given error message.
-func (m CrossChainTx) SetPendingRevert(message string) {
- m.CctxStatus.ChangeStatus(CctxStatus_PendingRevert, message)
+func (m CrossChainTx) SetPendingRevert(statusMsg, errorMsg string) {
+ m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_PendingRevert, statusMsg, errorMsg)
}
// SetPendingOutbound sets the CCTX status to PendingOutbound with the given error message.
-func (m CrossChainTx) SetPendingOutbound(message string) {
- m.CctxStatus.ChangeStatus(CctxStatus_PendingOutbound, message)
+func (m CrossChainTx) SetPendingOutbound(statusMsg string) {
+ m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_PendingOutbound, statusMsg, "")
}
// SetOutboundMined sets the CCTX status to OutboundMined with the given error message.
-func (m CrossChainTx) SetOutboundMined(message string) {
- m.CctxStatus.ChangeStatus(CctxStatus_OutboundMined, message)
+func (m CrossChainTx) SetOutboundMined(statusMsg string) {
+ m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_OutboundMined, statusMsg, "")
}
// SetReverted sets the CCTX status to Reverted with the given error message.
-func (m CrossChainTx) SetReverted(message string) {
- m.CctxStatus.ChangeStatus(CctxStatus_Reverted, message)
+func (m CrossChainTx) SetReverted(statusMsg, errorMsg string) {
+ m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_Reverted, statusMsg, errorMsg)
}
func (m CrossChainTx) GetCCTXIndexBytes() ([32]byte, error) {
@@ -259,6 +259,7 @@ func NewCCTX(ctx sdk.Context, msg MsgVoteInbound, tssPubkey string) (CrossChainT
status := &Status{
Status: CctxStatus_PendingInbound,
StatusMessage: "",
+ ErrorMessage: "",
CreatedTimestamp: ctx.BlockHeader().Time.Unix(),
LastUpdateTimestamp: ctx.BlockHeader().Time.Unix(),
IsAbortRefunded: false,
diff --git a/x/crosschain/types/cctx_test.go b/x/crosschain/types/cctx_test.go
index 00fbe2ac66..f49768f8a0 100644
--- a/x/crosschain/types/cctx_test.go
+++ b/x/crosschain/types/cctx_test.go
@@ -150,17 +150,20 @@ func Test_SetRevertOutboundValues(t *testing.T) {
func TestCrossChainTx_SetAbort(t *testing.T) {
cctx := sample.CrossChainTx(t, "test")
- cctx.SetAbort("test")
+ cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound
+ cctx.SetAbort("test", "test")
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
- require.Equal(t, "test", "test")
+ require.Contains(t, cctx.CctxStatus.StatusMessage, "test")
+ require.Contains(t, cctx.CctxStatus.ErrorMessage, "test")
}
func TestCrossChainTx_SetPendingRevert(t *testing.T) {
cctx := sample.CrossChainTx(t, "test")
cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound
- cctx.SetPendingRevert("test")
+ cctx.SetPendingRevert("test", "test")
require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status)
require.Contains(t, cctx.CctxStatus.StatusMessage, "test")
+ require.Contains(t, cctx.CctxStatus.ErrorMessage, "test")
}
func TestCrossChainTx_SetPendingOutbound(t *testing.T) {
@@ -169,6 +172,7 @@ func TestCrossChainTx_SetPendingOutbound(t *testing.T) {
cctx.SetPendingOutbound("test")
require.Equal(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status)
require.Contains(t, cctx.CctxStatus.StatusMessage, "test")
+ require.NotContains(t, cctx.CctxStatus.ErrorMessage, "test")
}
func TestCrossChainTx_SetOutboundMined(t *testing.T) {
@@ -177,12 +181,14 @@ func TestCrossChainTx_SetOutboundMined(t *testing.T) {
cctx.SetOutboundMined("test")
require.Equal(t, types.CctxStatus_OutboundMined, cctx.CctxStatus.Status)
require.Contains(t, cctx.CctxStatus.StatusMessage, "test")
+ require.NotContains(t, cctx.CctxStatus.ErrorMessage, "test")
}
func TestCrossChainTx_SetReverted(t *testing.T) {
cctx := sample.CrossChainTx(t, "test")
cctx.CctxStatus.Status = types.CctxStatus_PendingRevert
- cctx.SetReverted("test")
+ cctx.SetReverted("test", "test")
require.Equal(t, types.CctxStatus_Reverted, cctx.CctxStatus.Status)
require.Contains(t, cctx.CctxStatus.StatusMessage, "test")
+ require.Contains(t, cctx.CctxStatus.ErrorMessage, "test")
}
diff --git a/x/crosschain/types/cross_chain_tx.pb.go b/x/crosschain/types/cross_chain_tx.pb.go
index 842ba310ba..23923a9cbf 100644
--- a/x/crosschain/types/cross_chain_tx.pb.go
+++ b/x/crosschain/types/cross_chain_tx.pb.go
@@ -488,10 +488,15 @@ func (m *OutboundParams) GetCallOptions() *CallOptions {
}
type Status struct {
- Status CctxStatus `protobuf:"varint,1,opt,name=status,proto3,enum=zetachain.zetacore.crosschain.CctxStatus" json:"status,omitempty"`
- StatusMessage string `protobuf:"bytes,2,opt,name=status_message,json=statusMessage,proto3" json:"status_message,omitempty"`
- LastUpdateTimestamp int64 `protobuf:"varint,3,opt,name=lastUpdate_timestamp,json=lastUpdateTimestamp,proto3" json:"lastUpdate_timestamp,omitempty"`
- IsAbortRefunded bool `protobuf:"varint,4,opt,name=isAbortRefunded,proto3" json:"isAbortRefunded,omitempty"`
+ Status CctxStatus `protobuf:"varint,1,opt,name=status,proto3,enum=zetachain.zetacore.crosschain.CctxStatus" json:"status,omitempty"`
+ // status_message carries information about the status transitions:
+ // why they were triggered, old and new status.
+ StatusMessage string `protobuf:"bytes,2,opt,name=status_message,json=statusMessage,proto3" json:"status_message,omitempty"`
+ // error_message carries information about the error that caused the tx
+ // to be PendingRevert, Reverted or Aborted.
+ ErrorMessage string `protobuf:"bytes,6,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
+ LastUpdateTimestamp int64 `protobuf:"varint,3,opt,name=lastUpdate_timestamp,json=lastUpdateTimestamp,proto3" json:"lastUpdate_timestamp,omitempty"`
+ IsAbortRefunded bool `protobuf:"varint,4,opt,name=isAbortRefunded,proto3" json:"isAbortRefunded,omitempty"`
// when the CCTX was created. only populated on new transactions.
CreatedTimestamp int64 `protobuf:"varint,5,opt,name=created_timestamp,json=createdTimestamp,proto3" json:"created_timestamp,omitempty"`
}
@@ -543,6 +548,13 @@ func (m *Status) GetStatusMessage() string {
return ""
}
+func (m *Status) GetErrorMessage() string {
+ if m != nil {
+ return m.ErrorMessage
+ }
+ return ""
+}
+
func (m *Status) GetLastUpdateTimestamp() int64 {
if m != nil {
return m.LastUpdateTimestamp
@@ -753,92 +765,93 @@ func init() {
}
var fileDescriptor_d4c1966807fb5cb2 = []byte{
- // 1348 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x4d, 0x6f, 0x1b, 0x37,
- 0x13, 0xf6, 0xda, 0xb2, 0x2c, 0x8d, 0x3e, 0xbc, 0xa6, 0x15, 0x67, 0xe3, 0x17, 0x51, 0xfc, 0xaa,
- 0x75, 0xa2, 0xb8, 0xb5, 0x84, 0x28, 0x40, 0x51, 0xf4, 0x66, 0x1b, 0x71, 0xe2, 0xb6, 0x89, 0x8d,
- 0x8d, 0x63, 0x20, 0x39, 0x74, 0x4b, 0xed, 0xd2, 0x12, 0xe1, 0xd5, 0x52, 0x5d, 0x52, 0x86, 0x14,
- 0xf4, 0xd6, 0x73, 0x81, 0xfe, 0x88, 0x1e, 0x7a, 0xec, 0xcf, 0xc8, 0x31, 0xc7, 0xa2, 0x87, 0x20,
- 0x48, 0xfe, 0x41, 0xcf, 0x3d, 0x14, 0xfc, 0x92, 0xac, 0xc0, 0xb5, 0xd3, 0xb4, 0x27, 0x71, 0x66,
- 0x38, 0xcf, 0xcc, 0x0e, 0xe7, 0x19, 0x52, 0xd0, 0x7a, 0x4e, 0x04, 0x0e, 0xbb, 0x98, 0x26, 0x4d,
- 0xb5, 0x62, 0x29, 0x69, 0x86, 0x29, 0xe3, 0x5c, 0xeb, 0xd4, 0x32, 0x50, 0xeb, 0x40, 0x0c, 0x1b,
- 0xfd, 0x94, 0x09, 0x86, 0xae, 0x8f, 0x7d, 0x1a, 0xd6, 0xa7, 0x31, 0xf1, 0x59, 0xad, 0x74, 0x58,
- 0x87, 0xa9, 0x9d, 0x4d, 0xb9, 0xd2, 0x4e, 0xab, 0x37, 0xcf, 0x09, 0xd4, 0x3f, 0xe9, 0x34, 0x43,
- 0x26, 0xc3, 0x30, 0x9a, 0xe8, 0x7d, 0xb5, 0x5f, 0x33, 0x50, 0xda, 0x4b, 0xda, 0x6c, 0x90, 0x44,
- 0x07, 0x38, 0xc5, 0x3d, 0x8e, 0x56, 0x20, 0xcb, 0x49, 0x12, 0x91, 0xd4, 0x73, 0xd6, 0x9c, 0x7a,
- 0xde, 0x37, 0x12, 0xba, 0x09, 0x8b, 0x7a, 0x65, 0xf2, 0xa3, 0x91, 0x37, 0xbb, 0xe6, 0xd4, 0xe7,
- 0xfc, 0x92, 0x56, 0xef, 0x48, 0xed, 0x5e, 0x84, 0xfe, 0x07, 0x79, 0x31, 0x0c, 0x58, 0x4a, 0x3b,
- 0x34, 0xf1, 0xe6, 0x14, 0x44, 0x4e, 0x0c, 0xf7, 0x95, 0x8c, 0xb6, 0x21, 0x2f, 0x83, 0x07, 0x62,
- 0xd4, 0x27, 0x5e, 0x66, 0xcd, 0xa9, 0x97, 0x5b, 0xeb, 0x8d, 0x73, 0xbe, 0xaf, 0x7f, 0xd2, 0x69,
- 0xa8, 0x2c, 0x77, 0x18, 0x4d, 0x0e, 0x47, 0x7d, 0xe2, 0xe7, 0x42, 0xb3, 0x42, 0x15, 0x98, 0xc7,
- 0x9c, 0x13, 0xe1, 0xcd, 0x2b, 0x70, 0x2d, 0xa0, 0xfb, 0x90, 0xc5, 0x3d, 0x36, 0x48, 0x84, 0x97,
- 0x95, 0xea, 0xed, 0xe6, 0x8b, 0x57, 0x37, 0x66, 0x7e, 0x7f, 0x75, 0xe3, 0x56, 0x87, 0x8a, 0xee,
- 0xa0, 0xdd, 0x08, 0x59, 0xaf, 0x19, 0x32, 0xde, 0x63, 0xdc, 0xfc, 0x6c, 0xf2, 0xe8, 0xa4, 0x29,
- 0xf3, 0xe0, 0x8d, 0x27, 0x34, 0x11, 0xbe, 0x71, 0x47, 0x1f, 0x41, 0x89, 0xb5, 0x39, 0x49, 0x4f,
- 0x49, 0x14, 0x74, 0x31, 0xef, 0x7a, 0x0b, 0x2a, 0x4c, 0xd1, 0x2a, 0x1f, 0x60, 0xde, 0x45, 0x9f,
- 0x83, 0x37, 0xde, 0x44, 0x86, 0x82, 0xa4, 0x09, 0x8e, 0x83, 0x2e, 0xa1, 0x9d, 0xae, 0xf0, 0x72,
- 0x6b, 0x4e, 0x3d, 0xe3, 0xaf, 0x58, 0xfb, 0x3d, 0x63, 0x7e, 0xa0, 0xac, 0xe8, 0xff, 0x50, 0x6c,
- 0xe3, 0x38, 0x66, 0x22, 0xa0, 0x49, 0x44, 0x86, 0x5e, 0x5e, 0xa1, 0x17, 0xb4, 0x6e, 0x4f, 0xaa,
- 0x50, 0x0b, 0xae, 0x1c, 0xd3, 0x04, 0xc7, 0xf4, 0x39, 0x89, 0x02, 0x59, 0x12, 0x8b, 0x0c, 0x0a,
- 0x79, 0x79, 0x6c, 0x7c, 0x46, 0x04, 0x36, 0xb0, 0x14, 0x56, 0xc4, 0x30, 0x30, 0x16, 0x2c, 0x28,
- 0x4b, 0x02, 0x2e, 0xb0, 0x18, 0x70, 0xaf, 0xa0, 0xaa, 0x7c, 0xb7, 0x71, 0x61, 0x17, 0x35, 0x0e,
- 0x87, 0xbb, 0x67, 0x7c, 0x1f, 0x2b, 0x57, 0xbf, 0x22, 0xce, 0xd1, 0xd6, 0xbe, 0x83, 0xb2, 0x0c,
- 0xbc, 0x15, 0x86, 0xb2, 0x5e, 0x34, 0xe9, 0xa0, 0x00, 0x96, 0x71, 0x9b, 0xa5, 0xc2, 0xa6, 0x6b,
- 0x0e, 0xc2, 0xf9, 0xb0, 0x83, 0x58, 0x32, 0x58, 0x2a, 0x88, 0x42, 0xaa, 0x1d, 0x41, 0x61, 0x07,
- 0xc7, 0xf1, 0x7e, 0x5f, 0xa6, 0xc1, 0x65, 0x8b, 0x75, 0x30, 0x0f, 0x62, 0xda, 0xa3, 0x3a, 0x4a,
- 0xc6, 0xcf, 0x75, 0x30, 0xff, 0x5a, 0xca, 0x68, 0x03, 0x96, 0x28, 0x0f, 0x70, 0xda, 0xa6, 0x22,
- 0xc5, 0xe9, 0x28, 0x08, 0x71, 0x1c, 0xab, 0x4e, 0xcd, 0xf9, 0x8b, 0x94, 0x6f, 0x59, 0xbd, 0xc4,
- 0xab, 0xbd, 0xce, 0x42, 0x79, 0x7f, 0x20, 0xce, 0xb6, 0xff, 0x2a, 0xe4, 0x52, 0x12, 0x12, 0x7a,
- 0x3a, 0x26, 0xc0, 0x58, 0x46, 0xb7, 0xc1, 0xb5, 0x6b, 0x4d, 0x82, 0x3d, 0xcb, 0x81, 0x45, 0xab,
- 0xb7, 0x2c, 0x98, 0x6a, 0xf4, 0xb9, 0x0f, 0x6b, 0xf4, 0x49, 0x4b, 0x67, 0xfe, 0x5d, 0x4b, 0x4b,
- 0x4a, 0x72, 0x1e, 0x24, 0x2c, 0x09, 0x89, 0x62, 0x4d, 0xc6, 0xcf, 0x09, 0xce, 0x1f, 0x49, 0x79,
- 0xba, 0x98, 0xd9, 0x77, 0x8a, 0x69, 0x8c, 0xfd, 0x94, 0x86, 0xc4, 0x10, 0x41, 0x1a, 0x0f, 0xa4,
- 0x8c, 0xea, 0xe0, 0x1a, 0x23, 0x4b, 0xa9, 0x18, 0x05, 0xc7, 0x84, 0x78, 0x57, 0xd5, 0x9e, 0xb2,
- 0xde, 0xa3, 0xd4, 0xbb, 0x84, 0x20, 0x04, 0x19, 0x45, 0xa5, 0x9c, 0xb2, 0xaa, 0xf5, 0xfb, 0x10,
- 0xe1, 0x22, 0x96, 0xc1, 0x85, 0x2c, 0xbb, 0x06, 0x32, 0xcd, 0x60, 0xc0, 0x49, 0xe4, 0x55, 0xd4,
- 0xce, 0x85, 0x0e, 0xe6, 0x4f, 0x38, 0x89, 0xd0, 0x37, 0xb0, 0x4c, 0x8e, 0x8f, 0x49, 0x28, 0xe8,
- 0x29, 0x09, 0x26, 0x1f, 0x77, 0x45, 0x95, 0xb8, 0x61, 0x4a, 0x7c, 0xf3, 0x3d, 0x4a, 0xbc, 0x27,
- 0x7b, 0x75, 0x0c, 0x75, 0xdf, 0x56, 0xa5, 0xf1, 0x2e, 0xbe, 0xae, 0xec, 0x8a, 0xca, 0x62, 0x6a,
- 0xbf, 0x2e, 0xf1, 0x75, 0x00, 0x79, 0x38, 0xfd, 0x41, 0xfb, 0x84, 0x8c, 0x14, 0x5b, 0xf3, 0xbe,
- 0x3c, 0xae, 0x03, 0xa5, 0xb8, 0x80, 0xd8, 0xc5, 0xff, 0x98, 0xd8, 0xe8, 0x21, 0x14, 0x25, 0x59,
- 0x02, 0xa6, 0x69, 0xe6, 0x79, 0x6b, 0x4e, 0xbd, 0xd0, 0xda, 0xb8, 0x24, 0xc0, 0x19, 0x62, 0xfa,
- 0x85, 0x70, 0x22, 0x7c, 0x99, 0xc9, 0x95, 0xdc, 0x4a, 0xed, 0x4f, 0x07, 0xb2, 0x06, 0x7f, 0x0b,
- 0xb2, 0x26, 0x75, 0x47, 0xa5, 0x7e, 0xfb, 0x32, 0xe4, 0x50, 0x0c, 0x4d, 0xc2, 0xc6, 0x11, 0xad,
- 0x43, 0x59, 0xaf, 0x82, 0x1e, 0xe1, 0x1c, 0x77, 0x88, 0xe2, 0x5f, 0xde, 0x2f, 0x69, 0xed, 0x43,
- 0xad, 0x44, 0x77, 0xa0, 0x12, 0x63, 0x2e, 0x9e, 0xf4, 0x23, 0x2c, 0x48, 0x20, 0x68, 0x8f, 0x70,
- 0x81, 0x7b, 0x7d, 0x45, 0xc4, 0x39, 0x7f, 0x79, 0x62, 0x3b, 0xb4, 0x26, 0x54, 0x07, 0x39, 0x1d,
- 0xe4, 0xe4, 0xf1, 0xc9, 0xf1, 0x20, 0x89, 0x48, 0xa4, 0x58, 0xa7, 0x87, 0xc6, 0x59, 0x35, 0xfa,
- 0x04, 0x96, 0xc2, 0x94, 0x60, 0x39, 0xed, 0x26, 0xc8, 0xf3, 0x0a, 0xd9, 0x35, 0x86, 0x31, 0x6c,
- 0xed, 0x87, 0x59, 0x28, 0xf9, 0xe4, 0x94, 0xa4, 0xc2, 0x0e, 0xaf, 0x75, 0x28, 0xa7, 0x4a, 0x11,
- 0xe0, 0x28, 0x4a, 0x09, 0xe7, 0x66, 0xcc, 0x94, 0xb4, 0x76, 0x4b, 0x2b, 0xd1, 0xc7, 0x50, 0xd6,
- 0x87, 0x91, 0x04, 0xda, 0x60, 0x66, 0x98, 0x3a, 0xa2, 0xfd, 0x44, 0x63, 0xca, 0xcb, 0x4a, 0x4d,
- 0xcb, 0x31, 0x96, 0xbe, 0x70, 0x8b, 0x4a, 0x69, 0xa1, 0x26, 0x11, 0x6d, 0xd1, 0xe4, 0x97, 0x15,
- 0x6d, 0x44, 0x5b, 0xb4, 0xa7, 0x72, 0xba, 0xa9, 0x6d, 0x93, 0xae, 0x9d, 0xff, 0xb0, 0xc1, 0x63,
- 0xe2, 0xd9, 0x1e, 0xaf, 0xfd, 0x38, 0x0f, 0xc5, 0x1d, 0x79, 0xb0, 0x6a, 0x3c, 0x1e, 0x0e, 0x91,
- 0x07, 0x0b, 0xaa, 0x54, 0xcc, 0x0e, 0x59, 0x2b, 0xca, 0xdb, 0x5d, 0xcf, 0x03, 0x7d, 0xb0, 0x5a,
- 0x40, 0xdf, 0x42, 0x5e, 0xdd, 0x2c, 0xc7, 0x84, 0x70, 0x93, 0xd4, 0xce, 0x3f, 0x4c, 0xea, 0x8f,
- 0x57, 0x37, 0xdc, 0x11, 0xee, 0xc5, 0x5f, 0xd4, 0xc6, 0x48, 0x35, 0x3f, 0x27, 0xd7, 0xbb, 0x84,
- 0x70, 0x74, 0x0b, 0x16, 0x53, 0x12, 0xe3, 0x11, 0x89, 0xc6, 0x55, 0xca, 0xea, 0x59, 0x66, 0xd4,
- 0xb6, 0x4c, 0xbb, 0x50, 0x08, 0x43, 0x31, 0xb4, 0x2c, 0xcc, 0x29, 0x92, 0xac, 0x5f, 0xd2, 0xca,
- 0xa6, 0x8d, 0x21, 0x1c, 0xb7, 0x34, 0x7a, 0x0c, 0x65, 0xaa, 0x1f, 0x5e, 0x41, 0x5f, 0x5d, 0x3d,
- 0x6a, 0x02, 0x16, 0x5a, 0x9f, 0x5e, 0x02, 0x35, 0xf5, 0x5a, 0xf3, 0x4b, 0x74, 0xea, 0xf1, 0x76,
- 0x04, 0x8b, 0xcc, 0xdc, 0x67, 0x16, 0x15, 0xd6, 0xe6, 0xea, 0x85, 0xd6, 0xe6, 0x25, 0xa8, 0xd3,
- 0xb7, 0xa0, 0x5f, 0x66, 0xd3, 0xb7, 0x62, 0x0a, 0xd7, 0xd4, 0x7b, 0x31, 0x64, 0x71, 0x10, 0xb2,
- 0x44, 0xa4, 0x38, 0x14, 0xc1, 0x29, 0x49, 0x39, 0x65, 0x89, 0x79, 0x61, 0x7c, 0x76, 0x49, 0x84,
- 0x03, 0xe3, 0xbf, 0x63, 0xdc, 0x8f, 0xb4, 0xb7, 0x7f, 0xb5, 0x7f, 0xbe, 0x01, 0x3d, 0x1d, 0xb7,
- 0xad, 0x1d, 0x48, 0xc5, 0xf7, 0x2a, 0xd0, 0x14, 0xdd, 0xb6, 0x33, 0xb2, 0x4d, 0x6c, 0xab, 0x1b,
- 0xe5, 0xc6, 0xf7, 0x00, 0x93, 0xe1, 0x82, 0x10, 0x94, 0x0f, 0x48, 0x12, 0xd1, 0xa4, 0x63, 0x6a,
- 0xeb, 0xce, 0xa0, 0x65, 0x58, 0x34, 0x3a, 0x5b, 0x19, 0xd7, 0x41, 0x4b, 0x50, 0xb2, 0xd2, 0x43,
- 0x9a, 0x90, 0xc8, 0x9d, 0x93, 0x2a, 0xb3, 0x4f, 0x87, 0x75, 0x33, 0xa8, 0x08, 0x39, 0xbd, 0x26,
- 0x91, 0x3b, 0x8f, 0x0a, 0xb0, 0xb0, 0xa5, 0xdf, 0x33, 0x6e, 0x76, 0x35, 0xf3, 0xcb, 0xcf, 0x55,
- 0x67, 0xe3, 0x2b, 0xa8, 0x9c, 0x37, 0x95, 0x91, 0x0b, 0xc5, 0x47, 0x4c, 0xec, 0xda, 0xd7, 0x9d,
- 0x3b, 0x83, 0x4a, 0x90, 0x9f, 0x88, 0x8e, 0x44, 0xbe, 0x37, 0x24, 0xe1, 0x40, 0x82, 0xcd, 0x1a,
- 0xb0, 0x26, 0x5c, 0xfd, 0x9b, 0xca, 0xa2, 0x2c, 0xcc, 0x1e, 0xdd, 0x71, 0x67, 0xd4, 0x6f, 0xcb,
- 0x75, 0xb4, 0xc3, 0xf6, 0xfd, 0x17, 0x6f, 0xaa, 0xce, 0xcb, 0x37, 0x55, 0xe7, 0xf5, 0x9b, 0xaa,
- 0xf3, 0xd3, 0xdb, 0xea, 0xcc, 0xcb, 0xb7, 0xd5, 0x99, 0xdf, 0xde, 0x56, 0x67, 0x9e, 0x6d, 0x9e,
- 0x61, 0x92, 0x2c, 0xec, 0xa6, 0xfe, 0xff, 0x90, 0xb0, 0x88, 0x34, 0x87, 0x67, 0xff, 0xa6, 0x28,
- 0x52, 0xb5, 0xb3, 0xea, 0xe0, 0xee, 0xfe, 0x15, 0x00, 0x00, 0xff, 0xff, 0x94, 0x4b, 0x30, 0xb4,
- 0xd4, 0x0c, 0x00, 0x00,
+ // 1361 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x6e, 0x1b, 0xb7,
+ 0x13, 0xf6, 0xda, 0xb2, 0x2c, 0x8d, 0xfe, 0x78, 0x4d, 0x2b, 0xce, 0xc6, 0x3f, 0x44, 0xf1, 0x4f,
+ 0xad, 0x13, 0xc5, 0xad, 0x25, 0x44, 0x01, 0x8a, 0xa2, 0x37, 0xdb, 0x88, 0x13, 0xb7, 0x4d, 0x6c,
+ 0x6c, 0x1c, 0x03, 0xc9, 0xa1, 0x5b, 0x6a, 0x97, 0x96, 0x08, 0xaf, 0x96, 0xea, 0x92, 0x32, 0xa4,
+ 0xa0, 0xb7, 0x9e, 0x0b, 0xf4, 0x15, 0x0a, 0xf4, 0xd0, 0x63, 0x1f, 0x23, 0xc7, 0x1c, 0x8b, 0x1e,
+ 0x82, 0x20, 0x79, 0x83, 0x3e, 0x41, 0xc1, 0x7f, 0x92, 0x15, 0xb8, 0x76, 0x9a, 0xf6, 0x24, 0xf2,
+ 0x1b, 0xf2, 0x9b, 0xd9, 0xe1, 0x7c, 0x43, 0x0a, 0x5a, 0xcf, 0x89, 0xc0, 0x61, 0x17, 0xd3, 0xa4,
+ 0xa9, 0x46, 0x2c, 0x25, 0xcd, 0x30, 0x65, 0x9c, 0x6b, 0x4c, 0x0d, 0x03, 0x35, 0x0e, 0xc4, 0xb0,
+ 0xd1, 0x4f, 0x99, 0x60, 0xe8, 0xfa, 0x78, 0x4f, 0xc3, 0xee, 0x69, 0x4c, 0xf6, 0xac, 0x56, 0x3a,
+ 0xac, 0xc3, 0xd4, 0xca, 0xa6, 0x1c, 0xe9, 0x4d, 0xab, 0x37, 0xcf, 0x71, 0xd4, 0x3f, 0xe9, 0x34,
+ 0x43, 0x26, 0xdd, 0x30, 0x9a, 0xe8, 0x75, 0xb5, 0xdf, 0x32, 0x50, 0xda, 0x4b, 0xda, 0x6c, 0x90,
+ 0x44, 0x07, 0x38, 0xc5, 0x3d, 0x8e, 0x56, 0x20, 0xcb, 0x49, 0x12, 0x91, 0xd4, 0x73, 0xd6, 0x9c,
+ 0x7a, 0xde, 0x37, 0x33, 0x74, 0x13, 0x16, 0xf5, 0xc8, 0xc4, 0x47, 0x23, 0x6f, 0x76, 0xcd, 0xa9,
+ 0xcf, 0xf9, 0x25, 0x0d, 0xef, 0x48, 0x74, 0x2f, 0x42, 0xff, 0x83, 0xbc, 0x18, 0x06, 0x2c, 0xa5,
+ 0x1d, 0x9a, 0x78, 0x73, 0x8a, 0x22, 0x27, 0x86, 0xfb, 0x6a, 0x8e, 0xb6, 0x21, 0x2f, 0x9d, 0x07,
+ 0x62, 0xd4, 0x27, 0x5e, 0x66, 0xcd, 0xa9, 0x97, 0x5b, 0xeb, 0x8d, 0x73, 0xbe, 0xaf, 0x7f, 0xd2,
+ 0x69, 0xa8, 0x28, 0x77, 0x18, 0x4d, 0x0e, 0x47, 0x7d, 0xe2, 0xe7, 0x42, 0x33, 0x42, 0x15, 0x98,
+ 0xc7, 0x9c, 0x13, 0xe1, 0xcd, 0x2b, 0x72, 0x3d, 0x41, 0xf7, 0x21, 0x8b, 0x7b, 0x6c, 0x90, 0x08,
+ 0x2f, 0x2b, 0xe1, 0xed, 0xe6, 0x8b, 0x57, 0x37, 0x66, 0xfe, 0x78, 0x75, 0xe3, 0x56, 0x87, 0x8a,
+ 0xee, 0xa0, 0xdd, 0x08, 0x59, 0xaf, 0x19, 0x32, 0xde, 0x63, 0xdc, 0xfc, 0x6c, 0xf2, 0xe8, 0xa4,
+ 0x29, 0xe3, 0xe0, 0x8d, 0x27, 0x34, 0x11, 0xbe, 0xd9, 0x8e, 0x3e, 0x82, 0x12, 0x6b, 0x73, 0x92,
+ 0x9e, 0x92, 0x28, 0xe8, 0x62, 0xde, 0xf5, 0x16, 0x94, 0x9b, 0xa2, 0x05, 0x1f, 0x60, 0xde, 0x45,
+ 0x9f, 0x83, 0x37, 0x5e, 0x44, 0x86, 0x82, 0xa4, 0x09, 0x8e, 0x83, 0x2e, 0xa1, 0x9d, 0xae, 0xf0,
+ 0x72, 0x6b, 0x4e, 0x3d, 0xe3, 0xaf, 0x58, 0xfb, 0x3d, 0x63, 0x7e, 0xa0, 0xac, 0xe8, 0xff, 0x50,
+ 0x6c, 0xe3, 0x38, 0x66, 0x22, 0xa0, 0x49, 0x44, 0x86, 0x5e, 0x5e, 0xb1, 0x17, 0x34, 0xb6, 0x27,
+ 0x21, 0xd4, 0x82, 0x2b, 0xc7, 0x34, 0xc1, 0x31, 0x7d, 0x4e, 0xa2, 0x40, 0xa6, 0xc4, 0x32, 0x83,
+ 0x62, 0x5e, 0x1e, 0x1b, 0x9f, 0x11, 0x81, 0x0d, 0x2d, 0x85, 0x15, 0x31, 0x0c, 0x8c, 0x05, 0x0b,
+ 0xca, 0x92, 0x80, 0x0b, 0x2c, 0x06, 0xdc, 0x2b, 0xa8, 0x2c, 0xdf, 0x6d, 0x5c, 0x58, 0x45, 0x8d,
+ 0xc3, 0xe1, 0xee, 0x99, 0xbd, 0x8f, 0xd5, 0x56, 0xbf, 0x22, 0xce, 0x41, 0x6b, 0xdf, 0x41, 0x59,
+ 0x3a, 0xde, 0x0a, 0x43, 0x99, 0x2f, 0x9a, 0x74, 0x50, 0x00, 0xcb, 0xb8, 0xcd, 0x52, 0x61, 0xc3,
+ 0x35, 0x07, 0xe1, 0x7c, 0xd8, 0x41, 0x2c, 0x19, 0x2e, 0xe5, 0x44, 0x31, 0xd5, 0x8e, 0xa0, 0xb0,
+ 0x83, 0xe3, 0x78, 0xbf, 0x2f, 0xc3, 0xe0, 0xb2, 0xc4, 0x3a, 0x98, 0x07, 0x31, 0xed, 0x51, 0xed,
+ 0x25, 0xe3, 0xe7, 0x3a, 0x98, 0x7f, 0x2d, 0xe7, 0x68, 0x03, 0x96, 0x28, 0x0f, 0x70, 0xda, 0xa6,
+ 0x22, 0xc5, 0xe9, 0x28, 0x08, 0x71, 0x1c, 0xab, 0x4a, 0xcd, 0xf9, 0x8b, 0x94, 0x6f, 0x59, 0x5c,
+ 0xf2, 0xd5, 0x5e, 0x67, 0xa1, 0xbc, 0x3f, 0x10, 0x67, 0xcb, 0x7f, 0x15, 0x72, 0x29, 0x09, 0x09,
+ 0x3d, 0x1d, 0x0b, 0x60, 0x3c, 0x47, 0xb7, 0xc1, 0xb5, 0x63, 0x2d, 0x82, 0x3d, 0xab, 0x81, 0x45,
+ 0x8b, 0x5b, 0x15, 0x4c, 0x15, 0xfa, 0xdc, 0x87, 0x15, 0xfa, 0xa4, 0xa4, 0x33, 0xff, 0xae, 0xa4,
+ 0xa5, 0x24, 0x39, 0x0f, 0x12, 0x96, 0x84, 0x44, 0xa9, 0x26, 0xe3, 0xe7, 0x04, 0xe7, 0x8f, 0xe4,
+ 0x7c, 0x3a, 0x99, 0xd9, 0x77, 0x92, 0x69, 0x8c, 0xfd, 0x94, 0x86, 0xc4, 0x08, 0x41, 0x1a, 0x0f,
+ 0xe4, 0x1c, 0xd5, 0xc1, 0x35, 0x46, 0x96, 0x52, 0x31, 0x0a, 0x8e, 0x09, 0xf1, 0xae, 0xaa, 0x35,
+ 0x65, 0xbd, 0x46, 0xc1, 0xbb, 0x84, 0x20, 0x04, 0x19, 0x25, 0xa5, 0x9c, 0xb2, 0xaa, 0xf1, 0xfb,
+ 0x08, 0xe1, 0x22, 0x95, 0xc1, 0x85, 0x2a, 0xbb, 0x06, 0x32, 0xcc, 0x60, 0xc0, 0x49, 0xe4, 0x55,
+ 0xd4, 0xca, 0x85, 0x0e, 0xe6, 0x4f, 0x38, 0x89, 0xd0, 0x37, 0xb0, 0x4c, 0x8e, 0x8f, 0x49, 0x28,
+ 0xe8, 0x29, 0x09, 0x26, 0x1f, 0x77, 0x45, 0xa5, 0xb8, 0x61, 0x52, 0x7c, 0xf3, 0x3d, 0x52, 0xbc,
+ 0x27, 0x6b, 0x75, 0x4c, 0x75, 0xdf, 0x66, 0xa5, 0xf1, 0x2e, 0xbf, 0xce, 0xec, 0x8a, 0x8a, 0x62,
+ 0x6a, 0xbd, 0x4e, 0xf1, 0x75, 0x00, 0x79, 0x38, 0xfd, 0x41, 0xfb, 0x84, 0x8c, 0x94, 0x5a, 0xf3,
+ 0xbe, 0x3c, 0xae, 0x03, 0x05, 0x5c, 0x20, 0xec, 0xe2, 0x7f, 0x2c, 0x6c, 0xf4, 0x10, 0x8a, 0x52,
+ 0x2c, 0x01, 0xd3, 0x32, 0xf3, 0xbc, 0x35, 0xa7, 0x5e, 0x68, 0x6d, 0x5c, 0xe2, 0xe0, 0x8c, 0x30,
+ 0xfd, 0x42, 0x38, 0x99, 0x7c, 0x99, 0xc9, 0x95, 0xdc, 0x4a, 0xed, 0xe7, 0x59, 0xc8, 0x1a, 0xfe,
+ 0x2d, 0xc8, 0x9a, 0xd0, 0x1d, 0x15, 0xfa, 0xed, 0xcb, 0x98, 0x43, 0x31, 0x34, 0x01, 0x9b, 0x8d,
+ 0x68, 0x1d, 0xca, 0x7a, 0x14, 0xf4, 0x08, 0xe7, 0xb8, 0x43, 0x94, 0xfe, 0xf2, 0x7e, 0x49, 0xa3,
+ 0x0f, 0x35, 0x28, 0x7b, 0x38, 0x49, 0x53, 0x96, 0x8e, 0x57, 0x65, 0x75, 0x0f, 0x57, 0xa0, 0x5d,
+ 0x74, 0x07, 0x2a, 0x31, 0xe6, 0xe2, 0x49, 0x3f, 0xc2, 0x82, 0x04, 0x82, 0xf6, 0x08, 0x17, 0xb8,
+ 0xd7, 0x57, 0x6a, 0x9d, 0xf3, 0x97, 0x27, 0xb6, 0x43, 0x6b, 0x42, 0x75, 0x90, 0x2d, 0x44, 0xb6,
+ 0x27, 0x9f, 0x1c, 0x0f, 0x92, 0x88, 0x44, 0x4a, 0x9a, 0xba, 0xb3, 0x9c, 0x85, 0xd1, 0x27, 0xb0,
+ 0x14, 0xa6, 0x04, 0xcb, 0x96, 0x38, 0x61, 0x9e, 0x57, 0xcc, 0xae, 0x31, 0x8c, 0x69, 0x6b, 0x3f,
+ 0xcc, 0x42, 0xc9, 0x27, 0xa7, 0x24, 0x15, 0xb6, 0xc3, 0xad, 0x43, 0x39, 0x55, 0x40, 0x80, 0xa3,
+ 0x28, 0x25, 0x9c, 0x9b, 0x5e, 0x54, 0xd2, 0xe8, 0x96, 0x06, 0xd1, 0xc7, 0x50, 0xd6, 0x27, 0x96,
+ 0x04, 0xda, 0x60, 0x1a, 0x9d, 0x3a, 0xc7, 0xfd, 0x44, 0x73, 0xca, 0x6c, 0xa8, 0x96, 0x3a, 0xe6,
+ 0xd2, 0xb7, 0x72, 0x51, 0x81, 0x96, 0x6a, 0xe2, 0xd1, 0xe6, 0x4c, 0x7e, 0x59, 0xd1, 0x7a, 0xb4,
+ 0x49, 0x7b, 0x2a, 0x5b, 0xa0, 0x5a, 0x36, 0x29, 0xed, 0xf9, 0x0f, 0xeb, 0x4e, 0xc6, 0x9f, 0x15,
+ 0x42, 0xed, 0xc7, 0x79, 0x28, 0xee, 0xc8, 0xd3, 0x57, 0x3d, 0xf4, 0x70, 0x88, 0x3c, 0x58, 0x50,
+ 0xa9, 0x62, 0xb6, 0x13, 0xdb, 0xa9, 0x7c, 0x02, 0xe8, 0xa6, 0xa1, 0x4f, 0x5f, 0x4f, 0xd0, 0xb7,
+ 0x90, 0x57, 0xd7, 0xcf, 0x31, 0x21, 0xdc, 0x04, 0xb5, 0xf3, 0x0f, 0x83, 0xfa, 0xf3, 0xd5, 0x0d,
+ 0x77, 0x84, 0x7b, 0xf1, 0x17, 0xb5, 0x31, 0x53, 0xcd, 0xcf, 0xc9, 0xf1, 0x2e, 0x21, 0x1c, 0xdd,
+ 0x82, 0xc5, 0x94, 0xc4, 0x78, 0x44, 0xa2, 0x77, 0x2a, 0xab, 0x6c, 0x60, 0x9b, 0xa6, 0x5d, 0x28,
+ 0x84, 0xa1, 0x18, 0x5a, 0xa9, 0xe6, 0x94, 0x92, 0xd6, 0x2f, 0xa9, 0x77, 0x53, 0xeb, 0x10, 0x8e,
+ 0xeb, 0x1e, 0x3d, 0x86, 0x32, 0xd5, 0xaf, 0xb3, 0xa0, 0xaf, 0xee, 0x27, 0xd5, 0x26, 0x0b, 0xad,
+ 0x4f, 0x2f, 0xa1, 0x9a, 0x7a, 0xd2, 0xf9, 0x25, 0x3a, 0xf5, 0xc2, 0x3b, 0x82, 0x45, 0x66, 0x2e,
+ 0x3d, 0xcb, 0x0a, 0x6b, 0x73, 0xf5, 0x42, 0x6b, 0xf3, 0x12, 0xd6, 0xe9, 0xab, 0xd2, 0x2f, 0xb3,
+ 0xe9, 0xab, 0x33, 0x85, 0x6b, 0xea, 0x51, 0x19, 0xb2, 0x38, 0x08, 0x59, 0x22, 0x52, 0x1c, 0x8a,
+ 0xe0, 0x94, 0xa4, 0x9c, 0xb2, 0xc4, 0x3c, 0x43, 0x3e, 0xbb, 0xc4, 0xc3, 0x81, 0xd9, 0xbf, 0x63,
+ 0xb6, 0x1f, 0xe9, 0xdd, 0xfe, 0xd5, 0xfe, 0xf9, 0x06, 0xf4, 0x74, 0x5c, 0xb6, 0xb6, 0x6b, 0x15,
+ 0xdf, 0x2b, 0x41, 0x53, 0x72, 0xdb, 0xce, 0xc8, 0x32, 0xb1, 0xa5, 0x6e, 0xc0, 0x8d, 0xef, 0x01,
+ 0x26, 0x1d, 0x08, 0x21, 0x28, 0x1f, 0x90, 0x24, 0xa2, 0x49, 0xc7, 0xe4, 0xd6, 0x9d, 0x41, 0xcb,
+ 0xb0, 0x68, 0x30, 0x9b, 0x19, 0xd7, 0x41, 0x4b, 0x50, 0xb2, 0xb3, 0x87, 0x34, 0x21, 0x91, 0x3b,
+ 0x27, 0x21, 0xb3, 0x4e, 0xbb, 0x75, 0x33, 0xa8, 0x08, 0x39, 0x3d, 0x26, 0x91, 0x3b, 0x8f, 0x0a,
+ 0xb0, 0xb0, 0xa5, 0x1f, 0x3d, 0x6e, 0x76, 0x35, 0xf3, 0xeb, 0x2f, 0x55, 0x67, 0xe3, 0x2b, 0xa8,
+ 0x9c, 0xd7, 0xba, 0x91, 0x0b, 0xc5, 0x47, 0x4c, 0xec, 0xda, 0x27, 0xa0, 0x3b, 0x83, 0x4a, 0x90,
+ 0x9f, 0x4c, 0x1d, 0xc9, 0x7c, 0x6f, 0x48, 0xc2, 0x81, 0x24, 0x9b, 0x35, 0x64, 0x4d, 0xb8, 0xfa,
+ 0x37, 0x99, 0x45, 0x59, 0x98, 0x3d, 0xba, 0xe3, 0xce, 0xa8, 0xdf, 0x96, 0xeb, 0xe8, 0x0d, 0xdb,
+ 0xf7, 0x5f, 0xbc, 0xa9, 0x3a, 0x2f, 0xdf, 0x54, 0x9d, 0xd7, 0x6f, 0xaa, 0xce, 0x4f, 0x6f, 0xab,
+ 0x33, 0x2f, 0xdf, 0x56, 0x67, 0x7e, 0x7f, 0x5b, 0x9d, 0x79, 0xb6, 0x79, 0x46, 0x49, 0x32, 0xb1,
+ 0x9b, 0xfa, 0x4f, 0x46, 0xc2, 0x22, 0xd2, 0x1c, 0x9e, 0xfd, 0x2f, 0xa3, 0x44, 0xd5, 0xce, 0xaa,
+ 0x83, 0xbb, 0xfb, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x84, 0x70, 0x56, 0x74, 0xf9, 0x0c, 0x00,
+ 0x00,
}
func (m *InboundParams) Marshal() (dAtA []byte, err error) {
@@ -1172,6 +1185,13 @@ func (m *Status) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
+ if len(m.ErrorMessage) > 0 {
+ i -= len(m.ErrorMessage)
+ copy(dAtA[i:], m.ErrorMessage)
+ i = encodeVarintCrossChainTx(dAtA, i, uint64(len(m.ErrorMessage)))
+ i--
+ dAtA[i] = 0x32
+ }
if m.CreatedTimestamp != 0 {
i = encodeVarintCrossChainTx(dAtA, i, uint64(m.CreatedTimestamp))
i--
@@ -1548,6 +1568,10 @@ func (m *Status) Size() (n int) {
if m.CreatedTimestamp != 0 {
n += 1 + sovCrossChainTx(uint64(m.CreatedTimestamp))
}
+ l = len(m.ErrorMessage)
+ if l > 0 {
+ n += 1 + l + sovCrossChainTx(uint64(l))
+ }
return n
}
@@ -2773,6 +2797,38 @@ func (m *Status) Unmarshal(dAtA []byte) error {
break
}
}
+ case 6:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field ErrorMessage", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowCrossChainTx
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthCrossChainTx
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex < 0 {
+ return ErrInvalidLengthCrossChainTx
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.ErrorMessage = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipCrossChainTx(dAtA[iNdEx:])
diff --git a/x/crosschain/types/keys.go b/x/crosschain/types/keys.go
index d9fea4a77d..76d343dd3e 100644
--- a/x/crosschain/types/keys.go
+++ b/x/crosschain/types/keys.go
@@ -3,7 +3,7 @@ package types
import (
"fmt"
- sdk "github.com/cosmos/cosmos-sdk/types"
+ "cosmossdk.io/math"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/ethereum/go-ethereum/common"
)
@@ -29,8 +29,8 @@ const (
CCTXIndexLength = 66
)
-func GetProtocolFee() sdk.Uint {
- return sdk.NewUint(ProtocolFee)
+func GetProtocolFee() math.Uint {
+ return math.NewUint(ProtocolFee)
}
func KeyPrefix(p string) []byte {
diff --git a/x/crosschain/types/rate_limiter_flags_test.go b/x/crosschain/types/rate_limiter_flags_test.go
index 8531a34105..545ef708a7 100644
--- a/x/crosschain/types/rate_limiter_flags_test.go
+++ b/x/crosschain/types/rate_limiter_flags_test.go
@@ -6,7 +6,6 @@ import (
"testing"
"cosmossdk.io/math"
- sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
@@ -263,7 +262,7 @@ func TestConvertCctxValue(t *testing.T) {
erc20AssetRates map[int64]map[string]types.AssetRate
// output
- expectedValue sdkmath.Int
+ expectedValue math.Int
}{
{
name: "should convert cctx ZETA value correctly",
diff --git a/x/crosschain/types/status.go b/x/crosschain/types/status.go
index cdaf5b0aae..00c08bf6f7 100644
--- a/x/crosschain/types/status.go
+++ b/x/crosschain/types/status.go
@@ -9,28 +9,45 @@ func (m *Status) AbortRefunded() {
m.StatusMessage = "CCTX aborted and Refunded"
}
-// ChangeStatus changes the status of the cross chain transaction
-// empty msg does not overwrite old status message
-func (m *Status) ChangeStatus(newStatus CctxStatus, msg string) {
- if len(msg) > 0 {
- if m.StatusMessage != "" {
- m.StatusMessage = fmt.Sprintf("%s : %s", m.StatusMessage, msg)
- } else {
- m.StatusMessage = msg
- }
+// UpdateStatusAndErrorMessages transitions the Status and Error messages.
+func (m *Status) UpdateStatusAndErrorMessages(newStatus CctxStatus, statusMsg, errorMsg string) {
+ m.UpdateStatus(newStatus, statusMsg)
+
+ if newStatus == CctxStatus_Aborted || newStatus == CctxStatus_Reverted || newStatus == CctxStatus_PendingRevert {
+ m.UpdateErrorMessage(errorMsg)
}
- if !m.ValidateTransition(newStatus) {
+}
+
+// UpdateStatus updates the cctx status and cctx.status.status_message.
+func (m *Status) UpdateStatus(newStatus CctxStatus, statusMsg string) {
+ if m.ValidateTransition(newStatus) {
+ m.StatusMessage = fmt.Sprintf("Status changed from %s to %s", m.Status.String(), newStatus.String())
+ m.Status = newStatus
+ } else {
m.StatusMessage = fmt.Sprintf(
- "Failed to transition : OldStatus %s , NewStatus %s , MSG : %s :",
+ "Failed to transition status from %s to %s",
m.Status.String(),
newStatus.String(),
- msg,
)
+
m.Status = CctxStatus_Aborted
- return
}
- m.Status = newStatus
-} //nolint:typecheck
+
+ if statusMsg != "" {
+ m.StatusMessage += fmt.Sprintf(": %s", statusMsg)
+ }
+}
+
+// UpdateErrorMessage updates cctx.status.error_message.
+func (m *Status) UpdateErrorMessage(errorMsg string) {
+ errMsg := errorMsg
+
+ if errMsg == "" {
+ errMsg = "unknown error"
+ }
+
+ m.ErrorMessage = errMsg
+}
func (m *Status) ValidateTransition(newStatus CctxStatus) bool {
stateTransitionMap := stateTransitionMap()
diff --git a/x/crosschain/types/status_test.go b/x/crosschain/types/status_test.go
index 88013a41a9..5bb272d522 100644
--- a/x/crosschain/types/status_test.go
+++ b/x/crosschain/types/status_test.go
@@ -140,31 +140,35 @@ func TestStatus_ChangeStatus(t *testing.T) {
t.Run("should change status and msg if transition is valid", func(t *testing.T) {
s := types.Status{Status: types.CctxStatus_PendingInbound}
- s.ChangeStatus(types.CctxStatus_PendingOutbound, "msg")
+ s.UpdateStatus(types.CctxStatus_PendingOutbound, "msg")
assert.Equal(t, s.Status, types.CctxStatus_PendingOutbound)
- assert.Equal(t, s.StatusMessage, "msg")
+ assert.Equal(t, s.StatusMessage, "Status changed from PendingInbound to PendingOutbound: msg")
})
t.Run("should change status if transition is valid", func(t *testing.T) {
s := types.Status{Status: types.CctxStatus_PendingInbound}
- s.ChangeStatus(types.CctxStatus_PendingOutbound, "")
+ s.UpdateStatus(types.CctxStatus_PendingOutbound, "")
+ fmt.Printf("%+v\n", s)
assert.Equal(t, s.Status, types.CctxStatus_PendingOutbound)
- assert.Equal(t, s.StatusMessage, "")
+ assert.Equal(t, s.StatusMessage, fmt.Sprintf(
+ "Status changed from %s to %s",
+ types.CctxStatus_PendingInbound.String(),
+ types.CctxStatus_PendingOutbound.String()),
+ )
})
t.Run("should change status to aborted and msg if transition is invalid", func(t *testing.T) {
s := types.Status{Status: types.CctxStatus_PendingOutbound}
- s.ChangeStatus(types.CctxStatus_PendingInbound, "msg")
+ s.UpdateStatus(types.CctxStatus_PendingInbound, "msg")
assert.Equal(t, s.Status, types.CctxStatus_Aborted)
assert.Equal(
t,
fmt.Sprintf(
- "Failed to transition : OldStatus %s , NewStatus %s , MSG : %s :",
+ "Failed to transition status from %s to %s: msg",
types.CctxStatus_PendingOutbound.String(),
types.CctxStatus_PendingInbound.String(),
- "msg",
),
s.StatusMessage,
)
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 {
diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go
index 6cf8af9de6..f089f26815 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 if it's already 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.
@@ -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.
@@ -329,7 +334,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(),
@@ -338,6 +348,7 @@ func (ob *Observer) WithLogger(logger Logger) *Observer {
Headers: chainLogger.With().Str(logs.FieldModule, logs.ModNameHeaders).Logger(),
Compliance: logger.Compliance,
}
+
return ob
}
@@ -461,22 +472,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/base/observer_test.go b/zetaclient/chains/base/observer_test.go
index 0e772e31f9..0c53bea35c 100644
--- a/zetaclient/chains/base/observer_test.go
+++ b/zetaclient/chains/base/observer_test.go
@@ -175,15 +175,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 d31715db9a..fad3c87230 100644
--- a/zetaclient/chains/bitcoin/observer/inbound.go
+++ b/zetaclient/chains/bitcoin/observer/inbound.go
@@ -59,7 +59,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
@@ -89,7 +89,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
@@ -205,7 +205,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
@@ -224,7 +224,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 80781fdd33..f78d81af9c 100644
--- a/zetaclient/chains/bitcoin/observer/observer.go
+++ b/zetaclient/chains/bitcoin/observer/observer.go
@@ -162,25 +162,9 @@ 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 {
+ if ok := ob.Observer.Start(); !ok {
ob.Logger().Chain.Info().Msgf("observer is already started for chain %d", ob.Chain().ChainId)
return
}
@@ -219,12 +203,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
@@ -238,25 +222,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
@@ -316,7 +300,7 @@ func (ob *Observer) PostGasPrice(ctx context.Context) error {
// 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
@@ -326,7 +310,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)
@@ -341,7 +325,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 3b25d8c0c1..16c8d24b81 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 e0fc3c651d..03688f4aa4 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..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"
@@ -39,23 +38,20 @@ 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)
}
- 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 {
@@ -74,7 +70,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 +87,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 +106,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 +187,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 +611,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..8823b13c47 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
}
@@ -190,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/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 ab58456de6..c89a77e4b8 100644
--- a/zetaclient/chains/interfaces/interfaces.go
+++ b/zetaclient/chains/interfaces/interfaces.go
@@ -39,15 +39,21 @@ const (
// ChainObserver is the interface for chain observer
type ChainObserver interface {
+ // Start starts the observer
Start(ctx context.Context)
+
+ // Stop stops the observer
Stop()
- VoteOutboundIfConfirmed(
- ctx context.Context,
- cctx *crosschaintypes.CrossChainTx,
- ) (bool, error)
+
+ // ChainParams returns observer chain params (might be out of date with zetacore)
+ ChainParams() observertypes.ChainParams
+
+ // SetChainParams sets observer chain params
SetChainParams(observertypes.ChainParams)
- GetChainParams() observertypes.ChainParams
- WatchInboundTracker(ctx context.Context) error
+
+ // VoteOutboundIfConfirmed checks outbound status and returns (continueKeySign, error)
+ // todo we should make this simpler.
+ 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..0548fcd6d3 100644
--- a/zetaclient/chains/solana/observer/observer.go
+++ b/zetaclient/chains/solana/observer/observer.go
@@ -96,25 +96,9 @@ 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 {
+ 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_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/config.go b/zetaclient/chains/ton/config.go
index ee7eaac701..731287756e 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,16 @@ func ConfigFromURL(ctx context.Context, url string) (*GlobalConfigurationFile, e
return config.ParseConfig(res.Body)
}
+
+func ConfigFromPath(path string) (*GlobalConfigurationFile, error) {
+ return config.ParseConfigFile(path)
+}
+
+// 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())
+ }
+
+ return ConfigFromPath(urlOrPath)
+}
diff --git a/zetaclient/chains/ton/liteapi/client.go b/zetaclient/chains/ton/liteapi/client.go
new file mode 100644
index 0000000000..25b0efcf39
--- /dev/null
+++ b/zetaclient/chains/ton/liteapi/client.go
@@ -0,0 +1,230 @@
+package liteapi
+
+import (
+ "context"
+ "fmt"
+ "slices"
+ "strconv"
+ "strings"
+
+ lru "github.com/hashicorp/golang-lru"
+ "github.com/pkg/errors"
+ "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
+// 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
+ blockCacheSize = 250
+)
+
+// New Client constructor.
+func New(client *liteapi.Client) *Client {
+ blockCache, _ := lru.New(blockCacheSize)
+
+ return &Client{Client: client, blockCache: blockCache}
+}
+
+// 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")
+ }
+
+ 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.
+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
+// 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.getLastTransactionHash(ctx, acc)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ var (
+ tx *ton.Transaction
+ scrolled int
+ )
+
+ 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
+}
+
+// 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.getLastTransactionHash(ctx, acc)
+ if err != nil {
+ return nil, err
+ }
+
+ var result []ton.Transaction
+
+ 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())
+ }
+
+ 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
+ }
+
+ // reverse the result to get the oldest tx first
+ slices.Reverse(result)
+
+ return result, nil
+}
+
+// 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")
+ }
+
+ if state.Account.Status() != tlb.AccountActive {
+ return 0, tlb.Bits256{}, errors.New("account is not active")
+ }
+
+ 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())
+}
+
+// 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 encoded 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..ed3c850dd8
--- /dev/null
+++ b/zetaclient/chains/ton/liteapi/client_live_test.go
@@ -0,0 +1,198 @@
+package liteapi
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "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/tlb"
+ "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 = New(mustCreateClient(t))
+ )
+
+ t.Run("GetFirstTransaction", func(t *testing.T) {
+ t.Run("Account doesn't exist", func(t *testing.T) {
+ // ARRANGE
+ accountID, err := ton.ParseAccountID("0:55798cb7b87168251a7c39f6806b8c202f6caa0f617a76f4070b3fdacfd056a2")
+ require.NoError(t, err)
+
+ // ACT
+ tx, scrolled, err := client.GetFirstTransaction(ctx, accountID)
+
+ // ASSERT
+ require.ErrorContains(t, err, "account is not active")
+ require.Zero(t, scrolled)
+ require.Nil(t, tx)
+ })
+
+ 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)
+
+ // 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)
+ })
+ })
+
+ t.Run("GetTransactionsUntil", func(t *testing.T) {
+ // ARRANGE
+ // Given sample account id (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.GetTransactionsSince(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)
+ })
+
+ 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 {
+ 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
+}
+
+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/liteapi/client_test.go b/zetaclient/chains/ton/liteapi/client_test.go
new file mode 100644
index 0000000000..a1148540be
--- /dev/null
+++ b/zetaclient/chains/ton/liteapi/client_test.go
@@ -0,0 +1,100 @@
+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))
+}
+
+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)
+ })
+ }
+}
diff --git a/zetaclient/chains/ton/observer/inbound.go b/zetaclient/chains/ton/observer/inbound.go
new file mode 100644
index 0000000000..95f9a510d7
--- /dev/null
+++ b/zetaclient/chains/ton/observer/inbound.go
@@ -0,0 +1,260 @@
+package observer
+
+import (
+ "context"
+ "encoding/hex"
+ "fmt"
+
+ "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 (
+ // 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); 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"),
+ )
+}
+
+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")
+ }
+
+ // 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.GetTransactionsSince(ctx, ob.gateway.AccountID(), lt, hashBits)
+ if err != nil {
+ return errors.Wrap(err, "unable to get transactions")
+ }
+
+ 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))
+ }
+
+ for i := range txs {
+ tx := txs[i]
+
+ parsedTX, skip, err := ob.gateway.ParseAndFilter(tx, toncontracts.FilterInbounds)
+ if err != nil {
+ return errors.Wrap(err, "unable to parse and filter tx")
+ }
+
+ 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 {
+ 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)
+ }
+
+ 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)
+}
+
+// extractInboundData parses Gateway tx into deposit (TON sender, amount, memo)
+func extractInboundData(tx *toncontracts.Transaction) (string, math.Uint, []byte, error) {
+ 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
+ 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)
+ }
+}
+
+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()))
+ )
+
+ // TODO: use protocol contract v2 for deposit
+ // https://github.com/zeta-chain/node/issues/2967
+
+ msg := zetacore.GetInboundVoteMessage(
+ sender,
+ ob.Chain().ChainId,
+ sender,
+ sender,
+ ob.ZetacoreClient().Chain().ChainId,
+ amount,
+ hex.EncodeToString(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.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)
+
+ if err := ob.WriteLastTxScannedToDB(txHash); err != nil {
+ ob.Logger().Inbound.Error().
+ Err(err).
+ Fields(txLogFields(tx)).
+ Msgf("setLastScannedTX: unable to WriteLastTxScannedToDB")
+
+ return
+ }
+
+ ob.Logger().Inbound.Info().
+ 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.BlockID.String(),
+ }
+}
diff --git a/zetaclient/chains/ton/observer/inbound_test.go b/zetaclient/chains/ton/observer/inbound_test.go
new file mode 100644
index 0000000000..281db44f79
--- /dev/null
+++ b/zetaclient/chains/ton/observer/inbound_test.go
@@ -0,0 +1,331 @@
+package observer
+
+import (
+ "encoding/hex"
+ "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("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
+ 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.OnGetTransactionsSince(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.
+ OnGetTransactionsSince(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.
+ OnGetTransactionsSince(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, hex.EncodeToString(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.
+ OnGetTransactionsSince(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())
+
+ 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))
+ 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.
+ OnGetTransactionsSince(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
new file mode 100644
index 0000000000..e20742116a
--- /dev/null
+++ b/zetaclient/chains/ton/observer/observer.go
@@ -0,0 +1,80 @@
+package observer
+
+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"
+)
+
+// Observer is a TON observer.
+type Observer struct {
+ *base.Observer
+
+ 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, blockID ton.BlockIDExt, mode uint32) (tlb.BlockInfo, 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)
+}
+
+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():
+ 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{
+ Observer: bo,
+ client: client,
+ gateway: 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)
+ 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))
+
+ // TODO: watchInboundTracker
+ // https://github.com/zeta-chain/node/issues/2935
+
+ // TODO: outbounds/withdrawals: (watchOutbound, watchGasPrice, watchRPCStatus)
+ // https://github.com/zeta-chain/node/issues/2807
+}
+
+func (ob *Observer) VoteOutboundIfConfirmed(_ context.Context, _ *types.CrossChainTx) (bool, error) {
+ return false, errors.New("not implemented")
+}
diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go
index e978a589b9..38c032eb4a 100644
--- a/zetaclient/chains/ton/observer/observer_test.go
+++ b/zetaclient/chains/ton/observer/observer_test.go
@@ -2,62 +2,171 @@ package observer
import (
"context"
- "encoding/json"
- "strings"
+ "strconv"
"testing"
+ "cosmossdk.io/math"
+ "github.com/rs/zerolog"
+ "github.com/stretchr/testify/mock"
"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/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"
)
-// 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="}}}`
+type testSuite struct {
+ ctx context.Context
+ t *testing.T
-func TestObserver(t *testing.T) {
- t.Skip("skip test")
+ chain chains.Chain
+ chainParams *observertypes.ChainParams
- ctx := context.Background()
+ liteClient *mocks.LiteClient
- cfg, err := config.ParseConfig(strings.NewReader(configRaw))
- require.NoError(t, err)
+ 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()
- client, err := liteapi.NewClient(liteapi.WithConfigurationFile(*cfg))
+ 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)
- res, err := client.GetMasterchainInfo(ctx)
+ baseObserver, err := base.NewObserver(
+ chain,
+ *chainParams,
+ zetacore,
+ tss,
+ 1,
+ 1,
+ 60,
+ nil,
+ database,
+ logger,
+ )
+
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, "", " ")
+ 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).Maybe()
+
+ 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) OnGetTransactionsSince(
+ acc ton.AccountID,
+ lt uint64,
+ hash ton.Bits256,
+ txs []ton.Transaction,
+ err error,
+) *mock.Call {
+ return ts.liteClient.
+ On("GetTransactionsSince", mock.Anything, 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", mock.Anything, id, uint32(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)
- t.Log(string(b))
+ 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)
+ require.True(ts.t, ok, "unexpected cctx type")
+
+ ts.votesBag = append(ts.votesBag, cctx)
+ }
+ 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 ca0234c126..6f17153b52 100644
--- a/zetaclient/config/config_chain.go
+++ b/zetaclient/config/config_chain.go
@@ -23,6 +23,7 @@ func New(setDefaults bool) Config {
cfg.EVMChainConfigs = evmChainsConfigs()
cfg.BTCChainConfigs = btcChainsConfigs()
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 bf225b97f4..a60875b5e8 100644
--- a/zetaclient/config/types.go
+++ b/zetaclient/config/types.go
@@ -60,6 +60,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"`
@@ -97,6 +104,7 @@ type Config struct {
// Deprecated: the 'BitcoinConfig' will be removed once the 'BTCChainConfigs' is fully adopted
BitcoinConfig BTCConfig `json:"BitcoinConfig"`
SolanaConfig SolanaConfig `json:"SolanaConfig"`
+ TONConfig TONConfig `json:"TONConfig"`
// compliance config
ComplianceConfig ComplianceConfig `json:"ComplianceConfig"`
@@ -149,6 +157,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{}
+}
+
// StringMasked returns the string representation of the config with sensitive fields masked.
// Currently only the endpoints and bitcoin credentials are masked.
func (c Config) StringMasked() string {
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")
}
diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go
index 117b921168..4f35953ce0 100644
--- a/zetaclient/context/chain.go
+++ b/zetaclient/context/chain.go
@@ -1,6 +1,7 @@
package context
import (
+ "cmp"
"fmt"
"sync"
@@ -65,7 +66,9 @@ func (cr *ChainRegistry) Get(chainID int64) (Chain, error) {
func (cr *ChainRegistry) All() []Chain {
items := maps.Values(cr.chains)
- slices.SortFunc(items, func(a, b Chain) bool { return a.ID() < b.ID() })
+ slices.SortFunc(items, func(a, b Chain) int {
+ return cmp.Compare(a.ID(), b.ID())
+ })
return items
}
@@ -165,6 +168,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/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/bootstap_test.go b/zetaclient/orchestrator/bootstap_test.go
index 73f47d21cf..eaae3a8e6d 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.BTCChainConfigs[chains.BitcoinMainnet.ChainId] = 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 08d625548f..34c94bf77d 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,9 +21,12 @@ 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"
+ "github.com/zeta-chain/node/zetaclient/logs"
"github.com/zeta-chain/node/zetaclient/metrics"
)
@@ -71,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++
}
@@ -80,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++
}
)
@@ -181,6 +186,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()).
@@ -238,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++
}
@@ -247,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++
}
@@ -394,6 +404,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.NewFromSource(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/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go
index dd0ef1eaab..698a520fcf 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"
@@ -228,7 +229,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 +448,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 +547,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 +619,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 {
@@ -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.
diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go
index 969dbbb393..2ab34b900e 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 {
diff --git a/zetaclient/testutils/mocks/ton_liteclient.go b/zetaclient/testutils/mocks/ton_liteclient.go
new file mode 100644
index 0000000000..f11ccaf24c
--- /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, 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")
+ }
+
+ var r0 tlb.BlockInfo
+ var r1 error
+ 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, 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, uint32) error); ok {
+ r1 = rf(ctx, blockID, 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
+}
+
+// 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 GetTransactionsSince")
+ }
+
+ 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
+}
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)