Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
514a519
Setup env
krebernisak Sep 25, 2025
454988a
Add TON domain - add configurer
krebernisak Sep 25, 2025
f45d05b
Encode SetConfig message
krebernisak Sep 25, 2025
78816c4
Add ton.ConfigTransformer
krebernisak Sep 25, 2025
f9a0fe5
Implement transformer.ToConfig
krebernisak Sep 25, 2025
dfc229e
Add sdk/ton/encoder.go
krebernisak Sep 26, 2025
8ed2b32
Add TON Encoder.HashMetadata
krebernisak Sep 26, 2025
3d8ddd0
Add sdk/ton/inspector.go
krebernisak Sep 26, 2025
5ed93ce
Add sdk.Inspector#GetRootMetadata
krebernisak Sep 26, 2025
a29b645
Add sdk/ton/timelock_inspector.go
krebernisak Sep 28, 2025
03fb9e7
Add sdk/ton/transaction.go
krebernisak Sep 28, 2025
2f8aa58
Add sdk/ton/timelock_executor.go
krebernisak Oct 22, 2025
d0e2042
Add sdk/ton/timelock_converter.go
krebernisak Oct 22, 2025
5d5d263
Add sdk/ton/executor.go
krebernisak Oct 22, 2025
f873845
Add signature and proof encoders
krebernisak Oct 22, 2025
a62e949
Update signatures and proof encoders type
krebernisak Oct 22, 2025
14b593c
Add sdk/ton/decoder.go
krebernisak Oct 28, 2025
3a4bac7
Bump github.com/smartcontractkit/chainlink-ton
krebernisak Oct 29, 2025
4fdd08a
Add cselectors.FamilyTon as option
krebernisak Oct 29, 2025
00abbc4
Add changesets and test entrypoints
krebernisak Oct 29, 2025
3ae9437
Add TON e2e setup + TimelockInspectionTestSuite stub (evm)
krebernisak Oct 29, 2025
c5c6e97
Add Configurer option to not send transactions (NONEVM-2862) - TON
krebernisak Oct 29, 2025
293338e
Add sdk/ton/decoded_operation_test.go
krebernisak Oct 29, 2025
ca19e16
Add sdk/ton/decoder_test.go
krebernisak Oct 29, 2025
cb6ab89
Add sdk/ton/encoder_test.go
krebernisak Oct 30, 2025
4e5ad3c
Add sdk/ton/config_transformer_test.go
krebernisak Oct 31, 2025
190fbf7
Add sdk/ton/timelock_converter_test.go
krebernisak Nov 1, 2025
28a3fff
Configure mockery v2
krebernisak Nov 2, 2025
b7ec5a8
Bump chainlink-ton
krebernisak Nov 2, 2025
6a0c6d6
Add wallet.TonAPI mock
krebernisak Nov 2, 2025
ee4c7ec
Add sdk/ton/configurer_test.go
krebernisak Nov 2, 2025
415a890
Add sdk/ton/executor_test.go
krebernisak Nov 3, 2025
7569270
Add sdk/ton/timelock_executor_test.go
krebernisak Nov 3, 2025
d01e39a
Add timelock inspector getRoleMembers helper func
krebernisak Nov 3, 2025
9ada35a
Add sdk/ton/timelock_inspector_test.go
krebernisak Nov 3, 2025
4dc2795
Add sdk/ton/timelock_inspector_test.go
krebernisak Nov 4, 2025
f0d24ee
Reuse common signing test with EVM
krebernisak Nov 7, 2025
9a12cc8
Apply PR feedback
krebernisak Nov 7, 2025
92f518c
Add e2e/tests/ton/set_config.go
krebernisak Nov 7, 2025
f25f421
Load MCMS (chainlink-ton) contracts
krebernisak Nov 7, 2025
1284d67
Implement e2e/tests/ton/set_config.go
krebernisak Nov 9, 2025
16ab6c4
Import code from chainlink-ton
krebernisak Nov 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/long-snakes-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smartcontractkit/mcms": minor
---

