diff --git a/chain/ton/provider/ctf_provider.go b/chain/ton/provider/ctf_provider.go index 85adf724..18c37fff 100644 --- a/chain/ton/provider/ctf_provider.go +++ b/chain/ton/provider/ctf_provider.go @@ -13,8 +13,6 @@ import ( "github.com/avast/retry-go/v4" "github.com/testcontainers/testcontainers-go" - "github.com/xssnick/tonutils-go/address" - "github.com/xssnick/tonutils-go/tlb" "github.com/xssnick/tonutils-go/ton" "github.com/xssnick/tonutils-go/ton/wallet" @@ -126,17 +124,11 @@ func (p *CTFChainProvider) Initialize(ctx context.Context) (chain.BlockChain, er return nil, fmt.Errorf("failed to create wallet: %w", err) } - // airdrop the deployer wallet - ferr := fundTonWallets(ctx, nodeClient, []*address.Address{tonWallet.Address()}, []tlb.Coins{tlb.MustFromTON("1000")}) - if ferr != nil { - return nil, fmt.Errorf("failed to fund wallet: %w", ferr) - } - p.chain = &cldf_ton.Chain{ ChainMetadata: cldf_ton.ChainMetadata{Selector: p.selector}, Client: nodeClient, Wallet: tonWallet, - WalletAddress: tonWallet.Address(), + WalletAddress: tonWallet.WalletAddress(), URL: url, } @@ -226,54 +218,6 @@ func createTonWallet(client ton.APIClientWrapped, versionConfig wallet.VersionCo return pw, nil } -func fundTonWallets(ctx context.Context, client ton.APIClientWrapped, recipients []*address.Address, amounts []tlb.Coins) error { - if len(amounts) != len(recipients) { - return errors.New("recipients and amounts must have the same length") - } - - // initialize the prefunded wallet(Highload-V2), for other wallets, see https://github.com/neodix42/mylocalton-docker#pre-installed-wallets - version := wallet.HighloadV2Verified //nolint:staticcheck // SA1019: only available option in mylocalton-docker - rawHlWallet, err := wallet.FromSeed(client, strings.Fields(blockchain.DefaultTonHlWalletMnemonic), version) - if err != nil { - return fmt.Errorf("failed to create wallet from seed: %w", err) - } - - mcFunderWallet, err := wallet.FromPrivateKeyWithOptions(client, rawHlWallet.PrivateKey(), version, wallet.WithWorkchain(-1)) - if err != nil { - return fmt.Errorf("failed to create wallet from private key: %w", err) - } - - funder, err := mcFunderWallet.GetSubwallet(uint32(42)) - if err != nil { - return fmt.Errorf("failed to get subwallet: %w", err) - } - - // double check funder address - if funder.Address().StringRaw() != blockchain.DefaultTonHlWalletAddress { - return fmt.Errorf("funder address mismatch: %s != %s", funder.Address().StringRaw(), blockchain.DefaultTonHlWalletAddress) - } - - // create transfer messages for each recipient - messages := make([]*wallet.Message, len(recipients)) - for i, addr := range recipients { - transfer, terr := funder.BuildTransfer(addr, amounts[i], false, "") - if terr != nil { - return fmt.Errorf("failed to build transfer: %w", terr) - } - messages[i] = transfer - } - - // we don't wait for the transaction to be confirmed here, as it may take some time - // the name SendManyWaitTransaction is misleading, it doesn't wait for the transaction to be confirmed, - // it just sends the transactions(TON has asynchronous transactions) - _, _, txerr := funder.SendManyWaitTransaction(ctx, messages) - if txerr != nil { - return fmt.Errorf("failed to send many wait transaction: %w", txerr) - } - - return nil -} - func getMasterchainBlockID(ctx context.Context, client ton.APIClientWrapped) (*ton.BlockIDExt, error) { var masterchainBlockID *ton.BlockIDExt // check connection, CTFv2 handles the readiness diff --git a/chain/ton/provider/ctf_provider_test.go b/chain/ton/provider/ctf_provider_test.go index 3be4936c..acd376f9 100644 --- a/chain/ton/provider/ctf_provider_test.go +++ b/chain/ton/provider/ctf_provider_test.go @@ -89,7 +89,7 @@ func Test_CTFChainProvider_Initialize(t *testing.T) { assert.Equal(t, tt.giveSelector, gotChain.Selector) assert.NotEmpty(t, gotChain.Client) assert.NotEmpty(t, gotChain.Wallet) - assert.NotEmpty(t, gotChain.WalletAddress) + require.False(t, gotChain.WalletAddress.IsAddrNone()) } }) } diff --git a/chain/ton/provider/rpc_provider_test.go b/chain/ton/provider/rpc_provider_test.go index add5c34b..4afa05d0 100644 --- a/chain/ton/provider/rpc_provider_test.go +++ b/chain/ton/provider/rpc_provider_test.go @@ -89,6 +89,22 @@ func Test_RPCChainProvider_Initialize(t *testing.T) { assert.Equal(t, existingChain.Selector, gotChain.Selector) } +func Test_RPCChainProvider_Initialize_InvalidConfig(t *testing.T) { + t.Parallel() + + p := &RPCChainProvider{ + selector: 123, + config: RPCChainProviderConfig{ + HTTPURL: "", // invalid - missing URL + DeployerSignerGen: PrivateKeyRandom(), + }, + } + + _, err := p.Initialize(t.Context()) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to validate provider config") +} + func Test_RPCChainProvider_Name(t *testing.T) { t.Parallel() diff --git a/engine/cld/legacy/cli/mcmsv2/mcms_v2.go b/engine/cld/legacy/cli/mcmsv2/mcms_v2.go index ef527bd9..420e60cd 100644 --- a/engine/cld/legacy/cli/mcmsv2/mcms_v2.go +++ b/engine/cld/legacy/cli/mcmsv2/mcms_v2.go @@ -28,9 +28,11 @@ import ( "github.com/smartcontractkit/mcms/sdk/evm/bindings" "github.com/smartcontractkit/mcms/sdk/solana" "github.com/smartcontractkit/mcms/sdk/sui" + "github.com/smartcontractkit/mcms/sdk/ton" "github.com/smartcontractkit/mcms/types" "github.com/spf13/cobra" "github.com/spf13/pflag" + "github.com/xssnick/tonutils-go/tlb" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -56,6 +58,11 @@ const ( indexFlag = "index" forkFlag = "fork" defaultProposalValidity = 72 * time.Hour + + // defaultTONExecutorAmount is the default amount of TON for MCMS/Timelock executor transactions. + // This is a static estimate that should be sufficient for most operations. + // TODO: Replace with gas estimator component when implemented. NONEVM-3482 + defaultTONExecutorAmount = "0.1" ) const mcmsv2DeprecatedWarning = ` @@ -1129,6 +1136,8 @@ func newCfgv2(lggr logger.Logger, cmd *cobra.Command, domain cldf_domain.Domain, if err != nil { return nil, fmt.Errorf("error creating Sui timelock converter: %w", err) } + case chainsel.FamilyTon: + converter = ton.NewTimelockConverter(tlb.MustFromTON(defaultTONExecutorAmount)) default: return nil, fmt.Errorf("unsupported chain family %s", fam) } @@ -1441,18 +1450,18 @@ func getExecutorWithChainOverride(cfg *cfgv2, chainSelector types.ChainSelector) if !ok { return nil, fmt.Errorf("invalid encoder type: %T", encoder) } - chain := cfg.blockchains.EVMChains()[uint64(chainSelector)] + c := cfg.blockchains.EVMChains()[uint64(chainSelector)] - return evm.NewExecutor(evmEncoder, chain.Client, chain.DeployerKey), nil + return evm.NewExecutor(evmEncoder, c.Client, c.DeployerKey), nil case chainsel.FamilySolana: solanaEncoder, ok := encoder.(*solana.Encoder) if !ok { return nil, fmt.Errorf("invalid encoder type: %T", encoder) } - chain := cfg.blockchains.SolanaChains()[uint64(chainSelector)] + c := cfg.blockchains.SolanaChains()[uint64(chainSelector)] - return solana.NewExecutor(solanaEncoder, chain.Client, *chain.DeployerKey), nil + return solana.NewExecutor(solanaEncoder, c.Client, *c.DeployerKey), nil case chainsel.FamilyAptos: encoder, ok := encoder.(*aptos.Encoder) @@ -1463,9 +1472,9 @@ func getExecutorWithChainOverride(cfg *cfgv2, chainSelector types.ChainSelector) if err != nil { return nil, fmt.Errorf("error getting aptos role from proposal: %w", err) } - chain := cfg.blockchains.AptosChains()[uint64(chainSelector)] + c := cfg.blockchains.AptosChains()[uint64(chainSelector)] - return aptos.NewExecutor(chain.Client, chain.DeployerSigner, encoder, *role), nil + return aptos.NewExecutor(c.Client, c.DeployerSigner, encoder, *role), nil case chainsel.FamilySui: encoder, ok := encoder.(*sui.Encoder) @@ -1476,10 +1485,24 @@ func getExecutorWithChainOverride(cfg *cfgv2, chainSelector types.ChainSelector) if err != nil { return nil, fmt.Errorf("error getting sui metadata from proposal: %w", err) } - chain := cfg.blockchains.SuiChains()[uint64(chainSelector)] + c := cfg.blockchains.SuiChains()[uint64(chainSelector)] entrypointEncoder := suibindings.NewCCIPEntrypointArgEncoder(metadata.RegistryObj, metadata.DeployerStateObj) - return sui.NewExecutor(chain.Client, chain.Signer, encoder, entrypointEncoder, metadata.McmsPackageID, metadata.Role, cfg.timelockProposal.ChainMetadata[chainSelector].MCMAddress, metadata.AccountObj, metadata.RegistryObj, metadata.TimelockObj) + return sui.NewExecutor(c.Client, c.Signer, encoder, entrypointEncoder, metadata.McmsPackageID, metadata.Role, cfg.timelockProposal.ChainMetadata[chainSelector].MCMAddress, metadata.AccountObj, metadata.RegistryObj, metadata.TimelockObj) + case chainsel.FamilyTon: + encoder, ok := encoder.(*ton.Encoder) + if !ok { + return nil, fmt.Errorf("invalid encoder type for TON chain %d: expected *ton.Encoder, got %T", chainSelector, encoder) + } + c := cfg.blockchains.TonChains()[uint64(chainSelector)] + opts := ton.ExecutorOpts{ + Encoder: encoder, + Client: c.Client, + Wallet: c.Wallet, + Amount: tlb.MustFromTON(defaultTONExecutorAmount), + } + + return ton.NewExecutor(opts) default: return nil, fmt.Errorf("unsupported chain family %s", family) } @@ -1509,26 +1532,35 @@ func getTimelockExecutorWithChainOverride(cfg *cfgv2, chainSelector types.ChainS var executor sdk.TimelockExecutor switch family { case chainsel.FamilyEVM: - chain := cfg.blockchains.EVMChains()[uint64(chainSelector)] + c := cfg.blockchains.EVMChains()[uint64(chainSelector)] - executor = evm.NewTimelockExecutor(chain.Client, chain.DeployerKey) + executor = evm.NewTimelockExecutor(c.Client, c.DeployerKey) case chainsel.FamilySolana: - chain := cfg.blockchains.SolanaChains()[uint64(chainSelector)] - executor = solana.NewTimelockExecutor(chain.Client, *chain.DeployerKey) + c := cfg.blockchains.SolanaChains()[uint64(chainSelector)] + executor = solana.NewTimelockExecutor(c.Client, *c.DeployerKey) case chainsel.FamilyAptos: - chain := cfg.blockchains.AptosChains()[uint64(chainSelector)] - executor = aptos.NewTimelockExecutor(chain.Client, chain.DeployerSigner) + c := cfg.blockchains.AptosChains()[uint64(chainSelector)] + executor = aptos.NewTimelockExecutor(c.Client, c.DeployerSigner) case chainsel.FamilySui: - chain := cfg.blockchains.SuiChains()[uint64(chainSelector)] + c := cfg.blockchains.SuiChains()[uint64(chainSelector)] metadata, err := suiMetadataFromProposal(chainSelector, cfg.timelockProposal) if err != nil { return nil, fmt.Errorf("error getting sui metadata from proposal: %w", err) } entrypointEncoder := suibindings.NewCCIPEntrypointArgEncoder(metadata.RegistryObj, metadata.DeployerStateObj) - executor, err = sui.NewTimelockExecutor(chain.Client, chain.Signer, entrypointEncoder, metadata.McmsPackageID, metadata.RegistryObj, metadata.AccountObj) + executor, err = sui.NewTimelockExecutor(c.Client, c.Signer, entrypointEncoder, metadata.McmsPackageID, metadata.RegistryObj, metadata.AccountObj) if err != nil { return nil, fmt.Errorf("error creating sui timelock executor: %w", err) } + case chainsel.FamilyTon: + c := cfg.blockchains.TonChains()[uint64(chainSelector)] + opts := ton.TimelockExecutorOpts{ + Client: c.Client, + Wallet: c.Wallet, + Amount: tlb.MustFromTON(defaultTONExecutorAmount), + } + + return ton.NewTimelockExecutor(opts) default: return nil, fmt.Errorf("unsupported chain family %s", family) } @@ -1583,6 +1615,9 @@ var getInspectorFromChainSelector = func(cfg cfgv2) (sdk.Inspector, error) { if err != nil { return nil, fmt.Errorf("error creating sui inspector: %w", err) } + case chainsel.FamilyTon: + chain := cfg.blockchains.TonChains()[cfg.chainSelector] + inspector = ton.NewInspector(chain.Client) default: return nil, fmt.Errorf("unsupported chain family %s", fam) } diff --git a/engine/cld/legacy/cli/mcmsv2/mcms_v2_test.go b/engine/cld/legacy/cli/mcmsv2/mcms_v2_test.go index ff2beada..5a7c7bfc 100644 --- a/engine/cld/legacy/cli/mcmsv2/mcms_v2_test.go +++ b/engine/cld/legacy/cli/mcmsv2/mcms_v2_test.go @@ -395,6 +395,15 @@ func Test_timelockExecuteOptions(t *testing.T) { require.Empty(t, opts) }, }, + { + name: "empty options for TON", + cfg: &cfgv2{chainSelector: chainsel.TON_MAINNET.Selector}, + assert: func(t *testing.T, opts []mcms.Option, err error) { + t.Helper() + require.NoError(t, err) + require.Empty(t, opts) + }, + }, { name: "CallProxy option added for EVM when addresses is in DataStore", cfg: &cfgv2{