From e0f9aab08b1ac365d8e9fee575be16079d453676 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Tue, 9 Dec 2025 21:07:40 -0600 Subject: [PATCH 01/22] support ton analyzer, need testing --- experimental/analyzer/report_builder.go | 19 +++++++++ experimental/analyzer/ton_analyzer.go | 47 ++++++++++++++++++++++ experimental/analyzer/upf/upf.go | 31 +++++++++++---- go.mod | 25 ++++++------ go.sum | 53 +++++++++++-------------- 5 files changed, 126 insertions(+), 49 deletions(-) create mode 100644 experimental/analyzer/ton_analyzer.go diff --git a/experimental/analyzer/report_builder.go b/experimental/analyzer/report_builder.go index 71e68030..9f2532cd 100644 --- a/experimental/analyzer/report_builder.go +++ b/experimental/analyzer/report_builder.go @@ -48,6 +48,12 @@ func BuildProposalReport(ctx context.Context, proposalContext ProposalContext, e return nil, err } calls = dec + case chainsel.FamilyTon: + dec, err := AnalyzeTONTransactions(proposalContext, chainSel, []types.Transaction{op.Transaction}) + if err != nil { + return nil, err + } + calls = dec default: calls = []*DecodedCall{} } @@ -128,6 +134,19 @@ func BuildTimelockReport(ctx context.Context, proposalCtx ProposalContext, env d Calls: []*DecodedCall{dec[j]}, } } + case chainsel.FamilyTon: + dec, err := AnalyzeTONTransactions(proposalCtx, chainSel, batch.Transactions) + if err != nil { + return nil, err + } + for j := range dec { + ops[j] = OperationReport{ + ChainSelector: chainSel, + ChainName: chainNameOrUnknown(chainName), + Family: family, + Calls: []*DecodedCall{dec[j]}, + } + } default: for j := range batch.Transactions { ops[j] = OperationReport{ diff --git a/experimental/analyzer/ton_analyzer.go b/experimental/analyzer/ton_analyzer.go new file mode 100644 index 00000000..e4959255 --- /dev/null +++ b/experimental/analyzer/ton_analyzer.go @@ -0,0 +1,47 @@ +package analyzer + +import ( + "fmt" + + "github.com/smartcontractkit/mcms/sdk" + "github.com/smartcontractkit/mcms/sdk/ton" + "github.com/smartcontractkit/mcms/types" +) + +func AnalyzeTONTransactions(ctx ProposalContext, chainSelector uint64, txs []types.Transaction) ([]*DecodedCall, error) { + decoder := ton.NewDecoder() + decodedTxs := make([]*DecodedCall, len(txs)) + for i, op := range txs { + analyzedTransaction, err := AnalyzeTONTransaction(ctx, decoder, chainSelector, op) + if err != nil { + return nil, fmt.Errorf("failed to analyze Sui transaction %d: %w", i, err) + } + decodedTxs[i] = analyzedTransaction + } + + return decodedTxs, nil +} + +func AnalyzeTONTransaction(ctx ProposalContext, decoder sdk.Decoder, chainSelector uint64, mcmsTx types.Transaction) (*DecodedCall, error) { + decodedOp, err := decoder.Decode(mcmsTx, mcmsTx.ContractType) + if err != nil { + // Don't return an error to not block the whole proposal decoding because of a single transaction decode failure + errStr := fmt.Errorf("failed to decode Sui transaction: %w", err) + + return &DecodedCall{ + Address: mcmsTx.To, + Method: errStr.Error(), + }, nil + } + namedArgs, err := toNamedFields(decodedOp) + if err != nil { + return nil, fmt.Errorf("failed to convert decoded operation to named arguments: %w", err) + } + + return &DecodedCall{ + Address: mcmsTx.To, + Method: decodedOp.MethodName(), + Inputs: namedArgs, + Outputs: []NamedField{}, + }, nil +} diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index 337b3506..28ebf752 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -14,6 +14,7 @@ import ( "github.com/smartcontractkit/mcms" mcmsaptossdk "github.com/smartcontractkit/mcms/sdk/aptos" mcmssuisdk "github.com/smartcontractkit/mcms/sdk/sui" + mcmstonsdk "github.com/smartcontractkit/mcms/sdk/ton" mcmstypes "github.com/smartcontractkit/mcms/types" "github.com/smartcontractkit/chainlink-deployments-framework/deployment" @@ -239,14 +240,10 @@ func encodeTransactionData(mcmsOp mcmstypes.Operation) (string, error) { } switch chainFamily { - case chainsel.FamilySolana: - return base64.StdEncoding.EncodeToString(mcmsOp.Transaction.Data), nil - case chainsel.FamilyAptos: - return base64.StdEncoding.EncodeToString(mcmsOp.Transaction.Data), nil - case chainsel.FamilySui: - return base64.StdEncoding.EncodeToString(mcmsOp.Transaction.Data), nil - default: + case chainsel.FamilyEVM: return "0x" + hex.EncodeToString(mcmsOp.Transaction.Data), nil + default: + return base64.StdEncoding.EncodeToString(mcmsOp.Transaction.Data), nil } } @@ -311,6 +308,18 @@ func batchOperationsToUpfDecodedCalls(ctx context.Context, proposalContext mcmsa } } + case chainsel.FamilyTon: + describedTxs, err := mcmsanalyzer.AnalyzeTONTransactions(proposalContext, chainSel, batch.Transactions) + if err != nil { + return nil, err + } + for callIdx, tx := range describedTxs { + decodedCalls[batchIdx][callIdx] = &DecodedInnerCall{ + To: tx.Address, + Data: cldDecodedCallToUpfDecodedCallData(tx), + } + } + default: for callIdx, mcmsTx := range batch.Transactions { decodedCalls[batchIdx][callIdx] = &DecodedInnerCall{ @@ -379,6 +388,14 @@ func analyzeTransaction( return analyzeResult, "", nil + case chainsel.FamilyTon: + decoder := mcmstonsdk.NewDecoder() + analyzeResult, err := mcmsanalyzer.AnalyzeTONTransaction(proposalCtx, decoder, uint64(mcmsOp.ChainSelector), mcmsOp.Transaction) + if err != nil { + return nil, "", err + } + + return analyzeResult, "", nil default: return nil, "", fmt.Errorf("unsupported chain family: %s", chainFamily) } diff --git a/go.mod b/go.mod index 24005314..2392884c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/smartcontractkit/chainlink-deployments-framework -go 1.24.5 +go 1.25.3 replace github.com/fbsobreira/gotron-sdk => github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.4 @@ -29,7 +29,7 @@ require ( github.com/smartcontractkit/ccip-owner-contracts v0.1.0 github.com/smartcontractkit/chain-selectors v1.0.85 github.com/smartcontractkit/chainlink-aptos v0.0.0-20251024142440-51f2ad2652a2 - github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20250903115155-a68d8c28ae1d + github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20251024142759-093ed1b4017f github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250805210128-7f8a0f403c3a github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250805210128-7f8a0f403c3a github.com/smartcontractkit/chainlink-protos/job-distributor v0.17.0 @@ -38,15 +38,15 @@ require ( github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e - github.com/smartcontractkit/libocr v0.0.0-20250707144819-babe0ec4e358 - github.com/smartcontractkit/mcms v0.31.1 - github.com/spf13/cobra v1.8.1 + github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d + github.com/smartcontractkit/mcms v0.31.2-0.20251205160904-b6f401b352d4 + github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/testcontainers/testcontainers-go v0.39.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.38.0 - github.com/xssnick/tonutils-go v1.13.0 + github.com/xssnick/tonutils-go v1.14.1 github.com/zksync-sdk/zksync2-go v1.1.1-0.20250620124214-2c742ee399c6 go.uber.org/zap v1.27.1 golang.org/x/crypto v0.45.0 @@ -70,9 +70,10 @@ require ( github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/lib/pq v1.10.9 // indirect github.com/minio/sha256-simd v1.0.1 // indirect - github.com/smartcontractkit/chainlink-common v0.9.5-0.20250908082700-aa3f5927af8c // indirect - github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.1 // indirect - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250829155125-f4655b0b4605 // indirect + github.com/smartcontractkit/chainlink-common v0.9.6-0.20251003171904-99a82a53b142 // indirect + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.4 // indirect + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250911124514-5874cc6d62b2 // indirect + github.com/smartcontractkit/chainlink-ton v0.0.0-20251201102550-b2faa06fd514 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) @@ -158,7 +159,6 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/graph-gophers/graphql-go v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect @@ -217,7 +217,6 @@ require ( github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect github.com/oklog/run v1.2.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect @@ -230,7 +229,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect @@ -272,7 +271,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.mongodb.org/mongo-driver v1.17.0 // indirect + go.mongodb.org/mongo-driver v1.17.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect diff --git a/go.sum b/go.sum index 091e7660..265ea329 100644 --- a/go.sum +++ b/go.sum @@ -171,7 +171,7 @@ github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= @@ -267,7 +267,6 @@ github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1: github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 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.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -332,7 +331,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -576,8 +574,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= -github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= @@ -602,7 +598,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -635,8 +630,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= @@ -695,18 +690,18 @@ github.com/smartcontractkit/chain-selectors v1.0.85 h1:A7eyN7KIACxmngn1MJJ0giVtf github.com/smartcontractkit/chain-selectors v1.0.85/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-aptos v0.0.0-20251024142440-51f2ad2652a2 h1:vGdeMwHO3ow88HvxfhA4DDPYNY0X9jmdux7L83UF/W8= github.com/smartcontractkit/chainlink-aptos v0.0.0-20251024142440-51f2ad2652a2/go.mod h1:iteU0WORHkArACVh/HoY/1bipV4TcNcJdTmom9uIT0E= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20250903115155-a68d8c28ae1d h1:4tEhMQnJW5jndVxukgC+/CVf+FkHVKg3AYygkMtOVUQ= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20250903115155-a68d8c28ae1d/go.mod h1://SdVNO8EApDtkB+iaexDScAnapRAvcGbxgF3H6ixhY= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20251024142759-093ed1b4017f h1:jP2BHA7+QSp5IzWykT8KunThNbSwEM9j6lCXwEoxTIc= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20251024142759-093ed1b4017f/go.mod h1:pETrvAF8uvkZgtDgI/oRllZZaC4IpPO26tMxh1u9LC4= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250805210128-7f8a0f403c3a h1:kQ8Zs6OzXizScIK8PEb8THxDUziGttGT9D6tTTAwmZk= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250805210128-7f8a0f403c3a/go.mod h1:Ve1xD71bl193YIZQEoJMmBqLGQJdNs29bwbuObwvbhQ= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250805210128-7f8a0f403c3a h1:38dAlTPRUQHZus5dCnBnQyf/V4oYn0p2svWlbPgHDQ4= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250805210128-7f8a0f403c3a/go.mod h1:xtZNi6pOKdC3sLvokDvXOhgHzT+cyBqH/gWwvxTxqrg= -github.com/smartcontractkit/chainlink-common v0.9.5-0.20250908082700-aa3f5927af8c h1:2cnAGt0nedGS/M0deXRCIJVsxTWi6CzYPXkTxqVwViY= -github.com/smartcontractkit/chainlink-common v0.9.5-0.20250908082700-aa3f5927af8c/go.mod h1:b5KI42+P0ZmUXuvOFzSH9uIB8K83wvXq1GNVoY+ePeg= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.1 h1:ca2z5OXgnbBPQRxpwXwBLJsUA1+cAp5ncfW4Ssvd6eY= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.1/go.mod h1:NZv/qKYGFRnkjOYBouajnDfFoZ+WDa6H2KNmSf1dnKc= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250829155125-f4655b0b4605 h1:yVH5tLDzW2ZBUpmkHF5nci1SRSXTcU3A1VZ8iS5qudA= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250829155125-f4655b0b4605/go.mod h1:jUC52kZzEnWF9tddHh85zolKybmLpbQ1oNA4FjOHt1Q= +github.com/smartcontractkit/chainlink-common v0.9.6-0.20251003171904-99a82a53b142 h1:xyGWWEwIwZv4wflEA12TDBGKEa86tDEECzl3n1Az2m0= +github.com/smartcontractkit/chainlink-common v0.9.6-0.20251003171904-99a82a53b142/go.mod h1:1r3aM96KHAESfnayJ3BTHCkP1qJS1BEG1r4czeoaXlA= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.4 h1:hvqATtrZ0iMRTI80cpBot/3JFbjz2j+2tvpfooVhRHw= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.4/go.mod h1:eKGyfTKzr0/PeR7qKN4l2FcW9p+HzyKUwAfGhm/5YZc= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250911124514-5874cc6d62b2 h1:1/KdO5AbUr3CmpLjMPuJXPo2wHMbfB8mldKLsg7D4M8= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250911124514-5874cc6d62b2/go.mod h1:jUC52kZzEnWF9tddHh85zolKybmLpbQ1oNA4FjOHt1Q= github.com/smartcontractkit/chainlink-protos/job-distributor v0.17.0 h1:xHPmFDhff7QpeFxKsZfk+24j4AlnQiFjjRh5O87Peu4= github.com/smartcontractkit/chainlink-protos/job-distributor v0.17.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4 h1:AEnxv4HM3WD1RbQkRiFyb9cJ6YKAcqBp1CpIcFdZfuo= @@ -717,6 +712,8 @@ github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1 h1:Ld3 github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1/go.mod h1:r6KXRM1u9ch5KFR2jspkgtyWEC1X+gxPCL8mR63U990= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 h1:ZJ/8Jx6Be5//TyjPi1pS1uotnmcYq5vVkSyISIymSj8= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2/go.mod h1:kHYJnZUqiPF7/xN5273prV+srrLJkS77GbBXHLKQpx0= +github.com/smartcontractkit/chainlink-ton v0.0.0-20251201102550-b2faa06fd514 h1:Bdn0Ovc/QXZHyW49QCnbWRD0I7WDFP2nCELG4kpZB+4= +github.com/smartcontractkit/chainlink-ton v0.0.0-20251201102550-b2faa06fd514/go.mod h1:rxekiaWnJnFFfklae1OvO6T7xHJtsEldDvW/e5+b/qg= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 h1:7bxYNrPpygn8PUSBiEKn8riMd7CXMi/4bjTy0fHhcrY= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335/go.mod h1:ccjEgNeqOO+bjPddnL4lUrNLzyCvGCxgBjJdhFX3wa8= github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.4 h1:J4qtAo0ZmgX5pIr8Y5mdC+J2rj2e/6CTUC263t6mGOM= @@ -725,19 +722,19 @@ github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e h1:Hv9 github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e/go.mod h1:T4zH9R8R8lVWKfU7tUvYz2o2jMv1OpGCdpY2j2QZXzU= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= -github.com/smartcontractkit/libocr v0.0.0-20250707144819-babe0ec4e358 h1:+NVzR5LZVazRUunzVn34u+lwnpmn6NTVPCeZOVyQHLo= -github.com/smartcontractkit/libocr v0.0.0-20250707144819-babe0ec4e358/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.31.1 h1:sUIJG9pTMTpQ9WkLGSuPAIjq7z0b1KQ5rnL9KxaonXE= -github.com/smartcontractkit/mcms v0.31.1/go.mod h1:s/FrY+wVrmK7IfrSq8VPLGqqplX9Nv6Qek47ubz2+n8= +github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d h1:/0/80Ic6wpKH5F1nwDoRj9+70IxXunvCyNcCkA+9ik0= +github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= +github.com/smartcontractkit/mcms v0.31.2-0.20251205160904-b6f401b352d4 h1:aeUE5LXEvKNr5Zsm3h8yCPomxSlKrnK2burShsCPEPE= +github.com/smartcontractkit/mcms v0.31.2-0.20251205160904-b6f401b352d4/go.mod h1:hQ0TI6ZLElXfGKZFulsGQ/OObWLuMzjgRNL6856t1nY= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= @@ -808,8 +805,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/xssnick/tonutils-go v1.13.0 h1:LV2JzB+CuuWaLQiYNolK+YI3NRQOpS0W+T+N+ctF6VQ= -github.com/xssnick/tonutils-go v1.13.0/go.mod h1:EDe/9D/HZpAenbR+WPMQHICOF0BZWAe01TU5+Vpg08k= +github.com/xssnick/tonutils-go v1.14.1 h1:zV/iVYl/h3hArS+tPsd9XrSFfGert3r21caMltPSeHg= +github.com/xssnick/tonutils-go v1.14.1/go.mod h1:68xwWjpoGGqiTbLJ0gT63sKu1Z1moCnDLLzA+DKanIg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -821,15 +818,14 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zksync-sdk/zksync2-go v1.1.1-0.20250620124214-2c742ee399c6 h1:VRdX3Gn/I7ITbzUY4ZNfgn65tdQM9Zhf2b7KP0HZllk= github.com/zksync-sdk/zksync2-go v1.1.1-0.20250620124214-2c742ee399c6/go.mod h1:NWNlQS21isOsSsn+hLRAPpiuv+3P+LcdaZNuRt2T5Yo= -go.mongodb.org/mongo-driver v1.17.0 h1:Hp4q2MCjvY19ViwimTs00wHi7G4yzxh4/2+nTx8r40k= -go.mongodb.org/mongo-driver v1.17.0/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= +go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= +go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= @@ -864,7 +860,6 @@ go.opentelemetry.io/otel/sdk/log/logtest v0.13.0 h1:9yio6AFZ3QD9j9oqshV1Ibm9gPLl go.opentelemetry.io/otel/sdk/log/logtest v0.13.0/go.mod h1:QOGiAJHl+fob8Nu85ifXfuQYmJTFAvcrxL6w5/tu168= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= From a51337a053f36d2cffe6d4c0a033188c7139ddc2 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Tue, 9 Dec 2025 21:10:41 -0600 Subject: [PATCH 02/22] fix typo --- experimental/analyzer/ton_analyzer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experimental/analyzer/ton_analyzer.go b/experimental/analyzer/ton_analyzer.go index e4959255..5e9b01bc 100644 --- a/experimental/analyzer/ton_analyzer.go +++ b/experimental/analyzer/ton_analyzer.go @@ -14,7 +14,7 @@ func AnalyzeTONTransactions(ctx ProposalContext, chainSelector uint64, txs []typ for i, op := range txs { analyzedTransaction, err := AnalyzeTONTransaction(ctx, decoder, chainSelector, op) if err != nil { - return nil, fmt.Errorf("failed to analyze Sui transaction %d: %w", i, err) + return nil, fmt.Errorf("failed to analyze TON transaction %d: %w", i, err) } decodedTxs[i] = analyzedTransaction } @@ -26,7 +26,7 @@ func AnalyzeTONTransaction(ctx ProposalContext, decoder sdk.Decoder, chainSelect decodedOp, err := decoder.Decode(mcmsTx, mcmsTx.ContractType) if err != nil { // Don't return an error to not block the whole proposal decoding because of a single transaction decode failure - errStr := fmt.Errorf("failed to decode Sui transaction: %w", err) + errStr := fmt.Errorf("failed to decode TON transaction: %w", err) return &DecodedCall{ Address: mcmsTx.To, From 09fc3c283f7359b61b79a0aecf1eced488774a31 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Tue, 9 Dec 2025 21:22:18 -0600 Subject: [PATCH 03/22] mod tidy --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 2392884c..c487f119 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d - github.com/smartcontractkit/mcms v0.31.2-0.20251205160904-b6f401b352d4 + github.com/smartcontractkit/mcms v0.31.2-0.20251209144844-5431a6ab1526 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 @@ -73,7 +73,7 @@ require ( github.com/smartcontractkit/chainlink-common v0.9.6-0.20251003171904-99a82a53b142 // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.4 // indirect github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250911124514-5874cc6d62b2 // indirect - github.com/smartcontractkit/chainlink-ton v0.0.0-20251201102550-b2faa06fd514 // indirect + github.com/smartcontractkit/chainlink-ton v0.0.0-20251209121704-8d997e4a1833 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) @@ -239,7 +239,7 @@ require ( github.com/rs/zerolog v1.34.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect - github.com/samber/lo v1.49.1 // indirect + github.com/samber/lo v1.52.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/scylladb/go-reflectx v1.0.1 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect diff --git a/go.sum b/go.sum index 265ea329..76cbf3a2 100644 --- a/go.sum +++ b/go.sum @@ -660,8 +660,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= -github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -712,8 +712,8 @@ github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1 h1:Ld3 github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1/go.mod h1:r6KXRM1u9ch5KFR2jspkgtyWEC1X+gxPCL8mR63U990= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 h1:ZJ/8Jx6Be5//TyjPi1pS1uotnmcYq5vVkSyISIymSj8= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2/go.mod h1:kHYJnZUqiPF7/xN5273prV+srrLJkS77GbBXHLKQpx0= -github.com/smartcontractkit/chainlink-ton v0.0.0-20251201102550-b2faa06fd514 h1:Bdn0Ovc/QXZHyW49QCnbWRD0I7WDFP2nCELG4kpZB+4= -github.com/smartcontractkit/chainlink-ton v0.0.0-20251201102550-b2faa06fd514/go.mod h1:rxekiaWnJnFFfklae1OvO6T7xHJtsEldDvW/e5+b/qg= +github.com/smartcontractkit/chainlink-ton v0.0.0-20251209121704-8d997e4a1833 h1:jy8TfjU+A1MFt70JRJ9GjTQshNlk/dBpRPzXmdHKDPk= +github.com/smartcontractkit/chainlink-ton v0.0.0-20251209121704-8d997e4a1833/go.mod h1:rxekiaWnJnFFfklae1OvO6T7xHJtsEldDvW/e5+b/qg= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 h1:7bxYNrPpygn8PUSBiEKn8riMd7CXMi/4bjTy0fHhcrY= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335/go.mod h1:ccjEgNeqOO+bjPddnL4lUrNLzyCvGCxgBjJdhFX3wa8= github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.4 h1:J4qtAo0ZmgX5pIr8Y5mdC+J2rj2e/6CTUC263t6mGOM= @@ -724,8 +724,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d h1:/0/80Ic6wpKH5F1nwDoRj9+70IxXunvCyNcCkA+9ik0= github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.31.2-0.20251205160904-b6f401b352d4 h1:aeUE5LXEvKNr5Zsm3h8yCPomxSlKrnK2burShsCPEPE= -github.com/smartcontractkit/mcms v0.31.2-0.20251205160904-b6f401b352d4/go.mod h1:hQ0TI6ZLElXfGKZFulsGQ/OObWLuMzjgRNL6856t1nY= +github.com/smartcontractkit/mcms v0.31.2-0.20251209144844-5431a6ab1526 h1:IZ8SZA8bLPe0eQ14npcVuTmDV6BldY9aeHKzCviloL0= +github.com/smartcontractkit/mcms v0.31.2-0.20251209144844-5431a6ab1526/go.mod h1:ciwGs3cGDB5yCorkEWy8VSOmE89HZwuldz0tKfjeCwI= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= From fe17bf138cc4bd38d2aea7cfa5af2cbad2ee354a Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Tue, 9 Dec 2025 21:49:07 -0600 Subject: [PATCH 04/22] update test --- experimental/analyzer/report_builder_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/experimental/analyzer/report_builder_test.go b/experimental/analyzer/report_builder_test.go index 444f3de1..891ef581 100644 --- a/experimental/analyzer/report_builder_test.go +++ b/experimental/analyzer/report_builder_test.go @@ -372,14 +372,14 @@ func TestBuildProposalReport_DefaultCase(t *testing.T) { renderer: NewMarkdownRenderer(), } - // Use a TON chain selector - TON family is not handled in the switch statement + // Use a TRON chain selector - TRON family is not handled in the switch statement // so it will trigger the default case - tonChainSelector := chainsel.TON_LOCALNET.Selector + tronChainSelector := chainsel.TRON_DEVNET.Selector proposal := &mcms.Proposal{ Operations: []types.Operation{ { - ChainSelector: types.ChainSelector(tonChainSelector), + ChainSelector: types.ChainSelector(tronChainSelector), Transaction: types.Transaction{ To: "0x1234567890123456789012345678901234567890", Data: []byte{0x01, 0x02, 0x03, 0x04}, @@ -395,8 +395,8 @@ func TestBuildProposalReport_DefaultCase(t *testing.T) { require.Len(t, report.Operations, 1) operation := report.Operations[0] - require.Equal(t, tonChainSelector, operation.ChainSelector) - require.Equal(t, "ton-localnet", operation.ChainName) // TON chain has a known name - require.Equal(t, "ton", operation.Family) // TON family - require.Empty(t, operation.Calls) // Default case sets calls to empty slice + require.Equal(t, tronChainSelector, operation.ChainSelector) + require.Equal(t, chainsel.TRON_DEVNET.Name, operation.ChainName) // TRON chain has a known name + require.Equal(t, chainsel.FamilyTron, operation.Family) // TRON family + require.Empty(t, operation.Calls) // Default case sets calls to empty slice } From a7aa1a27439abb7f1cc196f0441cd8e4181ba55a Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Wed, 10 Dec 2025 13:23:28 -0600 Subject: [PATCH 05/22] simplify analyze function --- experimental/analyzer/report_builder.go | 4 ++-- experimental/analyzer/sui_analyzer.go | 6 +++--- experimental/analyzer/sui_analyzer_test.go | 7 ++----- experimental/analyzer/ton_analyzer.go | 9 ++++----- experimental/analyzer/upf/upf.go | 10 +++------- 5 files changed, 14 insertions(+), 22 deletions(-) diff --git a/experimental/analyzer/report_builder.go b/experimental/analyzer/report_builder.go index 9f2532cd..5cf8be1d 100644 --- a/experimental/analyzer/report_builder.go +++ b/experimental/analyzer/report_builder.go @@ -49,7 +49,7 @@ func BuildProposalReport(ctx context.Context, proposalContext ProposalContext, e } calls = dec case chainsel.FamilyTon: - dec, err := AnalyzeTONTransactions(proposalContext, chainSel, []types.Transaction{op.Transaction}) + dec, err := AnalyzeTONTransactions(proposalContext, []types.Transaction{op.Transaction}) if err != nil { return nil, err } @@ -135,7 +135,7 @@ func BuildTimelockReport(ctx context.Context, proposalCtx ProposalContext, env d } } case chainsel.FamilyTon: - dec, err := AnalyzeTONTransactions(proposalCtx, chainSel, batch.Transactions) + dec, err := AnalyzeTONTransactions(proposalCtx, batch.Transactions) if err != nil { return nil, err } diff --git a/experimental/analyzer/sui_analyzer.go b/experimental/analyzer/sui_analyzer.go index 9b53e6f6..3f48d13d 100644 --- a/experimental/analyzer/sui_analyzer.go +++ b/experimental/analyzer/sui_analyzer.go @@ -10,10 +10,9 @@ import ( ) func AnalyzeSuiTransactions(ctx ProposalContext, chainSelector uint64, txs []types.Transaction) ([]*DecodedCall, error) { - decoder := mcmssuisdk.NewDecoder() decodedTxs := make([]*DecodedCall, len(txs)) for i, op := range txs { - analyzedTransaction, err := AnalyzeSuiTransaction(ctx, decoder, chainSelector, op) + analyzedTransaction, err := AnalyzeSuiTransaction(ctx, chainSelector, op) if err != nil { return nil, fmt.Errorf("failed to analyze Sui transaction %d: %w", i, err) } @@ -23,7 +22,8 @@ func AnalyzeSuiTransactions(ctx ProposalContext, chainSelector uint64, txs []typ return decodedTxs, nil } -func AnalyzeSuiTransaction(ctx ProposalContext, decoder *mcmssuisdk.Decoder, chainSelector uint64, mcmsTx types.Transaction) (*DecodedCall, error) { +func AnalyzeSuiTransaction(_ ProposalContext, chainSelector uint64, mcmsTx types.Transaction) (*DecodedCall, error) { + decoder := mcmssuisdk.NewDecoder() var additionalFields mcmssuisdk.AdditionalFields if err := json.Unmarshal(mcmsTx.AdditionalFields, &additionalFields); err != nil { return nil, fmt.Errorf("failed to unmarshal Sui additional fields: %w", err) diff --git a/experimental/analyzer/sui_analyzer_test.go b/experimental/analyzer/sui_analyzer_test.go index 6259825a..8fd84f72 100644 --- a/experimental/analyzer/sui_analyzer_test.go +++ b/experimental/analyzer/sui_analyzer_test.go @@ -5,7 +5,6 @@ import ( "testing" chainsel "github.com/smartcontractkit/chain-selectors" - mcmssuisdk "github.com/smartcontractkit/mcms/sdk/sui" "github.com/smartcontractkit/mcms/types" "github.com/stretchr/testify/require" @@ -25,7 +24,6 @@ func TestAnalyzeSuiTransactions(t *testing.T) { }, } - decoder := mcmssuisdk.NewDecoder() chainSelector := chainsel.SUI_TESTNET.Selector tests := []struct { @@ -119,7 +117,7 @@ func TestAnalyzeSuiTransactions(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - result, err := AnalyzeSuiTransaction(defaultProposalCtx, decoder, chainSelector, tt.mcmsTx) + result, err := AnalyzeSuiTransaction(defaultProposalCtx, chainSelector, tt.mcmsTx) if tt.wantErr { require.Error(t, err, "AnalyzeSuiTransaction() should have failed") @@ -173,7 +171,6 @@ func TestAnalyzeSuiTransactionWithErrors(t *testing.T) { }, } - decoder := mcmssuisdk.NewDecoder() chainSelector := chainsel.SUI_TESTNET.Selector tests := []struct { @@ -251,7 +248,7 @@ func TestAnalyzeSuiTransactionWithErrors(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - result, err := AnalyzeSuiTransaction(defaultProposalCtx, decoder, chainSelector, tt.mcmsTx) + result, err := AnalyzeSuiTransaction(defaultProposalCtx, chainSelector, tt.mcmsTx) if tt.wantError { require.Error(t, err, "AnalyzeSuiTransaction() should have failed") diff --git a/experimental/analyzer/ton_analyzer.go b/experimental/analyzer/ton_analyzer.go index 5e9b01bc..93068197 100644 --- a/experimental/analyzer/ton_analyzer.go +++ b/experimental/analyzer/ton_analyzer.go @@ -3,16 +3,14 @@ package analyzer import ( "fmt" - "github.com/smartcontractkit/mcms/sdk" "github.com/smartcontractkit/mcms/sdk/ton" "github.com/smartcontractkit/mcms/types" ) -func AnalyzeTONTransactions(ctx ProposalContext, chainSelector uint64, txs []types.Transaction) ([]*DecodedCall, error) { - decoder := ton.NewDecoder() +func AnalyzeTONTransactions(ctx ProposalContext, txs []types.Transaction) ([]*DecodedCall, error) { decodedTxs := make([]*DecodedCall, len(txs)) for i, op := range txs { - analyzedTransaction, err := AnalyzeTONTransaction(ctx, decoder, chainSelector, op) + analyzedTransaction, err := AnalyzeTONTransaction(ctx, op) if err != nil { return nil, fmt.Errorf("failed to analyze TON transaction %d: %w", i, err) } @@ -22,7 +20,8 @@ func AnalyzeTONTransactions(ctx ProposalContext, chainSelector uint64, txs []typ return decodedTxs, nil } -func AnalyzeTONTransaction(ctx ProposalContext, decoder sdk.Decoder, chainSelector uint64, mcmsTx types.Transaction) (*DecodedCall, error) { +func AnalyzeTONTransaction(_ ProposalContext, mcmsTx types.Transaction) (*DecodedCall, error) { + decoder := ton.NewDecoder() decodedOp, err := decoder.Decode(mcmsTx, mcmsTx.ContractType) if err != nil { // Don't return an error to not block the whole proposal decoding because of a single transaction decode failure diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index 28ebf752..b29c8da8 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -13,8 +13,6 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/mcms" mcmsaptossdk "github.com/smartcontractkit/mcms/sdk/aptos" - mcmssuisdk "github.com/smartcontractkit/mcms/sdk/sui" - mcmstonsdk "github.com/smartcontractkit/mcms/sdk/ton" mcmstypes "github.com/smartcontractkit/mcms/types" "github.com/smartcontractkit/chainlink-deployments-framework/deployment" @@ -309,7 +307,7 @@ func batchOperationsToUpfDecodedCalls(ctx context.Context, proposalContext mcmsa } case chainsel.FamilyTon: - describedTxs, err := mcmsanalyzer.AnalyzeTONTransactions(proposalContext, chainSel, batch.Transactions) + describedTxs, err := mcmsanalyzer.AnalyzeTONTransactions(proposalContext, batch.Transactions) if err != nil { return nil, err } @@ -380,8 +378,7 @@ func analyzeTransaction( return analyzeResult, "", nil case chainsel.FamilySui: - decoder := mcmssuisdk.NewDecoder() - analyzeResult, err := mcmsanalyzer.AnalyzeSuiTransaction(proposalCtx, decoder, uint64(mcmsOp.ChainSelector), mcmsOp.Transaction) + analyzeResult, err := mcmsanalyzer.AnalyzeSuiTransaction(proposalCtx, uint64(mcmsOp.ChainSelector), mcmsOp.Transaction) if err != nil { return nil, "", err } @@ -389,8 +386,7 @@ func analyzeTransaction( return analyzeResult, "", nil case chainsel.FamilyTon: - decoder := mcmstonsdk.NewDecoder() - analyzeResult, err := mcmsanalyzer.AnalyzeTONTransaction(proposalCtx, decoder, uint64(mcmsOp.ChainSelector), mcmsOp.Transaction) + analyzeResult, err := mcmsanalyzer.AnalyzeTONTransaction(proposalCtx, mcmsOp.Transaction) if err != nil { return nil, "", err } From d6f8113242aa26b6a0d9c455863ddb17a8d57da2 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Wed, 10 Dec 2025 17:12:51 -0600 Subject: [PATCH 06/22] refactor --- experimental/analyzer/upf/upf.go | 50 +++++++++----------------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index b29c8da8..ecf619fb 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -256,67 +256,37 @@ func batchOperationsToUpfDecodedCalls(ctx context.Context, proposalContext mcmsa } decodedCalls[batchIdx] = make([]*DecodedInnerCall, len(batch.Transactions)) - + var describedTxs []*mcmsanalyzer.DecodedCall switch family { case chainsel.FamilyEVM: - describedTxs, err := mcmsanalyzer.AnalyzeEVMTransactions(ctx, proposalContext, env, chainSel, batch.Transactions) + describedTxs, err = mcmsanalyzer.AnalyzeEVMTransactions(ctx, proposalContext, env, chainSel, batch.Transactions) if err != nil { return nil, err } - for callIdx, tx := range describedTxs { - decodedCalls[batchIdx][callIdx] = &DecodedInnerCall{ - To: tx.Address, - Data: cldDecodedCallToUpfDecodedCallData(tx), - } - } case chainsel.FamilySolana: - describedTxs, err := mcmsanalyzer.AnalyzeSolanaTransactions(proposalContext, chainSel, batch.Transactions) + describedTxs, err = mcmsanalyzer.AnalyzeSolanaTransactions(proposalContext, chainSel, batch.Transactions) if err != nil { return nil, err } - for callIdx, tx := range describedTxs { - decodedCalls[batchIdx][callIdx] = &DecodedInnerCall{ - To: tx.Address, - Data: cldDecodedCallToUpfDecodedCallData(tx), - } - } case chainsel.FamilyAptos: - describedTxs, err := mcmsanalyzer.AnalyzeAptosTransactions(proposalContext, chainSel, batch.Transactions) + describedTxs, err = mcmsanalyzer.AnalyzeAptosTransactions(proposalContext, chainSel, batch.Transactions) if err != nil { return nil, err } - for callIdx, tx := range describedTxs { - decodedCalls[batchIdx][callIdx] = &DecodedInnerCall{ - To: tx.Address, - Data: cldDecodedCallToUpfDecodedCallData(tx), - } - } case chainsel.FamilySui: - describedTxs, err := mcmsanalyzer.AnalyzeSuiTransactions(proposalContext, chainSel, batch.Transactions) + describedTxs, err = mcmsanalyzer.AnalyzeSuiTransactions(proposalContext, chainSel, batch.Transactions) if err != nil { return nil, err } - for callIdx, tx := range describedTxs { - decodedCalls[batchIdx][callIdx] = &DecodedInnerCall{ - To: tx.Address, - Data: cldDecodedCallToUpfDecodedCallData(tx), - } - } case chainsel.FamilyTon: - describedTxs, err := mcmsanalyzer.AnalyzeTONTransactions(proposalContext, batch.Transactions) + describedTxs, err = mcmsanalyzer.AnalyzeTONTransactions(proposalContext, batch.Transactions) if err != nil { return nil, err } - for callIdx, tx := range describedTxs { - decodedCalls[batchIdx][callIdx] = &DecodedInnerCall{ - To: tx.Address, - Data: cldDecodedCallToUpfDecodedCallData(tx), - } - } default: for callIdx, mcmsTx := range batch.Transactions { @@ -325,6 +295,14 @@ func batchOperationsToUpfDecodedCalls(ctx context.Context, proposalContext mcmsa Data: &DecodedCallData{FunctionName: family + " transaction decoding is not supported"}, } } + continue + } + + for callIdx, tx := range describedTxs { + decodedCalls[batchIdx][callIdx] = &DecodedInnerCall{ + To: tx.Address, + Data: cldDecodedCallToUpfDecodedCallData(tx), + } } } From d33bd19f7700bef013e778cb52ef6266ef5a48c1 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Wed, 10 Dec 2025 18:44:19 -0500 Subject: [PATCH 07/22] fix lint --- experimental/analyzer/upf/upf.go | 1 + 1 file changed, 1 insertion(+) diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index ecf619fb..40b66488 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -295,6 +295,7 @@ func batchOperationsToUpfDecodedCalls(ctx context.Context, proposalContext mcmsa Data: &DecodedCallData{FunctionName: family + " transaction decoding is not supported"}, } } + continue } From c6e01f1919734f5c93db83d68c21de4f01c9644f Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Thu, 11 Dec 2025 21:00:52 -0500 Subject: [PATCH 08/22] add test coverage --- experimental/analyzer/ton_analyzer_test.go | 311 +++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 experimental/analyzer/ton_analyzer_test.go diff --git a/experimental/analyzer/ton_analyzer_test.go b/experimental/analyzer/ton_analyzer_test.go new file mode 100644 index 00000000..954a7215 --- /dev/null +++ b/experimental/analyzer/ton_analyzer_test.go @@ -0,0 +1,311 @@ +package analyzer + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" + "github.com/smartcontractkit/mcms/sdk/ton" + "github.com/smartcontractkit/mcms/types" + "github.com/stretchr/testify/require" + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/tvm/cell" +) + +const tonTestAddress = "EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2" + +// testTONSetup contains common test fixtures for TON analyzer tests. +type testTONSetup struct { + targetAddr *address.Address + exampleRoleBig *big.Int +} + +func newTestTONSetup(t *testing.T) *testTONSetup { + t.Helper() + + exampleRole := crypto.Keccak256Hash([]byte("EXAMPLE_ROLE")) + exampleRoleBig, _ := cell.BeginCell(). + MustStoreBigInt(new(big.Int).SetBytes(exampleRole[:]), 257). + EndCell(). + ToBuilder(). + ToSlice(). + LoadBigInt(256) + + return &testTONSetup{ + targetAddr: address.MustParseAddr("EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8"), + exampleRoleBig: exampleRoleBig, + } +} + +func (s *testTONSetup) makeGrantRoleTx(t *testing.T, queryID uint64) types.Transaction { + t.Helper() + + grantRoleData, err := tlb.ToCell(rbac.GrantRole{ + QueryID: queryID, + Role: s.exampleRoleBig, + Account: s.targetAddr, + }) + require.NoError(t, err) + + tx, err := ton.NewTransaction( + s.targetAddr, + grantRoleData.ToBuilder().ToSlice(), + big.NewInt(0), + "com.chainlink.ton.lib.access.RBAC", + []string{"grantRole"}, + ) + require.NoError(t, err) + + return tx +} + +func (s *testTONSetup) expectedGrantRoleCall(queryID uint64) *DecodedCall { + return &DecodedCall{ + Address: s.targetAddr.String(), + Method: "com.chainlink.ton.lib.access.RBAC::GrantRole(0x0)", + Inputs: []NamedField{ + {Name: "QueryID", Value: SimpleField{Value: bigIntStr(queryID)}}, + {Name: "Role", Value: SimpleField{Value: s.exampleRoleBig.String()}}, + {Name: "Account", Value: SimpleField{Value: s.targetAddr.String()}}, + }, + } +} + +func bigIntStr(v uint64) string { + return new(big.Int).SetUint64(v).String() +} + +func makeInvalidTx(contractType string) types.Transaction { + return types.Transaction{ + OperationMetadata: types.OperationMetadata{ContractType: contractType}, + To: tonTestAddress, + Data: []byte{0xFF, 0xFF}, + AdditionalFields: json.RawMessage(`{"value":"0"}`), + } +} + +func TestAnalyzeTONTransaction(t *testing.T) { + t.Parallel() + + setup := newTestTONSetup(t) + ctx := &DefaultProposalContext{} + + tests := []struct { + name string + mcmsTx types.Transaction + want *DecodedCall + wantErrContain string + }{ + { + name: "success - RBAC GrantRole", + mcmsTx: setup.makeGrantRoleTx(t, 1), + want: setup.expectedGrantRoleCall(1), + }, + { + name: "invalid data", + mcmsTx: makeInvalidTx("com.chainlink.ton.mcms.MCMS"), + want: &DecodedCall{Address: tonTestAddress}, + wantErrContain: "invalid cell BOC data", + }, + { + name: "unknown contract type", + mcmsTx: types.Transaction{ + OperationMetadata: types.OperationMetadata{ContractType: "unknown.type"}, + To: tonTestAddress, + Data: []byte{0x01, 0x02}, + AdditionalFields: json.RawMessage(`{"value":"0"}`), + }, + want: &DecodedCall{Address: tonTestAddress}, + wantErrContain: "unknown contract interface: unknown.type", + }, + { + name: "empty data", + mcmsTx: types.Transaction{ + OperationMetadata: types.OperationMetadata{ContractType: "com.chainlink.ton.mcms.MCMS"}, + To: tonTestAddress, + Data: []byte{}, + AdditionalFields: json.RawMessage(`{"value":"0"}`), + }, + want: &DecodedCall{Address: tonTestAddress}, + wantErrContain: "invalid cell BOC data", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result, err := AnalyzeTONTransaction(ctx, tt.mcmsTx) + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, tt.want.Address, result.Address) + + if tt.wantErrContain != "" { + require.Contains(t, result.Method, tt.wantErrContain) + require.Nil(t, result.Inputs) + return + } + + assertDecodedCallEqual(t, tt.want, result) + }) + } +} + +func TestAnalyzeTONTransactions(t *testing.T) { + t.Parallel() + + setup := newTestTONSetup(t) + ctx := &DefaultProposalContext{} + + tests := []struct { + name string + txs []types.Transaction + want []*DecodedCall + wantErrContains []string + }{ + { + name: "multiple valid transactions", + txs: []types.Transaction{ + setup.makeGrantRoleTx(t, 1), + setup.makeGrantRoleTx(t, 2), + setup.makeGrantRoleTx(t, 3), + }, + want: []*DecodedCall{ + setup.expectedGrantRoleCall(1), + setup.expectedGrantRoleCall(2), + setup.expectedGrantRoleCall(3), + }, + }, + { + name: "mixed valid and invalid", + txs: []types.Transaction{ + makeInvalidTx("com.chainlink.ton.mcms.MCMS"), + setup.makeGrantRoleTx(t, 1), + makeInvalidTx("com.chainlink.ton.mcms.Timelock"), + }, + want: []*DecodedCall{ + {Address: tonTestAddress}, + setup.expectedGrantRoleCall(1), + {Address: tonTestAddress}, + }, + wantErrContains: []string{"invalid cell BOC data", "", "invalid cell BOC data"}, + }, + { + name: "all decode failures", + txs: []types.Transaction{ + makeInvalidTx("com.chainlink.ton.mcms.MCMS"), + makeInvalidTx("com.chainlink.ton.mcms.Timelock"), + }, + want: []*DecodedCall{ + {Address: tonTestAddress}, + {Address: tonTestAddress}, + }, + wantErrContains: []string{"invalid cell BOC data", "invalid cell BOC data"}, + }, + { + name: "empty list", + txs: []types.Transaction{}, + want: []*DecodedCall{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + results, err := AnalyzeTONTransactions(ctx, tt.txs) + require.NoError(t, err) + require.Len(t, results, len(tt.want)) + + for i, result := range results { + require.Equal(t, tt.want[i].Address, result.Address, "call %d", i) + + if len(tt.wantErrContains) > i && tt.wantErrContains[i] != "" { + require.Contains(t, result.Method, tt.wantErrContains[i], "call %d", i) + require.Nil(t, result.Inputs, "call %d", i) + continue + } + + assertDecodedCallEqual(t, tt.want[i], result) + } + }) + } +} + +func TestAnalyzeTONTransactions_BatchOperations(t *testing.T) { + t.Parallel() + + setup := newTestTONSetup(t) + ctx := &DefaultProposalContext{} + chainSelector := chainsel.TON_TESTNET.Selector + + batchOps := []types.BatchOperation{ + { + ChainSelector: types.ChainSelector(chainSelector), + Transactions: []types.Transaction{ + setup.makeGrantRoleTx(t, 1), + setup.makeGrantRoleTx(t, 2), + }, + }, + { + ChainSelector: types.ChainSelector(chainSelector), + Transactions: []types.Transaction{ + setup.makeGrantRoleTx(t, 3), + makeInvalidTx("com.chainlink.ton.lib.access.RBAC"), + }, + }, + } + + want := []*DecodedCall{ + setup.expectedGrantRoleCall(1), + setup.expectedGrantRoleCall(2), + setup.expectedGrantRoleCall(3), + {Address: tonTestAddress}, + } + wantErrContains := []string{"", "", "", "invalid cell BOC data"} + + var allResults []*DecodedCall + for _, batch := range batchOps { + results, err := AnalyzeTONTransactions(ctx, batch.Transactions) + require.NoError(t, err) + allResults = append(allResults, results...) + } + + require.Len(t, allResults, len(want)) + + for i, result := range allResults { + require.Equal(t, want[i].Address, result.Address, "call %d", i) + + if wantErrContains[i] != "" { + require.Contains(t, result.Method, wantErrContains[i], "call %d", i) + require.Nil(t, result.Inputs, "call %d", i) + } else { + assertDecodedCallEqual(t, want[i], result) + } + } +} + +// assertDecodedCallEqual compares two DecodedCall structs for equality. +func assertDecodedCallEqual(t *testing.T, expected, actual *DecodedCall) { + t.Helper() + + require.Equal(t, expected.Method, actual.Method) + require.Len(t, actual.Inputs, len(expected.Inputs)) + + for j, input := range actual.Inputs { + expectedInput := expected.Inputs[j] + require.Equal(t, expectedInput.Name, input.Name, "input %d", j) + + expectedField, ok := expectedInput.Value.(SimpleField) + require.True(t, ok, "expected SimpleField for input %d", j) + + actualField, ok := input.Value.(SimpleField) + require.True(t, ok, "expected SimpleField but got %T for input %d", input.Value, j) + + require.Equal(t, expectedField.GetValue(), actualField.GetValue(), "input %d", j) + } +} From 93c385e5b19204fe93a6bb2eeff96f0fd2f32e42 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Thu, 11 Dec 2025 21:06:10 -0500 Subject: [PATCH 09/22] lint --- experimental/analyzer/ton_analyzer_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/experimental/analyzer/ton_analyzer_test.go b/experimental/analyzer/ton_analyzer_test.go index 954a7215..663a63e6 100644 --- a/experimental/analyzer/ton_analyzer_test.go +++ b/experimental/analyzer/ton_analyzer_test.go @@ -147,6 +147,7 @@ func TestAnalyzeTONTransaction(t *testing.T) { if tt.wantErrContain != "" { require.Contains(t, result.Method, tt.wantErrContain) require.Nil(t, result.Inputs) + return } @@ -227,6 +228,7 @@ func TestAnalyzeTONTransactions(t *testing.T) { if len(tt.wantErrContains) > i && tt.wantErrContains[i] != "" { require.Contains(t, result.Method, tt.wantErrContains[i], "call %d", i) require.Nil(t, result.Inputs, "call %d", i) + continue } From a840aea73d08d570b64bd9f496d40cfc5dbd99cc Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Thu, 11 Dec 2025 23:00:35 -0500 Subject: [PATCH 10/22] add test --- experimental/analyzer/report_builder_test.go | 110 ++++++---- experimental/analyzer/ton_analyzer.go | 14 +- experimental/analyzer/ton_analyzer_test.go | 6 +- experimental/analyzer/upf/upf.go | 33 ++- experimental/analyzer/upf/upf_test.go | 215 +++++++++++++++++++ 5 files changed, 331 insertions(+), 47 deletions(-) diff --git a/experimental/analyzer/report_builder_test.go b/experimental/analyzer/report_builder_test.go index 891ef581..bc39bba9 100644 --- a/experimental/analyzer/report_builder_test.go +++ b/experimental/analyzer/report_builder_test.go @@ -195,33 +195,44 @@ func TestChainNameOrUnknown(t *testing.T) { require.Equal(t, "", chainNameOrUnknown(" ")) } -func TestBuildProposalReport_FamilyBranches(t *testing.T) { +func TestBuildProposalReport_FamilyErrors(t *testing.T) { t.Parallel() tests := []struct { - name string - selector uint64 - expectedError string + name string + selector uint64 + expectedMsg string + wantErr bool // if true, expect returned error; if false, error is in Method field (TON behavior) }{ { - name: "EVM_missing_registry", - selector: chainsel.ETHEREUM_TESTNET_SEPOLIA.Selector, - expectedError: "EVM registry is not available", + name: "EVM_missing_registry", + selector: chainsel.ETHEREUM_TESTNET_SEPOLIA.Selector, + expectedMsg: "EVM registry is not available", + wantErr: true, }, { - name: "Solana_missing_registry", - selector: chainsel.SOLANA_DEVNET.Selector, - expectedError: "failed to analyze solana transaction 0: solana decoder registry is not available", + name: "Solana_missing_registry", + selector: chainsel.SOLANA_DEVNET.Selector, + expectedMsg: "failed to analyze solana transaction 0: solana decoder registry is not available", + wantErr: true, }, { - name: "Aptos_unmarshal_additional_fields", - selector: chainsel.APTOS_TESTNET.Selector, - expectedError: "failed to unmarshal Aptos additional fields: unexpected end of JSON input", + name: "Aptos_unmarshal_additional_fields", + selector: chainsel.APTOS_TESTNET.Selector, + expectedMsg: "failed to unmarshal Aptos additional fields: unexpected end of JSON input", + wantErr: true, }, { - name: "Sui_unmarshal_additional_fields", - selector: chainsel.SUI_TESTNET.Selector, - expectedError: "failed to unmarshal Sui additional fields: unexpected end of JSON input", + name: "Sui_unmarshal_additional_fields", + selector: chainsel.SUI_TESTNET.Selector, + expectedMsg: "failed to unmarshal Sui additional fields: unexpected end of JSON input", + wantErr: true, + }, + { + name: "TON_decode_failure", + selector: chainsel.TON_TESTNET.Selector, + expectedMsg: "failed to decode TON transaction", + wantErr: false, // TON doesn't unmarshal AdditionalFields, so decode errors go to Method field }, } @@ -245,40 +256,56 @@ func TestBuildProposalReport_FamilyBranches(t *testing.T) { }, } - _, err := BuildProposalReport(t.Context(), ctx, deployment.Environment{}, proposal) - require.Error(t, err) - require.Contains(t, err.Error(), tt.expectedError) + report, err := BuildProposalReport(t.Context(), ctx, deployment.Environment{}, proposal) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedMsg) + } else { + require.NoError(t, err) + require.Contains(t, report.Operations[0].Calls[0].Method, tt.expectedMsg) + } }) } } -func TestBuildTimelockReport_FamilyBranches(t *testing.T) { +func TestBuildTimelockReport_FamilyErrors(t *testing.T) { t.Parallel() tests := []struct { - name string - selector uint64 - expectedError string + name string + selector uint64 + expectedMsg string + wantErr bool // if true, expect returned error; if false, error is in Method field (TON behavior) }{ { - name: "EVM_missing_registry", - selector: chainsel.ETHEREUM_TESTNET_SEPOLIA.Selector, - expectedError: "EVM registry is not available", + name: "EVM_missing_registry", + selector: chainsel.ETHEREUM_TESTNET_SEPOLIA.Selector, + expectedMsg: "EVM registry is not available", + wantErr: true, }, { - name: "Solana_missing_registry", - selector: chainsel.SOLANA_DEVNET.Selector, - expectedError: "failed to analyze solana transaction 0: solana decoder registry is not available", + name: "Solana_missing_registry", + selector: chainsel.SOLANA_DEVNET.Selector, + expectedMsg: "failed to analyze solana transaction 0: solana decoder registry is not available", + wantErr: true, }, { - name: "Aptos_unmarshal_additional_fields", - selector: chainsel.APTOS_TESTNET.Selector, - expectedError: "failed to unmarshal Aptos additional fields: unexpected end of JSON input", + name: "Aptos_unmarshal_additional_fields", + selector: chainsel.APTOS_TESTNET.Selector, + expectedMsg: "failed to unmarshal Aptos additional fields: unexpected end of JSON input", + wantErr: true, }, { - name: "Sui_unmarshal_additional_fields", - selector: chainsel.SUI_TESTNET.Selector, - expectedError: "failed to unmarshal Sui additional fields: unexpected end of JSON input", + name: "Sui_unmarshal_additional_fields", + selector: chainsel.SUI_TESTNET.Selector, + expectedMsg: "failed to unmarshal Sui additional fields: unexpected end of JSON input", + wantErr: true, + }, + { + name: "TON_decode_failure", + selector: chainsel.TON_TESTNET.Selector, + expectedMsg: "failed to decode TON transaction", + wantErr: false, // TON doesn't unmarshal AdditionalFields, so decode errors go to Method field }, } @@ -302,9 +329,16 @@ func TestBuildTimelockReport_FamilyBranches(t *testing.T) { }, } - _, err := BuildTimelockReport(t.Context(), proposalCtx, deployment.Environment{}, proposal) - require.Error(t, err) - require.Contains(t, err.Error(), tt.expectedError) + report, err := BuildTimelockReport(t.Context(), proposalCtx, deployment.Environment{}, proposal) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedMsg) + } else { + require.NoError(t, err) + for _, op := range report.Batches[0].Operations { + require.Contains(t, op.Calls[0].Method, tt.expectedMsg) + } + } }) } } diff --git a/experimental/analyzer/ton_analyzer.go b/experimental/analyzer/ton_analyzer.go index 93068197..02a50743 100644 --- a/experimental/analyzer/ton_analyzer.go +++ b/experimental/analyzer/ton_analyzer.go @@ -7,6 +7,7 @@ import ( "github.com/smartcontractkit/mcms/types" ) +// AnalyzeTONTransactions decodes a slice of TON transactions and returns their decoded representations. func AnalyzeTONTransactions(ctx ProposalContext, txs []types.Transaction) ([]*DecodedCall, error) { decodedTxs := make([]*DecodedCall, len(txs)) for i, op := range txs { @@ -20,11 +21,21 @@ func AnalyzeTONTransactions(ctx ProposalContext, txs []types.Transaction) ([]*De return decodedTxs, nil } +// AnalyzeTONTransaction decodes a single TON transaction using the MCMS TON decoder. +// +// Unlike Aptos/Sui analyzers, this function does not unmarshal AdditionalFields because +// the TON decoder only requires tx.Data (BOC cell) and tx.ContractType (metadata). +// AdditionalFields in TON is only used by the encoder/timelock_converter for the Value field. +// +// On decode failure, this function returns a DecodedCall with the error in the Method field +// instead of returning an error. This allows the proposal to continue processing even if +// a single transaction fails to decode. func AnalyzeTONTransaction(_ ProposalContext, mcmsTx types.Transaction) (*DecodedCall, error) { decoder := ton.NewDecoder() decodedOp, err := decoder.Decode(mcmsTx, mcmsTx.ContractType) if err != nil { - // Don't return an error to not block the whole proposal decoding because of a single transaction decode failure + // Don't return an error to not block the whole proposal decoding because of a single transaction decode failure. + // Instead, put the error message in the Method field so it's visible in the report. errStr := fmt.Errorf("failed to decode TON transaction: %w", err) return &DecodedCall{ @@ -32,6 +43,7 @@ func AnalyzeTONTransaction(_ ProposalContext, mcmsTx types.Transaction) (*Decode Method: errStr.Error(), }, nil } + namedArgs, err := toNamedFields(decodedOp) if err != nil { return nil, fmt.Errorf("failed to convert decoded operation to named arguments: %w", err) diff --git a/experimental/analyzer/ton_analyzer_test.go b/experimental/analyzer/ton_analyzer_test.go index 663a63e6..d59acad2 100644 --- a/experimental/analyzer/ton_analyzer_test.go +++ b/experimental/analyzer/ton_analyzer_test.go @@ -84,7 +84,7 @@ func makeInvalidTx(contractType string) types.Transaction { OperationMetadata: types.OperationMetadata{ContractType: contractType}, To: tonTestAddress, Data: []byte{0xFF, 0xFF}, - AdditionalFields: json.RawMessage(`{"value":"0"}`), + AdditionalFields: json.RawMessage(`{"value":0}`), } } @@ -117,7 +117,7 @@ func TestAnalyzeTONTransaction(t *testing.T) { OperationMetadata: types.OperationMetadata{ContractType: "unknown.type"}, To: tonTestAddress, Data: []byte{0x01, 0x02}, - AdditionalFields: json.RawMessage(`{"value":"0"}`), + AdditionalFields: json.RawMessage(`{"value":0}`), }, want: &DecodedCall{Address: tonTestAddress}, wantErrContain: "unknown contract interface: unknown.type", @@ -128,7 +128,7 @@ func TestAnalyzeTONTransaction(t *testing.T) { OperationMetadata: types.OperationMetadata{ContractType: "com.chainlink.ton.mcms.MCMS"}, To: tonTestAddress, Data: []byte{}, - AdditionalFields: json.RawMessage(`{"value":"0"}`), + AdditionalFields: json.RawMessage(`{"value":0}`), }, want: &DecodedCall{Address: tonTestAddress}, wantErrContain: "invalid cell BOC data", diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index 40b66488..4f13a37f 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -43,11 +43,7 @@ func UpfConvertTimelockProposal( if batch.Metadata == nil || batch.Metadata.DecodedCalldata == nil { continue } - if batch.Metadata.ContractType == "RBACTimelock" && - (batch.Metadata.DecodedCalldata.FunctionName == "function scheduleBatch((address,uint256,bytes)[] calls, bytes32 predecessor, bytes32 salt, uint256 delay) returns()" || - batch.Metadata.DecodedCalldata.FunctionName == "function bypasserExecuteBatch((address,uint256,bytes)[] calls) payable returns()" || - batch.Metadata.DecodedCalldata.FunctionName == "BypasserExecuteBatch" || - batch.Metadata.DecodedCalldata.FunctionName == "ScheduleBatch") { + if batch.Metadata.ContractType == "RBACTimelock" && isTimelockBatchFunction(batch.Metadata.DecodedCalldata.FunctionName) { batch.Metadata.DecodedCalldata.FunctionArgs["calls"] = decodedBatches[decodedBatchesIndex] decodedBatchesIndex++ } @@ -217,6 +213,33 @@ func asciiHash(data [32]byte) rawBytes { return rawBytes(sb.String()) } +// isTimelockBatchFunction checks if the function name corresponds to a timelock batch operation +// across different chain families (EVM, Solana, Sui, Aptos, TON). +func isTimelockBatchFunction(functionName string) bool { + // EVM function signatures + if functionName == "function scheduleBatch((address,uint256,bytes)[] calls, bytes32 predecessor, bytes32 salt, uint256 delay) returns()" || + functionName == "function bypasserExecuteBatch((address,uint256,bytes)[] calls) payable returns()" { + return true + } + + // Solana function names + if functionName == "ScheduleBatch" || functionName == "BypasserExecuteBatch" { + return true + } + + // Sui: mcms::timelock_schedule_batch, mcms::timelock_bypasser_execute_batch + // Aptos: package::module::timelock_schedule_batch, package::module::timelock_bypasser_execute_batch + // TON: ContractType::ScheduleBatch(0x...), ContractType::BypasserExecuteBatch(0x...) + if strings.Contains(functionName, "::timelock_schedule_batch") || + strings.Contains(functionName, "::timelock_bypasser_execute_batch") || + strings.Contains(functionName, "::ScheduleBatch(") || + strings.Contains(functionName, "::BypasserExecuteBatch(") { + return true + } + + return false +} + func calculateOpCount(opCount uint64, opIndex int, operations []mcmstypes.Operation) uint64 { chainSelector := operations[opIndex].ChainSelector for i, op := range operations { diff --git a/experimental/analyzer/upf/upf_test.go b/experimental/analyzer/upf/upf_test.go index 9953d8b2..4e227de3 100644 --- a/experimental/analyzer/upf/upf_test.go +++ b/experimental/analyzer/upf/upf_test.go @@ -2,16 +2,23 @@ package upf import ( "context" + "encoding/json" "fmt" + "math/big" "strings" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/google/go-cmp/cmp" "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_6_0/rmn_remote" rmnremotebindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/v0_1_0/rmn_remote" timelockbindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/v0_1_0/timelock" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" "github.com/stretchr/testify/require" + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/tvm/cell" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/mcms" @@ -19,6 +26,7 @@ import ( mcmsevmsdk "github.com/smartcontractkit/mcms/sdk/evm" mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana" mcmssuisdk "github.com/smartcontractkit/mcms/sdk/sui" + mcmstonsdk "github.com/smartcontractkit/mcms/sdk/ton" mcmstypes "github.com/smartcontractkit/mcms/types" "github.com/smartcontractkit/chainlink-deployments-framework/datastore" @@ -130,6 +138,8 @@ func convertTimelockProposal(ctx context.Context, t *testing.T, timelockProposal converter, err := mcmssuisdk.NewTimelockConverter() require.NoError(t, err) converters[chain] = converter + case chainsel.FamilyTon: + converters[chain] = mcmstonsdk.NewTimelockConverter() default: t.Fatalf("unsupported chain family %s", chainFamily) } @@ -595,6 +605,65 @@ signers: - "0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953" ` +// timelockProposalTon is generated using makeTONGrantRoleTx helper +var timelockProposalTon = func() string { + // Create a GrantRole transaction for the test + targetAddr := address.MustParseAddr("EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8") + exampleRole := crypto.Keccak256Hash([]byte("EXAMPLE_ROLE")) + exampleRoleBig, _ := cell.BeginCell(). + MustStoreBigInt(new(big.Int).SetBytes(exampleRole[:]), 257). + EndCell(). + ToBuilder(). + ToSlice(). + LoadBigInt(256) + + grantRoleData, _ := tlb.ToCell(rbac.GrantRole{ + QueryID: 1, + Role: exampleRoleBig, + Account: targetAddr, + }) + + tx, _ := mcmstonsdk.NewTransaction( + targetAddr, + grantRoleData.ToBuilder().ToSlice(), + big.NewInt(0), + "com.chainlink.ton.lib.access.RBAC", + []string{"grantRole"}, + ) + + // Marshal the transaction data + txData, _ := json.Marshal(tx) + var txMap map[string]interface{} + _ = json.Unmarshal(txData, &txMap) + + return fmt.Sprintf(`{ + "version": "v1", + "kind": "TimelockProposal", + "validUntil": 1999999999, + "signatures": [], + "overridePreviousRoot": false, + "chainMetadata": { + "1399300952838017768": { + "startingOpCount": 1, + "mcmAddress": "EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8", + "additionalFields": null + } + }, + "description": "simple TON proposal with GrantRole", + "action": "schedule", + "delay": "5m0s", + "timelockAddresses": { + "1399300952838017768": "EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8" + }, + "operations": [ + { + "chainSelector": 1399300952838017768, + "transactions": [%s] + } + ] +}`, string(txData)) +}() + func TestUpfConvertTimelockProposalWithSui(t *testing.T) { t.Parallel() ds := datastore.NewMemoryDataStore() @@ -661,6 +730,152 @@ func TestUpfConvertTimelockProposalWithSui(t *testing.T) { } } +func TestUpfConvertTimelockProposalWithTon(t *testing.T) { + t.Parallel() + ds := datastore.NewMemoryDataStore() + + // ---- TON: testnet + dsAddContract(t, ds, chainsel.TON_TESTNET.Selector, "EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8", "MCMS 1.0.0") + + env := deployment.Environment{ + DataStore: ds.Seal(), + ExistingAddresses: deployment.NewMemoryAddressBook(), + } + + proposalCtx, err := mcmsanalyzer.NewDefaultProposalContext(env) + require.NoError(t, err) + + tests := []struct { + name string + timelockProposal string + signers map[mcmstypes.ChainSelector][]common.Address + assertion func(*testing.T, string, error) + }{ + { + name: "TON proposal with GrantRole transaction", + timelockProposal: timelockProposalTon, + signers: map[mcmstypes.ChainSelector][]common.Address{ + mcmstypes.ChainSelector(chainsel.TON_TESTNET.Selector): { + common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), + }, + }, + assertion: func(t *testing.T, gotUpf string, err error) { + t.Helper() + require.NoError(t, err) + // Verify it contains TON-specific content + require.Contains(t, gotUpf, "chainFamily: ton") + require.Contains(t, gotUpf, "chainName: ton-testnet") + require.Contains(t, gotUpf, "msigAddress: EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8") + require.Contains(t, gotUpf, "contractType: RBACTimelock") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + timelockProposal, err := mcms.NewTimelockProposal(strings.NewReader(tt.timelockProposal)) + require.NoError(t, err) + mcmProposal := convertTimelockProposal(t.Context(), t, timelockProposal) + + got, err := UpfConvertTimelockProposal(t.Context(), proposalCtx, env, timelockProposal, mcmProposal, tt.signers) + + tt.assertion(t, got, err) + }) + } +} + +func TestIsTimelockBatchFunction(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + functionName string + want bool + }{ + // EVM + { + name: "EVM scheduleBatch", + functionName: "function scheduleBatch((address,uint256,bytes)[] calls, bytes32 predecessor, bytes32 salt, uint256 delay) returns()", + want: true, + }, + { + name: "EVM bypasserExecuteBatch", + functionName: "function bypasserExecuteBatch((address,uint256,bytes)[] calls) payable returns()", + want: true, + }, + // Solana + { + name: "Solana ScheduleBatch", + functionName: "ScheduleBatch", + want: true, + }, + { + name: "Solana BypasserExecuteBatch", + functionName: "BypasserExecuteBatch", + want: true, + }, + // Sui + { + name: "Sui timelock_schedule_batch", + functionName: "mcms::timelock_schedule_batch", + want: true, + }, + { + name: "Sui timelock_bypasser_execute_batch", + functionName: "mcms::timelock_bypasser_execute_batch", + want: true, + }, + // Aptos + { + name: "Aptos timelock_schedule_batch", + functionName: "package::module::timelock_schedule_batch", + want: true, + }, + { + name: "Aptos timelock_bypasser_execute_batch", + functionName: "package::module::timelock_bypasser_execute_batch", + want: true, + }, + // TON + { + name: "TON ScheduleBatch", + functionName: "com.chainlink.ton.mcms.RBACTimelock::ScheduleBatch(0x12345678)", + want: true, + }, + { + name: "TON BypasserExecuteBatch", + functionName: "com.chainlink.ton.mcms.RBACTimelock::BypasserExecuteBatch(0xabcdef)", + want: true, + }, + // Non-matching + { + name: "unrelated function", + functionName: "function transfer(address to, uint256 amount) returns(bool)", + want: false, + }, + { + name: "empty string", + functionName: "", + want: false, + }, + { + name: "partial match without colon", + functionName: "timelock_schedule_batch", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := isTimelockBatchFunction(tt.functionName) + require.Equal(t, tt.want, got) + }) + } +} + // ----- helpers ----- func dsAddContract(t *testing.T, ds datastore.MutableDataStore, chain uint64, addr, typeAndVersion string) { From c56332efcafa10d436b52b3be1eec01ec8e4e2c6 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Thu, 11 Dec 2025 23:15:54 -0500 Subject: [PATCH 11/22] fix lint --- experimental/analyzer/upf/upf_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/experimental/analyzer/upf/upf_test.go b/experimental/analyzer/upf/upf_test.go index 4e227de3..76f08423 100644 --- a/experimental/analyzer/upf/upf_test.go +++ b/experimental/analyzer/upf/upf_test.go @@ -632,7 +632,10 @@ var timelockProposalTon = func() string { ) // Marshal the transaction data - txData, _ := json.Marshal(tx) + txData, err := json.Marshal(tx) + if err != nil { + panic(fmt.Sprintf("failed to marshal transaction: %v", err)) + } var txMap map[string]interface{} _ = json.Unmarshal(txData, &txMap) From ccad05a17de9c2db49c2329f65fea7d2854fb6b7 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 15 Dec 2025 17:10:53 -0600 Subject: [PATCH 12/22] address comments --- experimental/analyzer/report_builder.go | 38 ++++++++-------------- experimental/analyzer/ton_analyzer.go | 12 ++++++- experimental/analyzer/ton_analyzer_test.go | 31 ++++-------------- experimental/analyzer/upf/upf.go | 19 +++-------- experimental/analyzer/upf/upf_test.go | 32 +++++++----------- go.mod | 8 ++--- go.sum | 24 +++++++------- 7 files changed, 61 insertions(+), 103 deletions(-) diff --git a/experimental/analyzer/report_builder.go b/experimental/analyzer/report_builder.go index 5cf8be1d..1c81ec11 100644 --- a/experimental/analyzer/report_builder.go +++ b/experimental/analyzer/report_builder.go @@ -25,39 +25,27 @@ func BuildProposalReport(ctx context.Context, proposalContext ProposalContext, e var calls []*DecodedCall switch family { case chainsel.FamilyEVM: - dec, err := AnalyzeEVMTransactions(ctx, proposalContext, env, chainSel, []types.Transaction{op.Transaction}) - if err != nil { - return nil, err - } - calls = dec + calls, err = AnalyzeEVMTransactions(ctx, proposalContext, env, chainSel, []types.Transaction{op.Transaction}) + case chainsel.FamilySolana: - dec, err := AnalyzeSolanaTransactions(proposalContext, chainSel, []types.Transaction{op.Transaction}) - if err != nil { - return nil, err - } - calls = dec + calls, err = AnalyzeSolanaTransactions(proposalContext, chainSel, []types.Transaction{op.Transaction}) + case chainsel.FamilyAptos: - dec, err := AnalyzeAptosTransactions(proposalContext, chainSel, []types.Transaction{op.Transaction}) - if err != nil { - return nil, err - } - calls = dec + calls, err = AnalyzeAptosTransactions(proposalContext, chainSel, []types.Transaction{op.Transaction}) + case chainsel.FamilySui: - dec, err := AnalyzeSuiTransactions(proposalContext, chainSel, []types.Transaction{op.Transaction}) - if err != nil { - return nil, err - } - calls = dec + calls, err = AnalyzeSuiTransactions(proposalContext, chainSel, []types.Transaction{op.Transaction}) + case chainsel.FamilyTon: - dec, err := AnalyzeTONTransactions(proposalContext, []types.Transaction{op.Transaction}) - if err != nil { - return nil, err - } - calls = dec + calls, err = AnalyzeTONTransactions(proposalContext, []types.Transaction{op.Transaction}) default: calls = []*DecodedCall{} } + if err != nil { + return nil, err + } + rpt.Operations[i] = OperationReport{ ChainSelector: chainSel, ChainName: chainNameOrUnknown(chainName), diff --git a/experimental/analyzer/ton_analyzer.go b/experimental/analyzer/ton_analyzer.go index 02a50743..9239e32d 100644 --- a/experimental/analyzer/ton_analyzer.go +++ b/experimental/analyzer/ton_analyzer.go @@ -3,10 +3,20 @@ package analyzer import ( "fmt" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/mcms" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/timelock" + "github.com/smartcontractkit/chainlink-ton/pkg/ton/debug/lib" "github.com/smartcontractkit/mcms/sdk/ton" "github.com/smartcontractkit/mcms/types" ) +var typeToTLBMap = map[string]lib.TLBMap{ + "com.chainlink.ton.lib.access.RBAC": rbac.TLBs, + "com.chainlink.ton.mcms.MCMS": mcms.TLBs, + "RBACTimelock": timelock.TLBs, +} + // AnalyzeTONTransactions decodes a slice of TON transactions and returns their decoded representations. func AnalyzeTONTransactions(ctx ProposalContext, txs []types.Transaction) ([]*DecodedCall, error) { decodedTxs := make([]*DecodedCall, len(txs)) @@ -31,7 +41,7 @@ func AnalyzeTONTransactions(ctx ProposalContext, txs []types.Transaction) ([]*De // instead of returning an error. This allows the proposal to continue processing even if // a single transaction fails to decode. func AnalyzeTONTransaction(_ ProposalContext, mcmsTx types.Transaction) (*DecodedCall, error) { - decoder := ton.NewDecoder() + decoder := ton.NewDecoder(typeToTLBMap) decodedOp, err := decoder.Decode(mcmsTx, mcmsTx.ContractType) if err != nil { // Don't return an error to not block the whole proposal decoding because of a single transaction decode failure. diff --git a/experimental/analyzer/ton_analyzer_test.go b/experimental/analyzer/ton_analyzer_test.go index d59acad2..89c21016 100644 --- a/experimental/analyzer/ton_analyzer_test.go +++ b/experimental/analyzer/ton_analyzer_test.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" + "github.com/smartcontractkit/chainlink-ton/pkg/ton/tlbe" "github.com/smartcontractkit/mcms/sdk/ton" "github.com/smartcontractkit/mcms/types" "github.com/stretchr/testify/require" @@ -46,7 +47,7 @@ func (s *testTONSetup) makeGrantRoleTx(t *testing.T, queryID uint64) types.Trans grantRoleData, err := tlb.ToCell(rbac.GrantRole{ QueryID: queryID, - Role: s.exampleRoleBig, + Role: tlbe.NewUint256(s.exampleRoleBig), Account: s.targetAddr, }) require.NoError(t, err) @@ -72,6 +73,7 @@ func (s *testTONSetup) expectedGrantRoleCall(queryID uint64) *DecodedCall { {Name: "Role", Value: SimpleField{Value: s.exampleRoleBig.String()}}, {Name: "Account", Value: SimpleField{Value: s.targetAddr.String()}}, }, + Outputs: []NamedField{}, } } @@ -151,7 +153,7 @@ func TestAnalyzeTONTransaction(t *testing.T) { return } - assertDecodedCallEqual(t, tt.want, result) + require.Equal(t, tt.want, result) }) } } @@ -232,7 +234,7 @@ func TestAnalyzeTONTransactions(t *testing.T) { continue } - assertDecodedCallEqual(t, tt.want[i], result) + require.Equal(t, tt.want[i], result) } }) } @@ -286,28 +288,7 @@ func TestAnalyzeTONTransactions_BatchOperations(t *testing.T) { require.Contains(t, result.Method, wantErrContains[i], "call %d", i) require.Nil(t, result.Inputs, "call %d", i) } else { - assertDecodedCallEqual(t, want[i], result) + require.Equal(t, want[i], result) } } } - -// assertDecodedCallEqual compares two DecodedCall structs for equality. -func assertDecodedCallEqual(t *testing.T, expected, actual *DecodedCall) { - t.Helper() - - require.Equal(t, expected.Method, actual.Method) - require.Len(t, actual.Inputs, len(expected.Inputs)) - - for j, input := range actual.Inputs { - expectedInput := expected.Inputs[j] - require.Equal(t, expectedInput.Name, input.Name, "input %d", j) - - expectedField, ok := expectedInput.Value.(SimpleField) - require.True(t, ok, "expected SimpleField for input %d", j) - - actualField, ok := input.Value.(SimpleField) - require.True(t, ok, "expected SimpleField but got %T for input %d", input.Value, j) - - require.Equal(t, expectedField.GetValue(), actualField.GetValue(), "input %d", j) - } -} diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index 4f13a37f..b3c29607 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -283,33 +283,18 @@ func batchOperationsToUpfDecodedCalls(ctx context.Context, proposalContext mcmsa switch family { case chainsel.FamilyEVM: describedTxs, err = mcmsanalyzer.AnalyzeEVMTransactions(ctx, proposalContext, env, chainSel, batch.Transactions) - if err != nil { - return nil, err - } case chainsel.FamilySolana: describedTxs, err = mcmsanalyzer.AnalyzeSolanaTransactions(proposalContext, chainSel, batch.Transactions) - if err != nil { - return nil, err - } case chainsel.FamilyAptos: describedTxs, err = mcmsanalyzer.AnalyzeAptosTransactions(proposalContext, chainSel, batch.Transactions) - if err != nil { - return nil, err - } case chainsel.FamilySui: describedTxs, err = mcmsanalyzer.AnalyzeSuiTransactions(proposalContext, chainSel, batch.Transactions) - if err != nil { - return nil, err - } case chainsel.FamilyTon: describedTxs, err = mcmsanalyzer.AnalyzeTONTransactions(proposalContext, batch.Transactions) - if err != nil { - return nil, err - } default: for callIdx, mcmsTx := range batch.Transactions { @@ -322,6 +307,10 @@ func batchOperationsToUpfDecodedCalls(ctx context.Context, proposalContext mcmsa continue } + if err != nil { + return nil, err + } + for callIdx, tx := range describedTxs { decodedCalls[batchIdx][callIdx] = &DecodedInnerCall{ To: tx.Address, diff --git a/experimental/analyzer/upf/upf_test.go b/experimental/analyzer/upf/upf_test.go index 76f08423..b1fe10ec 100644 --- a/experimental/analyzer/upf/upf_test.go +++ b/experimental/analyzer/upf/upf_test.go @@ -11,16 +11,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/google/go-cmp/cmp" + chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_6_0/rmn_remote" rmnremotebindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/v0_1_0/rmn_remote" timelockbindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/v0_1_0/timelock" "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" - "github.com/stretchr/testify/require" - "github.com/xssnick/tonutils-go/address" - "github.com/xssnick/tonutils-go/tlb" - "github.com/xssnick/tonutils-go/tvm/cell" - - chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-ton/pkg/ton/tlbe" "github.com/smartcontractkit/mcms" mcmssdk "github.com/smartcontractkit/mcms/sdk" mcmsevmsdk "github.com/smartcontractkit/mcms/sdk/evm" @@ -28,6 +24,9 @@ import ( mcmssuisdk "github.com/smartcontractkit/mcms/sdk/sui" mcmstonsdk "github.com/smartcontractkit/mcms/sdk/ton" mcmstypes "github.com/smartcontractkit/mcms/types" + "github.com/stretchr/testify/require" + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" "github.com/smartcontractkit/chainlink-deployments-framework/datastore" "github.com/smartcontractkit/chainlink-deployments-framework/deployment" @@ -605,21 +604,14 @@ signers: - "0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953" ` -// timelockProposalTon is generated using makeTONGrantRoleTx helper -var timelockProposalTon = func() string { +// timelockProposalTON is generated using makeTONGrantRoleTx helper +var timelockProposalTON = func(t *testing.T) string { // Create a GrantRole transaction for the test targetAddr := address.MustParseAddr("EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8") exampleRole := crypto.Keccak256Hash([]byte("EXAMPLE_ROLE")) - exampleRoleBig, _ := cell.BeginCell(). - MustStoreBigInt(new(big.Int).SetBytes(exampleRole[:]), 257). - EndCell(). - ToBuilder(). - ToSlice(). - LoadBigInt(256) - grantRoleData, _ := tlb.ToCell(rbac.GrantRole{ QueryID: 1, - Role: exampleRoleBig, + Role: tlbe.NewUint256(new(big.Int).SetBytes(exampleRole[:])), Account: targetAddr, }) @@ -633,9 +625,7 @@ var timelockProposalTon = func() string { // Marshal the transaction data txData, err := json.Marshal(tx) - if err != nil { - panic(fmt.Sprintf("failed to marshal transaction: %v", err)) - } + require.NoError(t, err) var txMap map[string]interface{} _ = json.Unmarshal(txData, &txMap) @@ -665,7 +655,7 @@ var timelockProposalTon = func() string { } ] }`, string(txData)) -}() +} func TestUpfConvertTimelockProposalWithSui(t *testing.T) { t.Parallel() @@ -756,7 +746,7 @@ func TestUpfConvertTimelockProposalWithTon(t *testing.T) { }{ { name: "TON proposal with GrantRole transaction", - timelockProposal: timelockProposalTon, + timelockProposal: timelockProposalTON(t), signers: map[mcmstypes.ChainSelector][]common.Address{ mcmstypes.ChainSelector(chainsel.TON_TESTNET.Selector): { common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), diff --git a/go.mod b/go.mod index 108851f0..9b340f0a 100644 --- a/go.mod +++ b/go.mod @@ -36,10 +36,11 @@ require ( github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4 github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 + github.com/smartcontractkit/chainlink-ton v0.0.0-20251215075716-5098cfecb0d2 github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d - github.com/smartcontractkit/mcms v0.31.2-0.20251209144844-5431a6ab1526 + github.com/smartcontractkit/mcms v0.31.2-0.20251215091707-34ad98fc250c github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 @@ -73,7 +74,6 @@ require ( github.com/smartcontractkit/chainlink-common v0.9.6-0.20251003171904-99a82a53b142 // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.4 // indirect github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250911124514-5874cc6d62b2 // indirect - github.com/smartcontractkit/chainlink-ton v0.0.0-20251209121704-8d997e4a1833 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) @@ -296,8 +296,8 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.12.0 // indirect diff --git a/go.sum b/go.sum index 76cbf3a2..e270b66d 100644 --- a/go.sum +++ b/go.sum @@ -712,8 +712,8 @@ github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1 h1:Ld3 github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1/go.mod h1:r6KXRM1u9ch5KFR2jspkgtyWEC1X+gxPCL8mR63U990= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 h1:ZJ/8Jx6Be5//TyjPi1pS1uotnmcYq5vVkSyISIymSj8= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2/go.mod h1:kHYJnZUqiPF7/xN5273prV+srrLJkS77GbBXHLKQpx0= -github.com/smartcontractkit/chainlink-ton v0.0.0-20251209121704-8d997e4a1833 h1:jy8TfjU+A1MFt70JRJ9GjTQshNlk/dBpRPzXmdHKDPk= -github.com/smartcontractkit/chainlink-ton v0.0.0-20251209121704-8d997e4a1833/go.mod h1:rxekiaWnJnFFfklae1OvO6T7xHJtsEldDvW/e5+b/qg= +github.com/smartcontractkit/chainlink-ton v0.0.0-20251215075716-5098cfecb0d2 h1:VdoPwJ/IkgsnulmIvpjosJzyz8mVYczLvHUFROEb110= +github.com/smartcontractkit/chainlink-ton v0.0.0-20251215075716-5098cfecb0d2/go.mod h1:z27AgU6fEXkkfmUAzcbEH9u3RKz2oaGy3isb7KK6Z2E= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 h1:7bxYNrPpygn8PUSBiEKn8riMd7CXMi/4bjTy0fHhcrY= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335/go.mod h1:ccjEgNeqOO+bjPddnL4lUrNLzyCvGCxgBjJdhFX3wa8= github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.4 h1:J4qtAo0ZmgX5pIr8Y5mdC+J2rj2e/6CTUC263t6mGOM= @@ -724,8 +724,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d h1:/0/80Ic6wpKH5F1nwDoRj9+70IxXunvCyNcCkA+9ik0= github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.31.2-0.20251209144844-5431a6ab1526 h1:IZ8SZA8bLPe0eQ14npcVuTmDV6BldY9aeHKzCviloL0= -github.com/smartcontractkit/mcms v0.31.2-0.20251209144844-5431a6ab1526/go.mod h1:ciwGs3cGDB5yCorkEWy8VSOmE89HZwuldz0tKfjeCwI= +github.com/smartcontractkit/mcms v0.31.2-0.20251215091707-34ad98fc250c h1:XZu4NWW3bWIRwyHaNNkV3rOlnjojnLv460SpdJ3raGY= +github.com/smartcontractkit/mcms v0.31.2-0.20251215091707-34ad98fc250c/go.mod h1:PO3JzoPMjQq8BTfsVGXpcmEY7rN5zqI+A5oeWvJLeEc= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= @@ -927,8 +927,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -970,8 +970,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1023,10 +1023,10 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU= -golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251208220230-2638a1023523 h1:H52Mhyrc44wBgLTGzq6+0cmuVuF3LURCSXsLMOqfFos= +golang.org/x/telemetry v0.0.0-20251208220230-2638a1023523/go.mod h1:ArQvPJS723nJQietgilmZA+shuB3CZxH1n2iXq9VSfs= 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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 6f521dc302e81fbb58ed82d1a024caf5f3037c34 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 15 Dec 2025 17:41:39 -0600 Subject: [PATCH 13/22] update test --- experimental/analyzer/ton_analyzer.go | 1 + experimental/analyzer/upf/upf_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/experimental/analyzer/ton_analyzer.go b/experimental/analyzer/ton_analyzer.go index 9239e32d..1bdd0cf7 100644 --- a/experimental/analyzer/ton_analyzer.go +++ b/experimental/analyzer/ton_analyzer.go @@ -14,6 +14,7 @@ import ( var typeToTLBMap = map[string]lib.TLBMap{ "com.chainlink.ton.lib.access.RBAC": rbac.TLBs, "com.chainlink.ton.mcms.MCMS": mcms.TLBs, + "com.chainlink.ton.mcms.Timelock": timelock.TLBs, "RBACTimelock": timelock.TLBs, } diff --git a/experimental/analyzer/upf/upf_test.go b/experimental/analyzer/upf/upf_test.go index b1fe10ec..92f4b675 100644 --- a/experimental/analyzer/upf/upf_test.go +++ b/experimental/analyzer/upf/upf_test.go @@ -606,6 +606,7 @@ signers: // timelockProposalTON is generated using makeTONGrantRoleTx helper var timelockProposalTON = func(t *testing.T) string { + t.Helper() // Create a GrantRole transaction for the test targetAddr := address.MustParseAddr("EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8") exampleRole := crypto.Keccak256Hash([]byte("EXAMPLE_ROLE")) From 933d0b847260482eea3f41f6c737fa99f51e520a Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Tue, 16 Dec 2025 15:16:13 -0600 Subject: [PATCH 14/22] bump mcms version --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 9b340f0a..00a9e561 100644 --- a/go.mod +++ b/go.mod @@ -36,11 +36,11 @@ require ( github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4 github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 - github.com/smartcontractkit/chainlink-ton v0.0.0-20251215075716-5098cfecb0d2 + github.com/smartcontractkit/chainlink-ton v0.0.0-20251216164546-5c01294e67f0 github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d - github.com/smartcontractkit/mcms v0.31.2-0.20251215091707-34ad98fc250c + github.com/smartcontractkit/mcms v0.31.2-0.20251216193316-af713b02477f github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 diff --git a/go.sum b/go.sum index e270b66d..8045f2d5 100644 --- a/go.sum +++ b/go.sum @@ -712,8 +712,8 @@ github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1 h1:Ld3 github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.1/go.mod h1:r6KXRM1u9ch5KFR2jspkgtyWEC1X+gxPCL8mR63U990= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 h1:ZJ/8Jx6Be5//TyjPi1pS1uotnmcYq5vVkSyISIymSj8= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2/go.mod h1:kHYJnZUqiPF7/xN5273prV+srrLJkS77GbBXHLKQpx0= -github.com/smartcontractkit/chainlink-ton v0.0.0-20251215075716-5098cfecb0d2 h1:VdoPwJ/IkgsnulmIvpjosJzyz8mVYczLvHUFROEb110= -github.com/smartcontractkit/chainlink-ton v0.0.0-20251215075716-5098cfecb0d2/go.mod h1:z27AgU6fEXkkfmUAzcbEH9u3RKz2oaGy3isb7KK6Z2E= +github.com/smartcontractkit/chainlink-ton v0.0.0-20251216164546-5c01294e67f0 h1:e8C47c6WC/BSFd/joI5kadkmA9twR5ArK7rKisQPZ+k= +github.com/smartcontractkit/chainlink-ton v0.0.0-20251216164546-5c01294e67f0/go.mod h1:z27AgU6fEXkkfmUAzcbEH9u3RKz2oaGy3isb7KK6Z2E= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 h1:7bxYNrPpygn8PUSBiEKn8riMd7CXMi/4bjTy0fHhcrY= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335/go.mod h1:ccjEgNeqOO+bjPddnL4lUrNLzyCvGCxgBjJdhFX3wa8= github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.4 h1:J4qtAo0ZmgX5pIr8Y5mdC+J2rj2e/6CTUC263t6mGOM= @@ -724,8 +724,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d h1:/0/80Ic6wpKH5F1nwDoRj9+70IxXunvCyNcCkA+9ik0= github.com/smartcontractkit/libocr v0.0.0-20250905115425-2785a5cee79d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.31.2-0.20251215091707-34ad98fc250c h1:XZu4NWW3bWIRwyHaNNkV3rOlnjojnLv460SpdJ3raGY= -github.com/smartcontractkit/mcms v0.31.2-0.20251215091707-34ad98fc250c/go.mod h1:PO3JzoPMjQq8BTfsVGXpcmEY7rN5zqI+A5oeWvJLeEc= +github.com/smartcontractkit/mcms v0.31.2-0.20251216193316-af713b02477f h1:l/xeu3HIq9+VbNWHkmyk7+f4n32SyASc3Q3hTMy/cdM= +github.com/smartcontractkit/mcms v0.31.2-0.20251216193316-af713b02477f/go.mod h1:qThfLkRhd+spHTt05bXWqrL48vZnw9EVG93ymhN2BEE= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= From ed21211b450843d322a9c47d42c1516f610a5457 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Tue, 16 Dec 2025 16:49:32 -0600 Subject: [PATCH 15/22] bump version --- experimental/analyzer/ton_analyzer_test.go | 24 +++++++++++----------- experimental/analyzer/upf/upf.go | 11 +++++++--- experimental/analyzer/upf/upf_test.go | 8 ++++---- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/experimental/analyzer/ton_analyzer_test.go b/experimental/analyzer/ton_analyzer_test.go index 89c21016..af2fc523 100644 --- a/experimental/analyzer/ton_analyzer_test.go +++ b/experimental/analyzer/ton_analyzer_test.go @@ -17,7 +17,7 @@ import ( "github.com/xssnick/tonutils-go/tvm/cell" ) -const tonTestAddress = "EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2" +const testAddress = "EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2" // testTONSetup contains common test fixtures for TON analyzer tests. type testTONSetup struct { @@ -84,7 +84,7 @@ func bigIntStr(v uint64) string { func makeInvalidTx(contractType string) types.Transaction { return types.Transaction{ OperationMetadata: types.OperationMetadata{ContractType: contractType}, - To: tonTestAddress, + To: testAddress, Data: []byte{0xFF, 0xFF}, AdditionalFields: json.RawMessage(`{"value":0}`), } @@ -110,29 +110,29 @@ func TestAnalyzeTONTransaction(t *testing.T) { { name: "invalid data", mcmsTx: makeInvalidTx("com.chainlink.ton.mcms.MCMS"), - want: &DecodedCall{Address: tonTestAddress}, + want: &DecodedCall{Address: testAddress}, wantErrContain: "invalid cell BOC data", }, { name: "unknown contract type", mcmsTx: types.Transaction{ OperationMetadata: types.OperationMetadata{ContractType: "unknown.type"}, - To: tonTestAddress, + To: testAddress, Data: []byte{0x01, 0x02}, AdditionalFields: json.RawMessage(`{"value":0}`), }, - want: &DecodedCall{Address: tonTestAddress}, + want: &DecodedCall{Address: testAddress}, wantErrContain: "unknown contract interface: unknown.type", }, { name: "empty data", mcmsTx: types.Transaction{ OperationMetadata: types.OperationMetadata{ContractType: "com.chainlink.ton.mcms.MCMS"}, - To: tonTestAddress, + To: testAddress, Data: []byte{}, AdditionalFields: json.RawMessage(`{"value":0}`), }, - want: &DecodedCall{Address: tonTestAddress}, + want: &DecodedCall{Address: testAddress}, wantErrContain: "invalid cell BOC data", }, } @@ -191,9 +191,9 @@ func TestAnalyzeTONTransactions(t *testing.T) { makeInvalidTx("com.chainlink.ton.mcms.Timelock"), }, want: []*DecodedCall{ - {Address: tonTestAddress}, + {Address: testAddress}, setup.expectedGrantRoleCall(1), - {Address: tonTestAddress}, + {Address: testAddress}, }, wantErrContains: []string{"invalid cell BOC data", "", "invalid cell BOC data"}, }, @@ -204,8 +204,8 @@ func TestAnalyzeTONTransactions(t *testing.T) { makeInvalidTx("com.chainlink.ton.mcms.Timelock"), }, want: []*DecodedCall{ - {Address: tonTestAddress}, - {Address: tonTestAddress}, + {Address: testAddress}, + {Address: testAddress}, }, wantErrContains: []string{"invalid cell BOC data", "invalid cell BOC data"}, }, @@ -268,7 +268,7 @@ func TestAnalyzeTONTransactions_BatchOperations(t *testing.T) { setup.expectedGrantRoleCall(1), setup.expectedGrantRoleCall(2), setup.expectedGrantRoleCall(3), - {Address: tonTestAddress}, + {Address: testAddress}, } wantErrContains := []string{"", "", "", "invalid cell BOC data"} diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index b3c29607..079f18ec 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -229,10 +229,15 @@ func isTimelockBatchFunction(functionName string) bool { // Sui: mcms::timelock_schedule_batch, mcms::timelock_bypasser_execute_batch // Aptos: package::module::timelock_schedule_batch, package::module::timelock_bypasser_execute_batch + // Use HasSuffix to prevent false positives like "::timelock_schedule_batch_helper" + if strings.HasSuffix(functionName, "::timelock_schedule_batch") || + strings.HasSuffix(functionName, "::timelock_bypasser_execute_batch") { + return true + } + // TON: ContractType::ScheduleBatch(0x...), ContractType::BypasserExecuteBatch(0x...) - if strings.Contains(functionName, "::timelock_schedule_batch") || - strings.Contains(functionName, "::timelock_bypasser_execute_batch") || - strings.Contains(functionName, "::ScheduleBatch(") || + // Use Contains because the opcode suffix (0x...) varies + if strings.Contains(functionName, "::ScheduleBatch(") || strings.Contains(functionName, "::BypasserExecuteBatch(") { return true } diff --git a/experimental/analyzer/upf/upf_test.go b/experimental/analyzer/upf/upf_test.go index 92f4b675..d1261a30 100644 --- a/experimental/analyzer/upf/upf_test.go +++ b/experimental/analyzer/upf/upf_test.go @@ -138,7 +138,7 @@ func convertTimelockProposal(ctx context.Context, t *testing.T, timelockProposal require.NoError(t, err) converters[chain] = converter case chainsel.FamilyTon: - converters[chain] = mcmstonsdk.NewTimelockConverter() + converters[chain] = mcmstonsdk.NewTimelockConverter(mcmstonsdk.DefaultSendAmount) default: t.Fatalf("unsupported chain family %s", chainFamily) } @@ -604,8 +604,8 @@ signers: - "0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953" ` -// timelockProposalTON is generated using makeTONGrantRoleTx helper -var timelockProposalTON = func(t *testing.T) string { +// timelockProposalTon is generated using makeTONGrantRoleTx helper +var timelockProposalTon = func(t *testing.T) string { t.Helper() // Create a GrantRole transaction for the test targetAddr := address.MustParseAddr("EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8") @@ -747,7 +747,7 @@ func TestUpfConvertTimelockProposalWithTon(t *testing.T) { }{ { name: "TON proposal with GrantRole transaction", - timelockProposal: timelockProposalTON(t), + timelockProposal: timelockProposalTon(t), signers: map[mcmstypes.ChainSelector][]common.Address{ mcmstypes.ChainSelector(chainsel.TON_TESTNET.Selector): { common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), From 163c1f02b426e9eecaf1bee438f47daa92508a37 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Sun, 21 Dec 2025 11:13:33 -0600 Subject: [PATCH 16/22] address comments --- experimental/analyzer/sui_analyzer.go | 6 +- experimental/analyzer/sui_analyzer_test.go | 9 +- experimental/analyzer/ton_analyzer.go | 8 +- experimental/analyzer/ton_analyzer_test.go | 177 ++++++--------- experimental/analyzer/upf/upf.go | 20 +- experimental/analyzer/upf/upf_test.go | 246 ++++++++++----------- 6 files changed, 216 insertions(+), 250 deletions(-) diff --git a/experimental/analyzer/sui_analyzer.go b/experimental/analyzer/sui_analyzer.go index 3f48d13d..9b53e6f6 100644 --- a/experimental/analyzer/sui_analyzer.go +++ b/experimental/analyzer/sui_analyzer.go @@ -10,9 +10,10 @@ import ( ) func AnalyzeSuiTransactions(ctx ProposalContext, chainSelector uint64, txs []types.Transaction) ([]*DecodedCall, error) { + decoder := mcmssuisdk.NewDecoder() decodedTxs := make([]*DecodedCall, len(txs)) for i, op := range txs { - analyzedTransaction, err := AnalyzeSuiTransaction(ctx, chainSelector, op) + analyzedTransaction, err := AnalyzeSuiTransaction(ctx, decoder, chainSelector, op) if err != nil { return nil, fmt.Errorf("failed to analyze Sui transaction %d: %w", i, err) } @@ -22,8 +23,7 @@ func AnalyzeSuiTransactions(ctx ProposalContext, chainSelector uint64, txs []typ return decodedTxs, nil } -func AnalyzeSuiTransaction(_ ProposalContext, chainSelector uint64, mcmsTx types.Transaction) (*DecodedCall, error) { - decoder := mcmssuisdk.NewDecoder() +func AnalyzeSuiTransaction(ctx ProposalContext, decoder *mcmssuisdk.Decoder, chainSelector uint64, mcmsTx types.Transaction) (*DecodedCall, error) { var additionalFields mcmssuisdk.AdditionalFields if err := json.Unmarshal(mcmsTx.AdditionalFields, &additionalFields); err != nil { return nil, fmt.Errorf("failed to unmarshal Sui additional fields: %w", err) diff --git a/experimental/analyzer/sui_analyzer_test.go b/experimental/analyzer/sui_analyzer_test.go index 8fd84f72..953fe65f 100644 --- a/experimental/analyzer/sui_analyzer_test.go +++ b/experimental/analyzer/sui_analyzer_test.go @@ -5,6 +5,7 @@ import ( "testing" chainsel "github.com/smartcontractkit/chain-selectors" + mcmssuisdk "github.com/smartcontractkit/mcms/sdk/sui" "github.com/smartcontractkit/mcms/types" "github.com/stretchr/testify/require" @@ -24,6 +25,7 @@ func TestAnalyzeSuiTransactions(t *testing.T) { }, } + decoder := mcmssuisdk.NewDecoder() chainSelector := chainsel.SUI_TESTNET.Selector tests := []struct { @@ -117,7 +119,7 @@ func TestAnalyzeSuiTransactions(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - result, err := AnalyzeSuiTransaction(defaultProposalCtx, chainSelector, tt.mcmsTx) + result, err := AnalyzeSuiTransaction(defaultProposalCtx, decoder, chainSelector, tt.mcmsTx) if tt.wantErr { require.Error(t, err, "AnalyzeSuiTransaction() should have failed") @@ -170,7 +172,8 @@ func TestAnalyzeSuiTransactionWithErrors(t *testing.T) { }, }, } - + + decoder := mcmssuisdk.NewDecoder() chainSelector := chainsel.SUI_TESTNET.Selector tests := []struct { @@ -248,7 +251,7 @@ func TestAnalyzeSuiTransactionWithErrors(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - result, err := AnalyzeSuiTransaction(defaultProposalCtx, chainSelector, tt.mcmsTx) + result, err := AnalyzeSuiTransaction(defaultProposalCtx, decoder, chainSelector, tt.mcmsTx) if tt.wantError { require.Error(t, err, "AnalyzeSuiTransaction() should have failed") diff --git a/experimental/analyzer/ton_analyzer.go b/experimental/analyzer/ton_analyzer.go index 1bdd0cf7..5334b20c 100644 --- a/experimental/analyzer/ton_analyzer.go +++ b/experimental/analyzer/ton_analyzer.go @@ -7,10 +7,12 @@ import ( "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/mcms" "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/timelock" "github.com/smartcontractkit/chainlink-ton/pkg/ton/debug/lib" + "github.com/smartcontractkit/mcms/sdk" "github.com/smartcontractkit/mcms/sdk/ton" "github.com/smartcontractkit/mcms/types" ) +// TODO should imported from sdk var typeToTLBMap = map[string]lib.TLBMap{ "com.chainlink.ton.lib.access.RBAC": rbac.TLBs, "com.chainlink.ton.mcms.MCMS": mcms.TLBs, @@ -20,9 +22,10 @@ var typeToTLBMap = map[string]lib.TLBMap{ // AnalyzeTONTransactions decodes a slice of TON transactions and returns their decoded representations. func AnalyzeTONTransactions(ctx ProposalContext, txs []types.Transaction) ([]*DecodedCall, error) { + decoder := ton.NewDecoder(typeToTLBMap) decodedTxs := make([]*DecodedCall, len(txs)) for i, op := range txs { - analyzedTransaction, err := AnalyzeTONTransaction(ctx, op) + analyzedTransaction, err := AnalyzeTONTransaction(ctx, decoder, op) if err != nil { return nil, fmt.Errorf("failed to analyze TON transaction %d: %w", i, err) } @@ -41,8 +44,7 @@ func AnalyzeTONTransactions(ctx ProposalContext, txs []types.Transaction) ([]*De // On decode failure, this function returns a DecodedCall with the error in the Method field // instead of returning an error. This allows the proposal to continue processing even if // a single transaction fails to decode. -func AnalyzeTONTransaction(_ ProposalContext, mcmsTx types.Transaction) (*DecodedCall, error) { - decoder := ton.NewDecoder(typeToTLBMap) +func AnalyzeTONTransaction(_ ProposalContext, decoder sdk.Decoder, mcmsTx types.Transaction) (*DecodedCall, error) { decodedOp, err := decoder.Decode(mcmsTx, mcmsTx.ContractType) if err != nil { // Don't return an error to not block the whole proposal decoding because of a single transaction decode failure. diff --git a/experimental/analyzer/ton_analyzer_test.go b/experimental/analyzer/ton_analyzer_test.go index af2fc523..c704330c 100644 --- a/experimental/analyzer/ton_analyzer_test.go +++ b/experimental/analyzer/ton_analyzer_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/ethereum/go-ethereum/crypto" - chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" "github.com/smartcontractkit/chainlink-ton/pkg/ton/tlbe" "github.com/smartcontractkit/mcms/sdk/ton" @@ -19,82 +18,12 @@ import ( const testAddress = "EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2" -// testTONSetup contains common test fixtures for TON analyzer tests. -type testTONSetup struct { - targetAddr *address.Address - exampleRoleBig *big.Int -} - -func newTestTONSetup(t *testing.T) *testTONSetup { - t.Helper() - - exampleRole := crypto.Keccak256Hash([]byte("EXAMPLE_ROLE")) - exampleRoleBig, _ := cell.BeginCell(). - MustStoreBigInt(new(big.Int).SetBytes(exampleRole[:]), 257). - EndCell(). - ToBuilder(). - ToSlice(). - LoadBigInt(256) - - return &testTONSetup{ - targetAddr: address.MustParseAddr("EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8"), - exampleRoleBig: exampleRoleBig, - } -} - -func (s *testTONSetup) makeGrantRoleTx(t *testing.T, queryID uint64) types.Transaction { - t.Helper() - - grantRoleData, err := tlb.ToCell(rbac.GrantRole{ - QueryID: queryID, - Role: tlbe.NewUint256(s.exampleRoleBig), - Account: s.targetAddr, - }) - require.NoError(t, err) - - tx, err := ton.NewTransaction( - s.targetAddr, - grantRoleData.ToBuilder().ToSlice(), - big.NewInt(0), - "com.chainlink.ton.lib.access.RBAC", - []string{"grantRole"}, - ) - require.NoError(t, err) - - return tx -} - -func (s *testTONSetup) expectedGrantRoleCall(queryID uint64) *DecodedCall { - return &DecodedCall{ - Address: s.targetAddr.String(), - Method: "com.chainlink.ton.lib.access.RBAC::GrantRole(0x0)", - Inputs: []NamedField{ - {Name: "QueryID", Value: SimpleField{Value: bigIntStr(queryID)}}, - {Name: "Role", Value: SimpleField{Value: s.exampleRoleBig.String()}}, - {Name: "Account", Value: SimpleField{Value: s.targetAddr.String()}}, - }, - Outputs: []NamedField{}, - } -} - -func bigIntStr(v uint64) string { - return new(big.Int).SetUint64(v).String() -} - -func makeInvalidTx(contractType string) types.Transaction { - return types.Transaction{ - OperationMetadata: types.OperationMetadata{ContractType: contractType}, - To: testAddress, - Data: []byte{0xFF, 0xFF}, - AdditionalFields: json.RawMessage(`{"value":0}`), - } -} - func TestAnalyzeTONTransaction(t *testing.T) { t.Parallel() setup := newTestTONSetup(t) ctx := &DefaultProposalContext{} + deocder := ton.NewDecoder(typeToTLBMap) tests := []struct { name string @@ -141,7 +70,7 @@ func TestAnalyzeTONTransaction(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - result, err := AnalyzeTONTransaction(ctx, tt.mcmsTx) + result, err := AnalyzeTONTransaction(ctx, deocder, tt.mcmsTx) require.NoError(t, err) require.NotNil(t, result) require.Equal(t, tt.want.Address, result.Address) @@ -158,6 +87,12 @@ func TestAnalyzeTONTransaction(t *testing.T) { } } +// testTONSetup contains common test fixtures for TON analyzer tests. +type testTONSetup struct { + targetAddr *address.Address + exampleRoleBig *big.Int +} + func TestAnalyzeTONTransactions(t *testing.T) { t.Parallel() @@ -240,55 +175,67 @@ func TestAnalyzeTONTransactions(t *testing.T) { } } -func TestAnalyzeTONTransactions_BatchOperations(t *testing.T) { - t.Parallel() +func newTestTONSetup(t *testing.T) *testTONSetup { + t.Helper() - setup := newTestTONSetup(t) - ctx := &DefaultProposalContext{} - chainSelector := chainsel.TON_TESTNET.Selector + exampleRole := crypto.Keccak256Hash([]byte("EXAMPLE_ROLE")) + exampleRoleBig, _ := cell.BeginCell(). + MustStoreBigInt(new(big.Int).SetBytes(exampleRole[:]), 257). + EndCell(). + ToBuilder(). + ToSlice(). + LoadBigInt(256) - batchOps := []types.BatchOperation{ - { - ChainSelector: types.ChainSelector(chainSelector), - Transactions: []types.Transaction{ - setup.makeGrantRoleTx(t, 1), - setup.makeGrantRoleTx(t, 2), - }, - }, - { - ChainSelector: types.ChainSelector(chainSelector), - Transactions: []types.Transaction{ - setup.makeGrantRoleTx(t, 3), - makeInvalidTx("com.chainlink.ton.lib.access.RBAC"), - }, - }, + return &testTONSetup{ + targetAddr: address.MustParseAddr("EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8"), + exampleRoleBig: exampleRoleBig, } +} - want := []*DecodedCall{ - setup.expectedGrantRoleCall(1), - setup.expectedGrantRoleCall(2), - setup.expectedGrantRoleCall(3), - {Address: testAddress}, - } - wantErrContains := []string{"", "", "", "invalid cell BOC data"} +func (s *testTONSetup) makeGrantRoleTx(t *testing.T, queryID uint64) types.Transaction { + t.Helper() - var allResults []*DecodedCall - for _, batch := range batchOps { - results, err := AnalyzeTONTransactions(ctx, batch.Transactions) - require.NoError(t, err) - allResults = append(allResults, results...) - } + grantRoleData, err := tlb.ToCell(rbac.GrantRole{ + QueryID: queryID, + Role: tlbe.NewUint256(s.exampleRoleBig), + Account: s.targetAddr, + }) + require.NoError(t, err) + + tx, err := ton.NewTransaction( + s.targetAddr, + grantRoleData.ToBuilder().ToSlice(), + big.NewInt(0), + "com.chainlink.ton.lib.access.RBAC", + []string{"grantRole"}, + ) + require.NoError(t, err) - require.Len(t, allResults, len(want)) + return tx +} + +func (s *testTONSetup) expectedGrantRoleCall(queryID uint64) *DecodedCall { + return &DecodedCall{ + Address: s.targetAddr.String(), + Method: "com.chainlink.ton.lib.access.RBAC::GrantRole(0x0)", + Inputs: []NamedField{ + {Name: "QueryID", Value: SimpleField{Value: bigIntStr(queryID)}}, + {Name: "Role", Value: SimpleField{Value: s.exampleRoleBig.String()}}, + {Name: "Account", Value: SimpleField{Value: s.targetAddr.String()}}, + }, + Outputs: []NamedField{}, + } +} - for i, result := range allResults { - require.Equal(t, want[i].Address, result.Address, "call %d", i) +func bigIntStr(v uint64) string { + return new(big.Int).SetUint64(v).String() +} - if wantErrContains[i] != "" { - require.Contains(t, result.Method, wantErrContains[i], "call %d", i) - require.Nil(t, result.Inputs, "call %d", i) - } else { - require.Equal(t, want[i], result) - } +func makeInvalidTx(contractType string) types.Transaction { + return types.Transaction{ + OperationMetadata: types.OperationMetadata{ContractType: contractType}, + To: testAddress, + Data: []byte{0xFF, 0xFF}, + AdditionalFields: json.RawMessage(`{"value":0}`), } } diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index 079f18ec..477531ff 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -11,14 +11,28 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/goccy/go-yaml" chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" + tonmcms "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/mcms" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/timelock" + "github.com/smartcontractkit/chainlink-ton/pkg/ton/debug/lib" "github.com/smartcontractkit/mcms" mcmsaptossdk "github.com/smartcontractkit/mcms/sdk/aptos" + mcmssuisdk "github.com/smartcontractkit/mcms/sdk/sui" + mcmstonsdk "github.com/smartcontractkit/mcms/sdk/ton" mcmstypes "github.com/smartcontractkit/mcms/types" "github.com/smartcontractkit/chainlink-deployments-framework/deployment" mcmsanalyzer "github.com/smartcontractkit/chainlink-deployments-framework/experimental/analyzer" ) +// TODO should imported from sdk +var typeToTLBMap = map[string]lib.TLBMap{ + "com.chainlink.ton.lib.access.RBAC": rbac.TLBs, + "com.chainlink.ton.mcms.MCMS": tonmcms.TLBs, + "com.chainlink.ton.mcms.Timelock": timelock.TLBs, + "RBACTimelock": timelock.TLBs, +} + // UpfConvertTimelockProposal converts a TimelockProposal to a UPF proposal format. func UpfConvertTimelockProposal( ctx context.Context, @@ -374,7 +388,8 @@ func analyzeTransaction( return analyzeResult, "", nil case chainsel.FamilySui: - analyzeResult, err := mcmsanalyzer.AnalyzeSuiTransaction(proposalCtx, uint64(mcmsOp.ChainSelector), mcmsOp.Transaction) + decoder := mcmssuisdk.NewDecoder() + analyzeResult, err := mcmsanalyzer.AnalyzeSuiTransaction(proposalCtx, decoder, uint64(mcmsOp.ChainSelector), mcmsOp.Transaction) if err != nil { return nil, "", err } @@ -382,7 +397,8 @@ func analyzeTransaction( return analyzeResult, "", nil case chainsel.FamilyTon: - analyzeResult, err := mcmsanalyzer.AnalyzeTONTransaction(proposalCtx, mcmsOp.Transaction) + decoder := mcmstonsdk.NewDecoder(typeToTLBMap) + analyzeResult, err := mcmsanalyzer.AnalyzeTONTransaction(proposalCtx, decoder, mcmsOp.Transaction) if err != nil { return nil, "", err } diff --git a/experimental/analyzer/upf/upf_test.go b/experimental/analyzer/upf/upf_test.go index d1261a30..8bb32ec9 100644 --- a/experimental/analyzer/upf/upf_test.go +++ b/experimental/analyzer/upf/upf_test.go @@ -118,6 +118,128 @@ func TestUpfConvertTimelockProposal(t *testing.T) { } } +func TestUpfConvertTimelockProposalWithSui(t *testing.T) { + t.Parallel() + ds := datastore.NewMemoryDataStore() + + // ---- Sui: testnet + dsAddContract(t, ds, chainsel.SUI_TESTNET.Selector, "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d", "MCMSUser 1.0.0") + + env := deployment.Environment{ + DataStore: ds.Seal(), + ExistingAddresses: deployment.NewMemoryAddressBook(), + } + + proposalCtx, err := mcmsanalyzer.NewDefaultProposalContext(env) + require.NoError(t, err) + + tests := []struct { + name string + timelockProposal string + signers map[mcmstypes.ChainSelector][]common.Address + assertion func(*testing.T, string, error) + }{ + { + name: "Sui proposal with valid transaction", + timelockProposal: timelockProposalSui, + signers: map[mcmstypes.ChainSelector][]common.Address{ + mcmstypes.ChainSelector(chainsel.SUI_TESTNET.Selector): { + common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), + }, + }, + assertion: func(t *testing.T, gotUpf string, err error) { + t.Helper() + require.NoError(t, err) + require.Equal(t, upfProposalSui, gotUpf) + }, + }, + { + name: "Sui proposal with unknown module", + timelockProposal: timelockProposalSuiUnknownModule, + signers: map[mcmstypes.ChainSelector][]common.Address{ + mcmstypes.ChainSelector(chainsel.SUI_TESTNET.Selector): { + common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), + }, + }, + assertion: func(t *testing.T, gotUpf string, err error) { + t.Helper() + require.NoError(t, err) + require.Equal(t, upfProposalSuiUnknownModule, gotUpf) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + timelockProposal, err := mcms.NewTimelockProposal(strings.NewReader(tt.timelockProposal)) + require.NoError(t, err) + mcmProposal := convertTimelockProposal(t.Context(), t, timelockProposal) + + got, err := UpfConvertTimelockProposal(t.Context(), proposalCtx, env, timelockProposal, mcmProposal, tt.signers) + + tt.assertion(t, got, err) + }) + } +} + +func TestUpfConvertTimelockProposalWithTon(t *testing.T) { + t.Parallel() + ds := datastore.NewMemoryDataStore() + + // ---- TON: testnet + dsAddContract(t, ds, chainsel.TON_TESTNET.Selector, "EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8", "MCMS 1.0.0") + + env := deployment.Environment{ + DataStore: ds.Seal(), + ExistingAddresses: deployment.NewMemoryAddressBook(), + } + + proposalCtx, err := mcmsanalyzer.NewDefaultProposalContext(env) + require.NoError(t, err) + + tests := []struct { + name string + timelockProposal string + signers map[mcmstypes.ChainSelector][]common.Address + assertion func(*testing.T, string, error) + }{ + { + name: "TON proposal with GrantRole transaction", + timelockProposal: timelockProposalTon(t), + signers: map[mcmstypes.ChainSelector][]common.Address{ + mcmstypes.ChainSelector(chainsel.TON_TESTNET.Selector): { + common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), + }, + }, + assertion: func(t *testing.T, gotUpf string, err error) { + t.Helper() + require.NoError(t, err) + // Verify it contains TON-specific content + require.Contains(t, gotUpf, "chainFamily: ton") + require.Contains(t, gotUpf, "chainName: ton-testnet") + require.Contains(t, gotUpf, "msigAddress: EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8") + require.Contains(t, gotUpf, "contractType: RBACTimelock") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + timelockProposal, err := mcms.NewTimelockProposal(strings.NewReader(tt.timelockProposal)) + require.NoError(t, err) + mcmProposal := convertTimelockProposal(t.Context(), t, timelockProposal) + + got, err := UpfConvertTimelockProposal(t.Context(), proposalCtx, env, timelockProposal, mcmProposal, tt.signers) + + tt.assertion(t, got, err) + }) + } +} + // ----- helpers ----- func convertTimelockProposal(ctx context.Context, t *testing.T, timelockProposal *mcms.TimelockProposal) *mcms.Proposal { @@ -627,8 +749,6 @@ var timelockProposalTon = func(t *testing.T) string { // Marshal the transaction data txData, err := json.Marshal(tx) require.NoError(t, err) - var txMap map[string]interface{} - _ = json.Unmarshal(txData, &txMap) return fmt.Sprintf(`{ "version": "v1", @@ -658,128 +778,6 @@ var timelockProposalTon = func(t *testing.T) string { }`, string(txData)) } -func TestUpfConvertTimelockProposalWithSui(t *testing.T) { - t.Parallel() - ds := datastore.NewMemoryDataStore() - - // ---- Sui: testnet - dsAddContract(t, ds, chainsel.SUI_TESTNET.Selector, "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d", "MCMSUser 1.0.0") - - env := deployment.Environment{ - DataStore: ds.Seal(), - ExistingAddresses: deployment.NewMemoryAddressBook(), - } - - proposalCtx, err := mcmsanalyzer.NewDefaultProposalContext(env) - require.NoError(t, err) - - tests := []struct { - name string - timelockProposal string - signers map[mcmstypes.ChainSelector][]common.Address - assertion func(*testing.T, string, error) - }{ - { - name: "Sui proposal with valid transaction", - timelockProposal: timelockProposalSui, - signers: map[mcmstypes.ChainSelector][]common.Address{ - mcmstypes.ChainSelector(chainsel.SUI_TESTNET.Selector): { - common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), - }, - }, - assertion: func(t *testing.T, gotUpf string, err error) { - t.Helper() - require.NoError(t, err) - require.Equal(t, upfProposalSui, gotUpf) - }, - }, - { - name: "Sui proposal with unknown module", - timelockProposal: timelockProposalSuiUnknownModule, - signers: map[mcmstypes.ChainSelector][]common.Address{ - mcmstypes.ChainSelector(chainsel.SUI_TESTNET.Selector): { - common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), - }, - }, - assertion: func(t *testing.T, gotUpf string, err error) { - t.Helper() - require.NoError(t, err) - require.Equal(t, upfProposalSuiUnknownModule, gotUpf) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - timelockProposal, err := mcms.NewTimelockProposal(strings.NewReader(tt.timelockProposal)) - require.NoError(t, err) - mcmProposal := convertTimelockProposal(t.Context(), t, timelockProposal) - - got, err := UpfConvertTimelockProposal(t.Context(), proposalCtx, env, timelockProposal, mcmProposal, tt.signers) - - tt.assertion(t, got, err) - }) - } -} - -func TestUpfConvertTimelockProposalWithTon(t *testing.T) { - t.Parallel() - ds := datastore.NewMemoryDataStore() - - // ---- TON: testnet - dsAddContract(t, ds, chainsel.TON_TESTNET.Selector, "EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8", "MCMS 1.0.0") - - env := deployment.Environment{ - DataStore: ds.Seal(), - ExistingAddresses: deployment.NewMemoryAddressBook(), - } - - proposalCtx, err := mcmsanalyzer.NewDefaultProposalContext(env) - require.NoError(t, err) - - tests := []struct { - name string - timelockProposal string - signers map[mcmstypes.ChainSelector][]common.Address - assertion func(*testing.T, string, error) - }{ - { - name: "TON proposal with GrantRole transaction", - timelockProposal: timelockProposalTon(t), - signers: map[mcmstypes.ChainSelector][]common.Address{ - mcmstypes.ChainSelector(chainsel.TON_TESTNET.Selector): { - common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), - }, - }, - assertion: func(t *testing.T, gotUpf string, err error) { - t.Helper() - require.NoError(t, err) - // Verify it contains TON-specific content - require.Contains(t, gotUpf, "chainFamily: ton") - require.Contains(t, gotUpf, "chainName: ton-testnet") - require.Contains(t, gotUpf, "msigAddress: EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8") - require.Contains(t, gotUpf, "contractType: RBACTimelock") - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - timelockProposal, err := mcms.NewTimelockProposal(strings.NewReader(tt.timelockProposal)) - require.NoError(t, err) - mcmProposal := convertTimelockProposal(t.Context(), t, timelockProposal) - - got, err := UpfConvertTimelockProposal(t.Context(), proposalCtx, env, timelockProposal, mcmProposal, tt.signers) - - tt.assertion(t, got, err) - }) - } -} - func TestIsTimelockBatchFunction(t *testing.T) { t.Parallel() From 324486c69d645e386d56ceefb4bc2cd6b2802e03 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Sun, 21 Dec 2025 11:23:16 -0600 Subject: [PATCH 17/22] fix lint and changeset --- .changeset/khaki-geese-poke.md | 5 +++++ experimental/analyzer/sui_analyzer_test.go | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/khaki-geese-poke.md diff --git a/.changeset/khaki-geese-poke.md b/.changeset/khaki-geese-poke.md new file mode 100644 index 00000000..e3e1b7f9 --- /dev/null +++ b/.changeset/khaki-geese-poke.md @@ -0,0 +1,5 @@ +--- +"chainlink-deployments-framework": minor +--- + +Adds TON blockchain analyzer support diff --git a/experimental/analyzer/sui_analyzer_test.go b/experimental/analyzer/sui_analyzer_test.go index 953fe65f..ead38276 100644 --- a/experimental/analyzer/sui_analyzer_test.go +++ b/experimental/analyzer/sui_analyzer_test.go @@ -4,10 +4,11 @@ import ( "encoding/json" "testing" + "github.com/stretchr/testify/require" + chainsel "github.com/smartcontractkit/chain-selectors" mcmssuisdk "github.com/smartcontractkit/mcms/sdk/sui" "github.com/smartcontractkit/mcms/types" - "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-deployments-framework/deployment" ) @@ -172,7 +173,7 @@ func TestAnalyzeSuiTransactionWithErrors(t *testing.T) { }, }, } - + decoder := mcmssuisdk.NewDecoder() chainSelector := chainsel.SUI_TESTNET.Selector From a06f6ebb643e2b1e4fdc517cf342572f8baf55a6 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Sun, 21 Dec 2025 11:25:04 -0600 Subject: [PATCH 18/22] typo --- experimental/analyzer/ton_analyzer_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experimental/analyzer/ton_analyzer_test.go b/experimental/analyzer/ton_analyzer_test.go index c704330c..e86ac5f7 100644 --- a/experimental/analyzer/ton_analyzer_test.go +++ b/experimental/analyzer/ton_analyzer_test.go @@ -23,7 +23,7 @@ func TestAnalyzeTONTransaction(t *testing.T) { setup := newTestTONSetup(t) ctx := &DefaultProposalContext{} - deocder := ton.NewDecoder(typeToTLBMap) + decoder := ton.NewDecoder(typeToTLBMap) tests := []struct { name string @@ -70,7 +70,7 @@ func TestAnalyzeTONTransaction(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - result, err := AnalyzeTONTransaction(ctx, deocder, tt.mcmsTx) + result, err := AnalyzeTONTransaction(ctx, decoder, tt.mcmsTx) require.NoError(t, err) require.NotNil(t, result) require.Equal(t, tt.want.Address, result.Address) From 4d80162f6e331a0a999f90db86f780c950c5d741 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Wed, 21 Jan 2026 20:35:23 -0600 Subject: [PATCH 19/22] fix go mod and update registry --- experimental/analyzer/ton_analyzer.go | 15 ++------------- experimental/analyzer/ton_analyzer_test.go | 3 ++- experimental/analyzer/upf/upf.go | 15 ++------------- go.mod | 3 +-- go.sum | 2 ++ 5 files changed, 9 insertions(+), 29 deletions(-) diff --git a/experimental/analyzer/ton_analyzer.go b/experimental/analyzer/ton_analyzer.go index 5334b20c..d43ec236 100644 --- a/experimental/analyzer/ton_analyzer.go +++ b/experimental/analyzer/ton_analyzer.go @@ -3,26 +3,15 @@ package analyzer import ( "fmt" - "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" - "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/mcms" - "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/timelock" - "github.com/smartcontractkit/chainlink-ton/pkg/ton/debug/lib" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings" "github.com/smartcontractkit/mcms/sdk" "github.com/smartcontractkit/mcms/sdk/ton" "github.com/smartcontractkit/mcms/types" ) -// TODO should imported from sdk -var typeToTLBMap = map[string]lib.TLBMap{ - "com.chainlink.ton.lib.access.RBAC": rbac.TLBs, - "com.chainlink.ton.mcms.MCMS": mcms.TLBs, - "com.chainlink.ton.mcms.Timelock": timelock.TLBs, - "RBACTimelock": timelock.TLBs, -} - // AnalyzeTONTransactions decodes a slice of TON transactions and returns their decoded representations. func AnalyzeTONTransactions(ctx ProposalContext, txs []types.Transaction) ([]*DecodedCall, error) { - decoder := ton.NewDecoder(typeToTLBMap) + decoder := ton.NewDecoder(bindings.Registry) decodedTxs := make([]*DecodedCall, len(txs)) for i, op := range txs { analyzedTransaction, err := AnalyzeTONTransaction(ctx, decoder, op) diff --git a/experimental/analyzer/ton_analyzer_test.go b/experimental/analyzer/ton_analyzer_test.go index e86ac5f7..d2f582a6 100644 --- a/experimental/analyzer/ton_analyzer_test.go +++ b/experimental/analyzer/ton_analyzer_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/crypto" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings" "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" "github.com/smartcontractkit/chainlink-ton/pkg/ton/tlbe" "github.com/smartcontractkit/mcms/sdk/ton" @@ -23,7 +24,7 @@ func TestAnalyzeTONTransaction(t *testing.T) { setup := newTestTONSetup(t) ctx := &DefaultProposalContext{} - decoder := ton.NewDecoder(typeToTLBMap) + decoder := ton.NewDecoder(bindings.Registry) tests := []struct { name string diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index 477531ff..a78dae0a 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -11,10 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/goccy/go-yaml" chainsel "github.com/smartcontractkit/chain-selectors" - "github.com/smartcontractkit/chainlink-ton/pkg/bindings/lib/access/rbac" - tonmcms "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/mcms" - "github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/timelock" - "github.com/smartcontractkit/chainlink-ton/pkg/ton/debug/lib" + "github.com/smartcontractkit/chainlink-ton/pkg/bindings" "github.com/smartcontractkit/mcms" mcmsaptossdk "github.com/smartcontractkit/mcms/sdk/aptos" mcmssuisdk "github.com/smartcontractkit/mcms/sdk/sui" @@ -25,14 +22,6 @@ import ( mcmsanalyzer "github.com/smartcontractkit/chainlink-deployments-framework/experimental/analyzer" ) -// TODO should imported from sdk -var typeToTLBMap = map[string]lib.TLBMap{ - "com.chainlink.ton.lib.access.RBAC": rbac.TLBs, - "com.chainlink.ton.mcms.MCMS": tonmcms.TLBs, - "com.chainlink.ton.mcms.Timelock": timelock.TLBs, - "RBACTimelock": timelock.TLBs, -} - // UpfConvertTimelockProposal converts a TimelockProposal to a UPF proposal format. func UpfConvertTimelockProposal( ctx context.Context, @@ -397,7 +386,7 @@ func analyzeTransaction( return analyzeResult, "", nil case chainsel.FamilyTon: - decoder := mcmstonsdk.NewDecoder(typeToTLBMap) + decoder := mcmstonsdk.NewDecoder(bindings.Registry) analyzeResult, err := mcmsanalyzer.AnalyzeTONTransaction(proposalCtx, decoder, mcmsOp.Transaction) if err != nil { return nil, "", err diff --git a/go.mod b/go.mod index 803cd52f..d5fb1822 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4 github.com/smartcontractkit/chainlink-testing-framework/framework v0.13.3 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 - github.com/smartcontractkit/chainlink-ton v0.0.0-20251216164546-5c01294e67f0 + github.com/smartcontractkit/chainlink-ton v0.0.0-20260120144738-c9d69aa78a47 github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d @@ -77,7 +77,6 @@ require ( github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20251124151448-0448aefdaab9 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect - github.com/smartcontractkit/chainlink-ton v0.0.0-20260115170733-b16e9683d4d5 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 9b0686cb..06c2e010 100644 --- a/go.sum +++ b/go.sum @@ -722,6 +722,8 @@ github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 h1:ZJ/8Jx6B github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2/go.mod h1:kHYJnZUqiPF7/xN5273prV+srrLJkS77GbBXHLKQpx0= github.com/smartcontractkit/chainlink-ton v0.0.0-20260115170733-b16e9683d4d5 h1:qoXtC2Ypwt/4BYYCsjs58hnzL+38Mp5N7WYmN0cvMkM= github.com/smartcontractkit/chainlink-ton v0.0.0-20260115170733-b16e9683d4d5/go.mod h1:8Nbyr/8SUFNH9wmTlT4FNd80XzlO3RN5r2DQReeXg7k= +github.com/smartcontractkit/chainlink-ton v0.0.0-20260120144738-c9d69aa78a47 h1:rCO+HGhAYgnQQQfjNl0wAa3L/DQltIwRaxoaFTLu384= +github.com/smartcontractkit/chainlink-ton v0.0.0-20260120144738-c9d69aa78a47/go.mod h1:jeuUzo8fWXrqnMniJrtfmbbtE8FJr6why+Maj/Xz1ZU= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 h1:7bxYNrPpygn8PUSBiEKn8riMd7CXMi/4bjTy0fHhcrY= github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335/go.mod h1:ccjEgNeqOO+bjPddnL4lUrNLzyCvGCxgBjJdhFX3wa8= github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.4 h1:J4qtAo0ZmgX5pIr8Y5mdC+J2rj2e/6CTUC263t6mGOM= From a005805dd9bff01bf6f7472000a6f8b4ccb30e8b Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Wed, 21 Jan 2026 20:36:17 -0600 Subject: [PATCH 20/22] Update experimental/analyzer/upf/upf_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- experimental/analyzer/upf/upf_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/analyzer/upf/upf_test.go b/experimental/analyzer/upf/upf_test.go index 8bb32ec9..32ddb41a 100644 --- a/experimental/analyzer/upf/upf_test.go +++ b/experimental/analyzer/upf/upf_test.go @@ -727,7 +727,7 @@ signers: ` // timelockProposalTon is generated using makeTONGrantRoleTx helper -var timelockProposalTon = func(t *testing.T) string { +var timelockProposalTON = func(t *testing.T) string { t.Helper() // Create a GrantRole transaction for the test targetAddr := address.MustParseAddr("EQADa3W6G0nSiTV4a6euRA42fU9QxSEnb-WeDpcrtWzA2jM8") From c3530bfce963e43a93e5f08ee121f0595df547b4 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Wed, 21 Jan 2026 20:56:02 -0600 Subject: [PATCH 21/22] fix test and refactor timelock checker --- experimental/analyzer/upf/timelock_checker.go | 65 +++++++++++++++++++ experimental/analyzer/upf/upf.go | 32 --------- experimental/analyzer/upf/upf_test.go | 4 +- 3 files changed, 67 insertions(+), 34 deletions(-) create mode 100644 experimental/analyzer/upf/timelock_checker.go diff --git a/experimental/analyzer/upf/timelock_checker.go b/experimental/analyzer/upf/timelock_checker.go new file mode 100644 index 00000000..1fd4ba44 --- /dev/null +++ b/experimental/analyzer/upf/timelock_checker.go @@ -0,0 +1,65 @@ +package upf + +import "strings" + +// timelockBatchChecker provides chain-specific logic for detecting timelock batch functions. +type timelockBatchChecker interface { + isTimelockBatch(functionName string) bool +} + +// evmTimelockChecker handles EVM chains. +// Matches full function signatures for scheduleBatch and bypasserExecuteBatch. +type evmTimelockChecker struct{} + +func (evmTimelockChecker) isTimelockBatch(functionName string) bool { + return functionName == "function scheduleBatch((address,uint256,bytes)[] calls, bytes32 predecessor, bytes32 salt, uint256 delay) returns()" || + functionName == "function bypasserExecuteBatch((address,uint256,bytes)[] calls) payable returns()" +} + +// solanaTimelockChecker handles Solana chain. +// Matches exact function names: ScheduleBatch, BypasserExecuteBatch. +type solanaTimelockChecker struct{} + +func (solanaTimelockChecker) isTimelockBatch(functionName string) bool { + return functionName == "ScheduleBatch" || functionName == "BypasserExecuteBatch" +} + +// suiAptosTimelockChecker handles both Sui and Aptos chains. +// Sui: mcms::timelock_schedule_batch, mcms::timelock_bypasser_execute_batch +// Aptos: package::module::timelock_schedule_batch, package::module::timelock_bypasser_execute_batch +// Uses HasSuffix to prevent false positives like "::timelock_schedule_batch_helper". +type suiAptosTimelockChecker struct{} + +func (suiAptosTimelockChecker) isTimelockBatch(functionName string) bool { + return strings.HasSuffix(functionName, "::timelock_schedule_batch") || + strings.HasSuffix(functionName, "::timelock_bypasser_execute_batch") +} + +// tonTimelockChecker handles TON chain. +// TON: ContractType::ScheduleBatch(0x...), ContractType::BypasserExecuteBatch(0x...) +// Uses Contains because the opcode suffix (0x...) varies. +type tonTimelockChecker struct{} + +func (tonTimelockChecker) isTimelockBatch(functionName string) bool { + return strings.Contains(functionName, "::ScheduleBatch(") || + strings.Contains(functionName, "::BypasserExecuteBatch(") +} + +// timelockBatchCheckers is a list of chain-specific checkers for timelock batch functions. +var timelockBatchCheckers = []timelockBatchChecker{ + evmTimelockChecker{}, + solanaTimelockChecker{}, + suiAptosTimelockChecker{}, + tonTimelockChecker{}, +} + +// isTimelockBatchFunction checks if the function name corresponds to a timelock batch operation +// across different chain families (EVM, Solana, Sui, Aptos, TON). +func isTimelockBatchFunction(functionName string) bool { + for _, checker := range timelockBatchCheckers { + if checker.isTimelockBatch(functionName) { + return true + } + } + return false +} \ No newline at end of file diff --git a/experimental/analyzer/upf/upf.go b/experimental/analyzer/upf/upf.go index a78dae0a..9cf249f0 100644 --- a/experimental/analyzer/upf/upf.go +++ b/experimental/analyzer/upf/upf.go @@ -216,38 +216,6 @@ func asciiHash(data [32]byte) rawBytes { return rawBytes(sb.String()) } -// isTimelockBatchFunction checks if the function name corresponds to a timelock batch operation -// across different chain families (EVM, Solana, Sui, Aptos, TON). -func isTimelockBatchFunction(functionName string) bool { - // EVM function signatures - if functionName == "function scheduleBatch((address,uint256,bytes)[] calls, bytes32 predecessor, bytes32 salt, uint256 delay) returns()" || - functionName == "function bypasserExecuteBatch((address,uint256,bytes)[] calls) payable returns()" { - return true - } - - // Solana function names - if functionName == "ScheduleBatch" || functionName == "BypasserExecuteBatch" { - return true - } - - // Sui: mcms::timelock_schedule_batch, mcms::timelock_bypasser_execute_batch - // Aptos: package::module::timelock_schedule_batch, package::module::timelock_bypasser_execute_batch - // Use HasSuffix to prevent false positives like "::timelock_schedule_batch_helper" - if strings.HasSuffix(functionName, "::timelock_schedule_batch") || - strings.HasSuffix(functionName, "::timelock_bypasser_execute_batch") { - return true - } - - // TON: ContractType::ScheduleBatch(0x...), ContractType::BypasserExecuteBatch(0x...) - // Use Contains because the opcode suffix (0x...) varies - if strings.Contains(functionName, "::ScheduleBatch(") || - strings.Contains(functionName, "::BypasserExecuteBatch(") { - return true - } - - return false -} - func calculateOpCount(opCount uint64, opIndex int, operations []mcmstypes.Operation) uint64 { chainSelector := operations[opIndex].ChainSelector for i, op := range operations { diff --git a/experimental/analyzer/upf/upf_test.go b/experimental/analyzer/upf/upf_test.go index 32ddb41a..df6447cd 100644 --- a/experimental/analyzer/upf/upf_test.go +++ b/experimental/analyzer/upf/upf_test.go @@ -207,7 +207,7 @@ func TestUpfConvertTimelockProposalWithTon(t *testing.T) { }{ { name: "TON proposal with GrantRole transaction", - timelockProposal: timelockProposalTon(t), + timelockProposal: timelockProposalTON(t), signers: map[mcmstypes.ChainSelector][]common.Address{ mcmstypes.ChainSelector(chainsel.TON_TESTNET.Selector): { common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"), @@ -726,7 +726,7 @@ signers: - "0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953" ` -// timelockProposalTon is generated using makeTONGrantRoleTx helper +// timelockProposalTON is generated using makeTONGrantRoleTx helper var timelockProposalTON = func(t *testing.T) string { t.Helper() // Create a GrantRole transaction for the test From 46fb259256ed53e176232db830c397bc2bd85a82 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Wed, 21 Jan 2026 21:01:11 -0600 Subject: [PATCH 22/22] fix lint --- experimental/analyzer/upf/timelock_checker.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/experimental/analyzer/upf/timelock_checker.go b/experimental/analyzer/upf/timelock_checker.go index 1fd4ba44..c0d94cb7 100644 --- a/experimental/analyzer/upf/timelock_checker.go +++ b/experimental/analyzer/upf/timelock_checker.go @@ -61,5 +61,6 @@ func isTimelockBatchFunction(functionName string) bool { return true } } + return false -} \ No newline at end of file +}