Add TON implementation and unit/e2e tests
7 changes: 6 additions & 1 deletion .github/workflows/pull-request-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,16 @@ jobs:
CTF_CONFIGS=../config.sui.toml go test -p=1 -tags=e2e -v ./e2e/tests/... -run=TestSuiSuite || sui_failure=true
echo "::endgroup::"

echo "::group::TON"
CTF_CONFIGS=../config.ton.toml go test -p=1 -tags=e2e -v ./e2e/tests/... -run=TestTONSuite || ton_failure=true
echo "::endgroup::"

[[ -n "${evm_failure}" ]] && echo "🚨 EVM e2e tests failed."
[[ -n "${solana_failure}" ]] && echo "🚨 Solana e2e tests failed."
[[ -n "${aptos_failure}" ]] && echo "🚨 Aptos e2e tests failed."
[[ -n "${sui_failure}" ]] && echo "🚨 Sui e2e tests failed."
[[ -n "${evm_failure}" || -n "${solana_failure}" || -n "${aptos_failure}" || -n "${sui_failure}" ]] && {
[[ -n "${ton_failure}" ]] && echo "🚨 TON e2e tests failed."
[[ -n "${evm_failure}" || -n "${solana_failure}" || -n "${aptos_failure}" || -n "${sui_failure}" || -n "${ton_failure}" ]] && {
exit 1
} || {
echo "Exiting"
Expand Down
18 changes: 18 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ mockname: "{{.InterfaceName}}"
inpackage: false
outpkg: mocks
packages:
github.com/xssnick/tonutils-go/ton/wallet:
config:
all: false
outpkg: "mock_ton"
interfaces:
TonAPI:
config:
dir: "./sdk/ton/mocks"
filename: "wallet.go"
github.com/xssnick/tonutils-go/ton:
config:
all: false
outpkg: "mock_ton"
interfaces:
APIClientWrapped:
config:
dir: "./sdk/ton/mocks"
filename: "api.go"
github.com/smartcontractkit/mcms/sdk:
github.com/smartcontractkit/mcms/sdk/evm:
github.com/smartcontractkit/mcms/sdk/evm/bindings:
Expand Down
15 changes: 15 additions & 0 deletions e2e/config.ton.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[settings]
private_keys = [
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
"0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
]

[ton_config]
chain_id = "-217"
type = "ton"
image = "ghcr.io/neodix42/mylocalton-docker:v3.95"

[ton_config.custom_env]
GLOBAL_ID = "-217"
NEXT_BLOCK_GENERATION_DELAY = "0.5"
95 changes: 95 additions & 0 deletions e2e/tests/common/signing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//go:build e2e
// +build e2e

package common

import (
"encoding/json"
"io"
"os"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/suite"

"github.com/smartcontractkit/mcms"
"github.com/smartcontractkit/mcms/sdk"
"github.com/smartcontractkit/mcms/types"

e2e "github.com/smartcontractkit/mcms/e2e/tests"
testutils "github.com/smartcontractkit/mcms/e2e/utils"
)

// SigningTestSuite tests signing a proposal and converting back to a file
type SigningTestSuite struct {
suite.Suite
e2e.TestSetup
}

// SetupSuite runs before the test suite
func (s *SigningTestSuite) SetupSuite() {
s.TestSetup = *e2e.InitializeSharedTestSetup(s.T())
}

func (s *SigningTestSuite) TestReadAndSign() {
file, err := testutils.ReadFixture("proposal-testing.json")
s.Require().NoError(err, "Failed to read fixture") // Check immediately after ReadFixture
defer func(file *os.File) {
if file != nil {
err = file.Close()
s.Require().NoError(err, "Failed to close file")
}
}(file)
s.Require().NoError(err)
proposal, err := mcms.NewProposal(file)
s.Require().NoError(err)
s.Require().NotNil(proposal)

inspectors := map[types.ChainSelector]sdk.Inspector{} // empty
signable, err := mcms.NewSignable(proposal, inspectors)
s.Require().NoError(err)
signature, err := signable.SignAndAppend(
mcms.NewPrivateKeySigner(testutils.ParsePrivateKey(s.Settings.PrivateKeys[1])),
)
s.Require().NoError(err)
expected := types.Signature{
R: common.HexToHash("0x1ed7807767b09344df63797fa4986ce092730813922ce01563062cf51728ac34"),
S: common.HexToHash("0x556721244f77182c1130a5ee8d78ac7067cef52662dbb57b4132c6ec567ecbc8"),
V: 0,
}
s.Require().Equal(expected, signature)
// Write the proposal back to a temp file
tmpFile, err := os.CreateTemp("", "signed-proposal-*.json")
s.Require().NoError(err)
defer func(name string) {
err = os.Remove(name)
s.Require().NoError(err, "Failed to remove temp file")
}(tmpFile.Name())
err = mcms.WriteProposal(tmpFile, proposal)
s.Require().NoError(err)

// Read back the written proposal
_, err = tmpFile.Seek(0, io.SeekStart)
s.Require().NoError(err, "Failed to reset file pointer to the start")

writtenProposal, err := mcms.NewProposal(tmpFile)
s.Require().NoError(err)

// Validate the appended signature
signedProposalJSON, err := json.Marshal(writtenProposal)
s.Require().NoError(err)

var parsedProposal map[string]any
err = json.Unmarshal(signedProposalJSON, &parsedProposal)
s.Require().NoError(err)

// Ensure the signature is present and matches
signatures, ok := parsedProposal["signatures"].([]any)
s.Require().True(ok, "Signatures field is missing or of the wrong type")
s.Require().NotEmpty(signatures, "Signatures field is empty")

// Verify the appended signature matches the expected value
appendedSignature := signatures[len(signatures)-1].(map[string]any)
s.Require().Equal(expected.R.Hex(), appendedSignature["R"])
s.Require().Equal(expected.S.Hex(), appendedSignature["S"])
s.Require().InDelta(expected.V, appendedSignature["V"], 1e-9)
}
96 changes: 2 additions & 94 deletions e2e/tests/evm/signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,102 +4,10 @@
package evme2e

import (
"encoding/json"
"io"
"os"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
cselectors "github.com/smartcontractkit/chain-selectors"
"github.com/stretchr/testify/suite"

"github.com/smartcontractkit/mcms"
e2e "github.com/smartcontractkit/mcms/e2e/tests"
testutils "github.com/smartcontractkit/mcms/e2e/utils"
"github.com/smartcontractkit/mcms/sdk"
"github.com/smartcontractkit/mcms/sdk/evm"
mcmtypes "github.com/smartcontractkit/mcms/types"
"github.com/smartcontractkit/mcms/e2e/tests/common"
)

// SigningTestSuite tests signing a proposal and converting back to a file
type SigningTestSuite struct {
suite.Suite
e2e.TestSetup

client *ethclient.Client
chainSelector mcmtypes.ChainSelector
}

// SetupSuite runs before the test suite
func (s *SigningTestSuite) SetupSuite() {
s.TestSetup = *e2e.InitializeSharedTestSetup(s.T())

chainDetails, err := cselectors.GetChainDetailsByChainIDAndFamily(s.BlockchainA.Out.ChainID, s.BlockchainA.Out.Family)
s.Require().NoError(err)
s.chainSelector = mcmtypes.ChainSelector(chainDetails.ChainSelector)
}

func (s *SigningTestSuite) TestReadAndSign() {
file, err := testutils.ReadFixture("proposal-testing.json")
s.Require().NoError(err, "Failed to read fixture") // Check immediately after ReadFixture
defer func(file *os.File) {
if file != nil {
err = file.Close()
s.Require().NoError(err, "Failed to close file")
}
}(file)
s.Require().NoError(err)
proposal, err := mcms.NewProposal(file)
s.Require().NoError(err)
s.Require().NotNil(proposal)
inspectors := map[mcmtypes.ChainSelector]sdk.Inspector{
s.chainSelector: evm.NewInspector(s.client),
}
signable, err := mcms.NewSignable(proposal, inspectors)
s.Require().NoError(err)
signature, err := signable.SignAndAppend(
mcms.NewPrivateKeySigner(testutils.ParsePrivateKey(s.Settings.PrivateKeys[1])),
)
s.Require().NoError(err)
expected := mcmtypes.Signature{
R: common.HexToHash("0x1ed7807767b09344df63797fa4986ce092730813922ce01563062cf51728ac34"),
S: common.HexToHash("0x556721244f77182c1130a5ee8d78ac7067cef52662dbb57b4132c6ec567ecbc8"),
V: 0,
}
s.Require().Equal(expected, signature)
// Write the proposal back to a temp file
tmpFile, err := os.CreateTemp("", "signed-proposal-*.json")
s.Require().NoError(err)
defer func(name string) {
err = os.Remove(name)
s.Require().NoError(err, "Failed to remove temp file")
}(tmpFile.Name())
err = mcms.WriteProposal(tmpFile, proposal)
s.Require().NoError(err)

// Read back the written proposal
_, err = tmpFile.Seek(0, io.SeekStart)
s.Require().NoError(err, "Failed to reset file pointer to the start")

writtenProposal, err := mcms.NewProposal(tmpFile)
s.Require().NoError(err)

// Validate the appended signature
signedProposalJSON, err := json.Marshal(writtenProposal)
s.Require().NoError(err)

var parsedProposal map[string]any
err = json.Unmarshal(signedProposalJSON, &parsedProposal)
s.Require().NoError(err)

// Ensure the signature is present and matches
signatures, ok := parsedProposal["signatures"].([]any)
s.Require().True(ok, "Signatures field is missing or of the wrong type")
s.Require().NotEmpty(signatures, "Signatures field is empty")

// Verify the appended signature matches the expected value
appendedSignature := signatures[len(signatures)-1].(map[string]any)
s.Require().Equal(expected.R.Hex(), appendedSignature["R"])
s.Require().Equal(expected.S.Hex(), appendedSignature["S"])
s.Require().InDelta(expected.V, appendedSignature["V"], 1e-9)
common.SigningTestSuite
}
14 changes: 7 additions & 7 deletions e2e/tests/evm/timelock_inspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type TimelockInspectionTestSuite struct {
e2e.TestSetup
}

func (s *TimelockInspectionTestSuite) granRole(role [32]byte, address common.Address) {
func (s *TimelockInspectionTestSuite) grantRole(role [32]byte, address common.Address) {
ctx := context.Background()
tx, err := s.timelockContract.GrantRole(s.auth, role, address)
s.Require().NoError(err)
Expand Down Expand Up @@ -80,23 +80,23 @@ func (s *TimelockInspectionTestSuite) SetupSuite() {
// Proposers
role, err := s.timelockContract.PROPOSERROLE(&bind.CallOpts{})
s.Require().NoError(err)
s.granRole(role, s.signerAddresses[0])
s.grantRole(role, s.signerAddresses[0])
// Executors
role, err = s.timelockContract.EXECUTORROLE(&bind.CallOpts{})
s.Require().NoError(err)
s.granRole(role, s.signerAddresses[0])
s.granRole(role, s.signerAddresses[1])
s.grantRole(role, s.signerAddresses[0])
s.grantRole(role, s.signerAddresses[1])

// By passers
role, err = s.timelockContract.BYPASSERROLE(&bind.CallOpts{})
s.Require().NoError(err)
s.granRole(role, s.signerAddresses[1])
s.grantRole(role, s.signerAddresses[1])

// Cancellers
role, err = s.timelockContract.CANCELLERROLE(&bind.CallOpts{})
s.Require().NoError(err)
s.granRole(role, s.signerAddresses[0])
s.granRole(role, s.signerAddresses[1])
s.grantRole(role, s.signerAddresses[0])
s.grantRole(role, s.signerAddresses[1])
}

// TestGetProposers gets the list of proposers
Expand Down
7 changes: 7 additions & 0 deletions e2e/tests/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
evme2e "github.com/smartcontractkit/mcms/e2e/tests/evm"
solanae2e "github.com/smartcontractkit/mcms/e2e/tests/solana"
suie2e "github.com/smartcontractkit/mcms/e2e/tests/sui"
tone2e "github.com/smartcontractkit/mcms/e2e/tests/ton"
)

func TestEVMSuite(t *testing.T) {
Expand Down Expand Up @@ -40,3 +41,9 @@ func TestSuiSuite(t *testing.T) {
suite.Run(t, new(suie2e.TimelockCancelProposalTestSuite))
suite.Run(t, new(suie2e.MCMSUserUpgradeTestSuite))
}

func TestTONSuite(t *testing.T) {
// suite.Run(t, new(tone2e.TimelockInspectionTestSuite))
suite.Run(t, new(tone2e.SigningTestSuite))
suite.Run(t, new(tone2e.SetConfigTestSuite))
}
32 changes: 32 additions & 0 deletions e2e/tests/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (
"github.com/gagliardetto/solana-go/rpc/ws"
"github.com/joho/godotenv"
"github.com/stretchr/testify/require"
"github.com/xssnick/tonutils-go/ton"

tonchain "github.com/smartcontractkit/chainlink-ton/pkg/ton/chain"

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

Settings struct {
PrivateKeys []string `toml:"private_keys"`
Expand All @@ -57,6 +61,8 @@ type TestSetup struct {
AptosBlockchain *blockchain.Output
SuiClient sui.ISuiAPI
SuiBlockchain *blockchain.Output
TonClient *ton.APIClient
TonBlockchain *blockchain.Output
Config
}

Expand Down Expand Up @@ -203,6 +209,30 @@ func InitializeSharedTestSetup(t *testing.T) *TestSetup {
t.Logf("Initialized Sui RPC client @ %s", nodeUrl)
}

var (
tonClient *ton.APIClient
tonBlockchainOutput *blockchain.Output
)
if in.TonChain != nil {
// Use blockchain network setup (fallback)
ports := freeport.GetN(t, 2)
port := ports[0]
faucetPort := ports[1]
in.TonChain.Port = strconv.Itoa(port)
in.TonChain.FaucetPort = strconv.Itoa(faucetPort)

tonBlockchainOutput, err = blockchain.NewBlockchainNetwork(in.TonChain)
require.NoError(t, err, "Failed to initialize TON blockchain")

nodeUrl := tonBlockchainOutput.Nodes[0].ExternalHTTPUrl
pool, err := tonchain.CreateLiteserverConnectionPool(ctx, nodeUrl)
require.NoError(t, err, "Failed to initialize TON client - failed to create liteserver connection pool")
tonClient = ton.NewAPIClient(pool, ton.ProofCheckPolicyFast)

// Test liveness, will also fetch ChainID
t.Logf("Initialized TON RPC client @ %s", nodeUrl)
}

sharedSetup = &TestSetup{
ClientA: ethClientA,
ClientB: ethClientB,
Expand All @@ -213,6 +243,8 @@ func InitializeSharedTestSetup(t *testing.T) *TestSetup {
AptosBlockchain: aptosBlockchainOutput,
SuiClient: suiClient,
SuiBlockchain: suiBlockchainOutput,
TonClient: tonClient,
TonBlockchain: tonBlockchainOutput,
Config: *in,
}
})
Expand Down
Loading