Skip to content

Commit 4aa9bd8

Browse files
krebernisakhuangzhen1997
authored andcommitted
Add TON support (#486)
Co-authored-by: Joe Huang <joe.huang@smartcontract.com>
1 parent 241f0a0 commit 4aa9bd8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+8919
-104
lines changed

.changeset/long-snakes-ring.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smartcontractkit/mcms": minor
3+
---
4+
5+
Add TON implementation and unit/e2e tests

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
* @smartcontractkit/op-tooling
2+
3+
# Nix shell setup (supports TON e2e tests)
4+
/.github/workflows/pull-request-main-nix.yml @smartcontractkit/ccip-ton
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: pull-request-main-nix
2+
3+
on:
4+
merge_group:
5+
pull_request:
6+
branches:
7+
- main
8+
push:
9+
branches:
10+
- main
11+
12+
jobs:
13+
ci-test-e2e:
14+
name: Tests E2E - Nix setup
15+
runs-on: ubuntu-latest
16+
permissions:
17+
id-token: write
18+
contents: read
19+
actions: read
20+
steps:
21+
- name: Install Nix
22+
uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
23+
with:
24+
nix_path: nixpkgs=channel:nixos-unstable
25+
26+
- name: Checkout code
27+
uses: actions/checkout@v4
28+
29+
- name: Build TON contracts
30+
id: ton-contracts-build
31+
shell: bash
32+
run: |
33+
PATH_CONTRACTS_TON_PKG="$(nix build .#chainlink-ton-contracts --print-out-paths)/"
34+
PATH_CONTRACTS_TON="$PATH_CONTRACTS_TON_PKG/lib/node_modules/@chainlink/contracts-ton/build/"
35+
echo "path=$PATH_CONTRACTS_TON" >> "$GITHUB_OUTPUT"
36+
37+
- name: Run e2e tests
38+
uses: smartcontractkit/.github/actions/ci-test-go@ci-test-go/1.0.0
39+
with:
40+
checkout-repo: false
41+
use-go-cache: true
42+
go-test-cmd: |
43+
set +e
44+
45+
echo "::group::TON"
46+
export PATH_CONTRACTS_TON="${{ steps.ton-contracts-build.outputs.path }}"
47+
CTF_CONFIGS=../config.ton.toml go test -p=1 -tags=e2e -v ./e2e/tests/... -run=TestTONSuite || ton_failure=true
48+
echo "::endgroup::"
49+
50+
[[ -n "${ton_failure}" ]] && echo "🚨 TON e2e tests failed."
51+
[[ -n "${ton_failure}" ]] && {
52+
exit 1
53+
} || {
54+
echo "Exiting"
55+
exit 0
56+
}

.mockery.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,24 @@ mockname: "{{.InterfaceName}}"
1414
inpackage: false
1515
outpkg: mocks
1616
packages:
17+
github.com/xssnick/tonutils-go/ton/wallet:
18+
config:
19+
all: false
20+
outpkg: "mock_ton"
21+
interfaces:
22+
TonAPI:
23+
config:
24+
dir: "./sdk/ton/mocks"
25+
filename: "wallet.go"
26+
github.com/xssnick/tonutils-go/ton:
27+
config:
28+
all: false
29+
outpkg: "mock_ton"
30+
interfaces:
31+
APIClientWrapped:
32+
config:
33+
dir: "./sdk/ton/mocks"
34+
filename: "api.go"
1735
github.com/smartcontractkit/mcms/inspectors:
1836
github.com/smartcontractkit/mcms/sdk:
1937
github.com/smartcontractkit/mcms/sdk/evm:

e2e/config.ton.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[settings]
2+
private_keys = [
3+
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
4+
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
5+
"0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
6+
]
7+
8+
[ton_config]
9+
chain_id = "-217"
10+
type = "ton"
11+
image = "ghcr.io/neodix42/mylocalton-docker:v3.98"
12+
13+
[ton_config.out]
14+
family = "ton"
15+
16+
[ton_config.custom_env]
17+
NEXT_BLOCK_GENERATION_DELAY = "0.5"
18+
VERSION_CAPABILITIES = "12"

e2e/tests/aptos/set_config.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package aptos
44

55
import (
66
"slices"
7-
"strings"
87

98
"github.com/ethereum/go-ethereum/common"
109
"github.com/ethereum/go-ethereum/crypto"
@@ -30,7 +29,7 @@ func (a *TestSuite) TestSetConfig() {
3029
signers[i] = crypto.PubkeyToAddress(key.PublicKey)
3130
}
3231
slices.SortFunc(signers[:], func(a, b common.Address) int {
33-
return strings.Compare(strings.ToLower(a.Hex()), strings.ToLower(b.Hex()))
32+
return a.Cmp(b)
3433
})
3534

3635
bypasserConfig := &types.Config{

e2e/tests/runner_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
evme2e "github.com/smartcontractkit/mcms/e2e/tests/evm"
1212
solanae2e "github.com/smartcontractkit/mcms/e2e/tests/solana"
1313
suie2e "github.com/smartcontractkit/mcms/e2e/tests/sui"
14+
tone2e "github.com/smartcontractkit/mcms/e2e/tests/ton"
1415
)
1516

1617
func TestEVMSuite(t *testing.T) {
@@ -39,3 +40,12 @@ func TestSuiSuite(t *testing.T) {
3940
suite.Run(t, new(suie2e.TimelockCancelProposalTestSuite))
4041
suite.Run(t, new(suie2e.MCMSUserUpgradeTestSuite))
4142
}
43+
44+
func TestTONSuite(t *testing.T) {
45+
suite.Run(t, new(tone2e.SigningTestSuite))
46+
suite.Run(t, new(tone2e.SetConfigTestSuite))
47+
suite.Run(t, new(tone2e.SetRootTestSuite))
48+
suite.Run(t, new(tone2e.InspectionTestSuite))
49+
suite.Run(t, new(tone2e.ExecutionTestSuite))
50+
suite.Run(t, new(tone2e.TimelockInspectionTestSuite))
51+
}

e2e/tests/setup.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import (
1818
"github.com/gagliardetto/solana-go/rpc/ws"
1919
"github.com/joho/godotenv"
2020
"github.com/stretchr/testify/require"
21+
"github.com/xssnick/tonutils-go/ton"
22+
23+
tonchain "github.com/smartcontractkit/chainlink-ton/pkg/ton/chain"
2124

2225
"github.com/smartcontractkit/chainlink-testing-framework/framework"
2326
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
@@ -39,6 +42,7 @@ type Config struct {
3942
SolanaChain *blockchain.Input `toml:"solana_config"`
4043
AptosChain *blockchain.Input `toml:"aptos_config"`
4144
SuiChain *blockchain.Input `toml:"sui_config"`
45+
TonChain *blockchain.Input `toml:"ton_config"`
4246

4347
Settings struct {
4448
PrivateKeys []string `toml:"private_keys"`
@@ -57,6 +61,8 @@ type TestSetup struct {
5761
AptosBlockchain *blockchain.Output
5862
SuiClient sui.ISuiAPI
5963
SuiBlockchain *blockchain.Output
64+
TonClient *ton.APIClient
65+
TonBlockchain *blockchain.Output
6066
Config
6167
}
6268

@@ -203,6 +209,30 @@ func InitializeSharedTestSetup(t *testing.T) *TestSetup {
203209
t.Logf("Initialized Sui RPC client @ %s", nodeURL)
204210
}
205211

212+
var (
213+
tonClient *ton.APIClient
214+
tonBlockchainOutput *blockchain.Output
215+
)
216+
if in.TonChain != nil {
217+
// Use blockchain network setup (fallback)
218+
ports := freeport.GetN(t, 2)
219+
port := ports[0]
220+
faucetPort := ports[1]
221+
in.TonChain.Port = strconv.Itoa(port)
222+
in.TonChain.FaucetPort = strconv.Itoa(faucetPort)
223+
224+
tonBlockchainOutput, err = blockchain.NewBlockchainNetwork(in.TonChain)
225+
require.NoError(t, err, "Failed to initialize TON blockchain")
226+
227+
nodeURL := tonBlockchainOutput.Nodes[0].ExternalHTTPUrl
228+
pool, err := tonchain.CreateLiteserverConnectionPool(ctx, nodeURL)
229+
require.NoError(t, err, "Failed to initialize TON client - failed to create liteserver connection pool")
230+
tonClient = ton.NewAPIClient(pool, ton.ProofCheckPolicyFast)
231+
232+
// Test liveness, will also fetch ChainID
233+
t.Logf("Initialized TON RPC client @ %s", nodeURL)
234+
}
235+
206236
sharedSetup = &TestSetup{
207237
ClientA: ethClientA,
208238
ClientB: ethClientB,
@@ -213,6 +243,8 @@ func InitializeSharedTestSetup(t *testing.T) *TestSetup {
213243
AptosBlockchain: aptosBlockchainOutput,
214244
SuiClient: suiClient,
215245
SuiBlockchain: suiBlockchainOutput,
246+
TonClient: tonClient,
247+
TonBlockchain: tonBlockchainOutput,
216248
Config: *in,
217249
}
218250
})

e2e/tests/sui/set_config.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package sui
44

55
import (
66
"slices"
7-
"strings"
87

98
"github.com/ethereum/go-ethereum/common"
109
"github.com/ethereum/go-ethereum/crypto"
@@ -26,7 +25,7 @@ func (s *TestSuite) TestSetConfig() {
2625
signers[i] = crypto.PubkeyToAddress(key.PublicKey)
2726
}
2827
slices.SortFunc(signers[:], func(a, b common.Address) int {
29-
return strings.Compare(strings.ToLower(a.Hex()), strings.ToLower(b.Hex()))
28+
return a.Cmp(b)
3029
})
3130

3231
bypasserConfig := &types.Config{

e2e/tests/ton/common.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//go:build e2e
2+
3+
package tone2e
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
11+
"github.com/ethereum/go-ethereum/common"
12+
13+
"github.com/xssnick/tonutils-go/address"
14+
"github.com/xssnick/tonutils-go/tlb"
15+
"github.com/xssnick/tonutils-go/ton"
16+
"github.com/xssnick/tonutils-go/ton/wallet"
17+
"github.com/xssnick/tonutils-go/tvm/cell"
18+
19+
"github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/mcms"
20+
"github.com/smartcontractkit/chainlink-ton/pkg/bindings/mcms/timelock"
21+
"github.com/smartcontractkit/chainlink-ton/pkg/ton/tracetracking"
22+
"github.com/smartcontractkit/chainlink-ton/pkg/ton/wrappers"
23+
24+
"github.com/smartcontractkit/mcms/internal/testutils"
25+
"github.com/smartcontractkit/mcms/types"
26+
)
27+
28+
const (
29+
EnvPathContracts = "PATH_CONTRACTS_TON"
30+
31+
PathContractsMCMS = "mcms.MCMS.compiled.json"
32+
PathContractsTimelock = "mcms.RBACTimelock.compiled.json"
33+
)
34+
35+
func must[E any](out E, err error) E {
36+
if err != nil {
37+
panic(err)
38+
}
39+
40+
return out
41+
}
42+
43+
type DeployOpts struct {
44+
// Connection
45+
Client *ton.APIClient
46+
Wallet *wallet.Wallet
47+
48+
// Deployment info
49+
ContractPath string
50+
51+
Amount tlb.Coins
52+
Data any
53+
Body any
54+
}
55+
56+
func DeployContract(ctx context.Context, opts DeployOpts) (*address.Address, error) {
57+
contractCode, err := wrappers.ParseCompiledContract(opts.ContractPath)
58+
if err != nil {
59+
return nil, fmt.Errorf("failed to parse compiled contract: %w", err)
60+
}
61+
62+
contractData, ok := opts.Data.(*cell.Cell) // Cell or we try to decode
63+
if !ok {
64+
contractData, err = tlb.ToCell(opts.Data)
65+
if err != nil {
66+
return nil, fmt.Errorf("failed to create contract data cell: %w", err)
67+
}
68+
}
69+
70+
bodyCell, ok := opts.Body.(*cell.Cell) // Cell or we try to decode
71+
if !ok {
72+
bodyCell, err = tlb.ToCell(opts.Body)
73+
if err != nil {
74+
return nil, fmt.Errorf("failed to create contract body cell: %w", err)
75+
}
76+
}
77+
78+
_client := tracetracking.NewSignedAPIClient(opts.Client, *opts.Wallet)
79+
contract, _, err := wrappers.Deploy(ctx, &_client, contractCode, contractData, opts.Amount, bodyCell)
80+
if err != nil {
81+
return nil, fmt.Errorf("failed to deploy contract: %w", err)
82+
}
83+
84+
return contract.Address, nil
85+
}
86+
87+
func DeployMCMSContract(ctx context.Context, client *ton.APIClient, w *wallet.Wallet, amount tlb.Coins, data mcms.Data) (*address.Address, error) {
88+
return DeployContract(ctx, DeployOpts{
89+
Client: client,
90+
Wallet: w,
91+
ContractPath: filepath.Join(os.Getenv(EnvPathContracts), PathContractsMCMS),
92+
Amount: amount,
93+
Data: data,
94+
Body: cell.BeginCell().EndCell(), // empty cell, top up
95+
})
96+
}
97+
98+
func DeployTimelockContract(ctx context.Context, client *ton.APIClient, w *wallet.Wallet, amount tlb.Coins, data timelock.Data, body timelock.Init) (*address.Address, error) {
99+
return DeployContract(ctx, DeployOpts{
100+
Client: client,
101+
Wallet: w,
102+
ContractPath: filepath.Join(os.Getenv(EnvPathContracts), PathContractsTimelock),
103+
Amount: amount,
104+
Data: data,
105+
Body: body,
106+
})
107+
}
108+
109+
// GenSimpleTestMCMSConfig generates a simple test configuration that's used in e2e tests.
110+
func GenSimpleTestMCMSConfig(signers []testutils.ECDSASigner) *types.Config {
111+
return &types.Config{
112+
Quorum: 1,
113+
Signers: []common.Address{signers[0].Address()},
114+
GroupSigners: []types.Config{
115+
{
116+
Quorum: 1,
117+
Signers: []common.Address{signers[1].Address()},
118+
GroupSigners: []types.Config{},
119+
},
120+
},
121+
}
122+
}

0 commit comments

Comments
 (0)