Skip to content

Commit 4f868e5

Browse files
Add Sui SDK (#424)
### What - Add implementation for Sui - Add e2e tests - [x] Unit Tests (in progress) Closes https://smartcontract-it.atlassian.net/browse/NONEVM-2379 Pending: https://smartcontract-it.atlassian.net/browse/NONEVM-2493 --------- Co-authored-by: JohnChangUK <johnchang9094@gmail.com>
1 parent b630bd1 commit 4f868e5

Some content is hidden

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

55 files changed

+26970
-132
lines changed

.changeset/cold-pans-grow.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 Sui implementation and e2e tests

.github/workflows/pull-request-main.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656
- name: Build and test
5757
uses: smartcontractkit/.github/actions/ci-test-go@ci-test-go/1.0.0
5858
with:
59-
go-test-cmd: go test -coverpkg=../... -coverprofile=coverage.txt -test.gocoverdir=/tmp ./...
59+
go-test-cmd: go test -v -cover -coverpkg=./... -coverprofile=coverage.txt ./...
6060
use-go-cache: true
6161

6262
ci-test-e2e:
@@ -80,6 +80,15 @@ jobs:
8080
with:
8181
CLI_VERSION: 7.0.0
8282

83+
- name: Install Sui CLI
84+
shell: bash
85+
run: |
86+
SUI_VERSION="1.50.1"
87+
curl -L "https://github.com/MystenLabs/sui/releases/download/mainnet-v${SUI_VERSION}/sui-mainnet-v${SUI_VERSION}-ubuntu-x86_64.tgz" -o sui.tgz
88+
tar -xzf sui.tgz
89+
sudo mv sui /usr/local/bin/
90+
sui --version
91+
8392
- name: Checkout code
8493
uses: actions/checkout@v4
8594

.mockery.yaml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ packages:
6565
all: false
6666
outpkg: "mock_aptossdk"
6767
replace-type:
68-
- "github.com/aptos-labs/aptos-go-sdk/internal/types.AccountAddress=aptos:github.com/aptos-labs/aptos-go-sdk.AccountAddress"
68+
- "github.com/aptos-labs/aptos-go-sdk/internal/types.AccountAddress=aptos:github.com/aptos-labs/aptos-go-sdk.AccountAddress"
6969
interfaces:
7070
AptosRpcClient:
7171
config:
@@ -75,6 +75,50 @@ packages:
7575
config:
7676
dir: "./sdk/aptos/mocks/aptos"
7777
filename: "transactionsigner.go"
78+
github.com/block-vision/sui-go-sdk/sui:
79+
config:
80+
all: false
81+
outpkg: "mock_sui"
82+
interfaces:
83+
ISuiAPI:
84+
config:
85+
dir: "./sdk/sui/mocks/sui"
86+
filename: "isuiapi.go"
87+
github.com/smartcontractkit/chainlink-sui/bindings/utils:
88+
config:
89+
all: false
90+
outpkg: "mock_bindutils"
91+
interfaces:
92+
SuiSigner:
93+
config:
94+
dir: "./sdk/sui/mocks/bindutils"
95+
filename: "suisigner.go"
96+
github.com/smartcontractkit/chainlink-sui/bindings/bind:
97+
config:
98+
all: false
99+
outpkg: "mock_bindutils"
100+
interfaces:
101+
IBoundContract:
102+
config:
103+
dir: "./sdk/sui/mocks/bindutils"
104+
filename: "iboundcontract.go"
105+
github.com/smartcontractkit/chainlink-sui/bindings/generated/mcms/mcms:
106+
config:
107+
all: false
108+
outpkg: "mock_module_mcms"
109+
interfaces:
110+
IMcms:
111+
config:
112+
dir: "./sdk/sui/mocks/mcms"
113+
filename: "imcms.go"
114+
IMcmsDevInspect:
115+
config:
116+
dir: "./sdk/sui/mocks/mcms"
117+
filename: "imcmsdevinspect.go"
118+
McmsEncoder:
119+
config:
120+
dir: "./sdk/sui/mocks/mcms"
121+
filename: "mcmsencoder.go"
78122

79123
# Required to fix the following deprecation warning:
80124
# https://vektra.github.io/mockery/v2.48/deprecations/#issue-845-fix

e2e/config.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ external_program_cpi_stub = "2zZwzyptLqwFJFEFxjPvrdhiGpH9pJ3MfrrmZX6NTKxm"
4242

4343
[aptos_config]
4444
type = "aptos"
45+
46+
47+
[sui_config]
48+
type = "sui"

e2e/tests/runner_test.go

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

1617
func TestEVMSuite(t *testing.T) {
@@ -29,3 +30,11 @@ func TestSolanaSuite(t *testing.T) {
2930
func TestAptosSuite(t *testing.T) {
3031
suite.Run(t, new(aptose2e.AptosTestSuite))
3132
}
33+
34+
func TestSuiSuite(t *testing.T) {
35+
suite.Run(t, new(suie2e.TimelockProposalTestSuite))
36+
suite.Run(t, new(suie2e.InspectionTestSuite))
37+
suite.Run(t, new(suie2e.TimelockInspectionTestSuite))
38+
suite.Run(t, new(suie2e.SetRootTestSuite))
39+
suite.Run(t, new(suie2e.MCMSUserTestSuite))
40+
}

e2e/tests/setup.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"testing"
1313

1414
"github.com/aptos-labs/aptos-go-sdk"
15+
"github.com/block-vision/sui-go-sdk/sui"
1516
"github.com/ethereum/go-ethereum/ethclient"
1617
"github.com/gagliardetto/solana-go/rpc"
1718
"github.com/gagliardetto/solana-go/rpc/ws"
@@ -20,6 +21,7 @@ import (
2021

2122
"github.com/smartcontractkit/chainlink-testing-framework/framework"
2223
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
24+
"github.com/smartcontractkit/freeport"
2325
)
2426

2527
// Shared test setup
@@ -36,7 +38,9 @@ type Config struct {
3638
BlockchainB *blockchain.Input `toml:"evm_config_b"`
3739
SolanaChain *blockchain.Input `toml:"solana_config"`
3840
AptosChain *blockchain.Input `toml:"aptos_config"`
39-
Settings struct {
41+
SuiChain *blockchain.Input `toml:"sui_config"`
42+
43+
Settings struct {
4044
PrivateKeys []string `toml:"private_keys"`
4145
} `toml:"settings"`
4246
}
@@ -50,6 +54,8 @@ type TestSetup struct {
5054
AptosRPCClient *aptos.NodeClient
5155
SolanaBlockchain *blockchain.Output
5256
AptosBlockchain *blockchain.Output
57+
SuiClient sui.ISuiAPI
58+
SuiBlockchain *blockchain.Output
5359
Config
5460
}
5561

@@ -101,14 +107,14 @@ func InitializeSharedTestSetup(t *testing.T) *TestSetup {
101107
}
102108

103109
// Initialize Ethereum client A
104-
wsURLA := ethBlockChainOutputA.Nodes[0].HostWSUrl
110+
wsURLA := ethBlockChainOutputA.Nodes[0].ExternalWSUrl
105111
ethClientA, err = ethclient.DialContext(context.Background(), wsURLA)
106112
if err != nil {
107113
t.Fatalf("Failed to initialize Ethereum client: %v", err)
108114
}
109115

110116
// Initialize Ethereum client B
111-
wsURLB := ethBlockChainOutputB.Nodes[0].HostWSUrl
117+
wsURLB := ethBlockChainOutputB.Nodes[0].ExternalWSUrl
112118
ethClientB, err = ethclient.DialContext(context.Background(), wsURLB)
113119
if err != nil {
114120
t.Fatalf("Failed to initialize Ethereum client: %v", err)
@@ -129,8 +135,8 @@ func InitializeSharedTestSetup(t *testing.T) *TestSetup {
129135
t.Fatalf("Failed to initialize solana blockchain: %v", err)
130136
}
131137

132-
solanaClient = rpc.New(solanaBlockChainOutput.Nodes[0].HostHTTPUrl)
133-
solanaWsClient, err = ws.Connect(ctx, solanaBlockChainOutput.Nodes[0].HostWSUrl)
138+
solanaClient = rpc.New(solanaBlockChainOutput.Nodes[0].ExternalHTTPUrl)
139+
solanaWsClient, err = ws.Connect(ctx, solanaBlockChainOutput.Nodes[0].ExternalWSUrl)
134140
if err != nil {
135141
t.Fatalf("Failed to initialize Solana WS client: %v", err)
136142
}
@@ -157,17 +163,38 @@ func InitializeSharedTestSetup(t *testing.T) *TestSetup {
157163
aptosBlockchainOutput, err = blockchain.NewBlockchainNetwork(in.AptosChain)
158164
require.NoError(t, err, "Failed to initialize Aptos blockchain")
159165

160-
nodeUrl := fmt.Sprintf("%v/v1", aptosBlockchainOutput.Nodes[0].HostHTTPUrl)
166+
nodeUrl := fmt.Sprintf("%v/v1", aptosBlockchainOutput.Nodes[0].ExternalHTTPUrl)
161167

162168
aptosClient, err = aptos.NewNodeClient(nodeUrl, 0)
163169
require.NoError(t, err, "Failed to initialize Aptos RPC client")
164170

165171
// Test liveness, will also fetch ChainID
166172
t.Logf("Initialized Aptos RPC client @ %s", nodeUrl)
167-
info, err := aptosClient.Info()
168-
require.NoError(t, err, "Failed to get Aptos node info")
169-
require.NotEmpty(t, info.LedgerVersionStr)
170-
in.AptosChain.ChainID = strconv.FormatUint(uint64(info.ChainId), 10)
173+
nodeInfo, infoErr := aptosClient.Info()
174+
require.NoError(t, infoErr, "Failed to get Aptos node info")
175+
require.NotEmpty(t, nodeInfo.LedgerVersionStr)
176+
in.AptosChain.ChainID = strconv.FormatUint(uint64(nodeInfo.ChainId), 10)
177+
}
178+
179+
var (
180+
suiClient sui.ISuiAPI
181+
suiBlockchainOutput *blockchain.Output
182+
)
183+
if in.SuiChain != nil {
184+
ports := freeport.GetN(t, 2)
185+
port := ports[0]
186+
faucetPort := ports[1]
187+
in.SuiChain.Port = strconv.Itoa(port)
188+
in.SuiChain.FaucetPort = strconv.Itoa(faucetPort)
189+
190+
suiBlockchainOutput, err = blockchain.NewBlockchainNetwork(in.SuiChain)
191+
require.NoError(t, err, "Failed to initialize Sui blockchain")
192+
193+
nodeUrl := suiBlockchainOutput.Nodes[0].ExternalHTTPUrl
194+
suiClient = sui.NewSuiClient(nodeUrl)
195+
196+
// Test liveness, will also fetch ChainID
197+
t.Logf("Initialized Sui RPC client @ %s", nodeUrl)
171198
}
172199

173200
sharedSetup = &TestSetup{
@@ -178,6 +205,8 @@ func InitializeSharedTestSetup(t *testing.T) *TestSetup {
178205
AptosRPCClient: aptosClient,
179206
SolanaBlockchain: solanaBlockChainOutput,
180207
AptosBlockchain: aptosBlockchainOutput,
208+
SuiClient: suiClient,
209+
SuiBlockchain: suiBlockchainOutput,
181210
Config: *in,
182211
}
183212
})

e2e/tests/sui/common.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//go:build e2e
2+
3+
package sui
4+
5+
import (
6+
"github.com/block-vision/sui-go-sdk/signer"
7+
"github.com/block-vision/sui-go-sdk/sui"
8+
"github.com/stretchr/testify/suite"
9+
10+
cselectors "github.com/smartcontractkit/chain-selectors"
11+
12+
"github.com/smartcontractkit/chainlink-sui/bindings/bind"
13+
modulemcms "github.com/smartcontractkit/chainlink-sui/bindings/generated/mcms/mcms"
14+
modulemcmsaccount "github.com/smartcontractkit/chainlink-sui/bindings/generated/mcms/mcms_account"
15+
modulemcmsuser "github.com/smartcontractkit/chainlink-sui/bindings/generated/mcms/mcms_user"
16+
"github.com/smartcontractkit/chainlink-sui/bindings/packages/mcms"
17+
mcmsuser "github.com/smartcontractkit/chainlink-sui/bindings/packages/mcms/mcms_user"
18+
bindutils "github.com/smartcontractkit/chainlink-sui/bindings/utils"
19+
20+
e2e "github.com/smartcontractkit/mcms/e2e/tests"
21+
"github.com/smartcontractkit/mcms/types"
22+
)
23+
24+
type SuiTestSuite struct {
25+
suite.Suite
26+
e2e.TestSetup
27+
28+
client sui.ISuiAPI
29+
signer bindutils.SuiSigner
30+
31+
chainSelector types.ChainSelector
32+
33+
// MCMS
34+
mcmsPackageID string
35+
mcms modulemcms.IMcms
36+
mcmsObj string
37+
timelockObj string
38+
depStateObj string
39+
registryObj string
40+
accountObj string
41+
ownerCapObj string
42+
43+
// MCMS Account
44+
mcmsAccount modulemcmsaccount.IMcmsAccount
45+
46+
// MCMS User
47+
mcmsUserPackageId string
48+
mcmsUser modulemcmsuser.IMcmsUser
49+
mcmsUserOwnerCapObj string
50+
51+
// State Object passed into `mcms_entrypoint`
52+
stateObj string
53+
}
54+
55+
func (s *SuiTestSuite) SetupSuite() {
56+
s.TestSetup = *e2e.InitializeSharedTestSetup(s.T())
57+
58+
account := s.SuiBlockchain.NetworkSpecificData.SuiAccount
59+
60+
// Create a Sui signer from the mnemonic using the block-vision SDK
61+
signerAccount, err := signer.NewSignertWithMnemonic(account.Mnemonic)
62+
s.Require().NoError(err, "Failed to create signer from mnemonic")
63+
64+
// Get the private key from the signer
65+
privateKey := signerAccount.PriKey
66+
testSigner := NewTestPrivateKeySigner(privateKey)
67+
68+
// Set up Sui client
69+
s.client = s.SuiClient
70+
// TODO: Find funded accounts
71+
s.signer = testSigner
72+
s.chainSelector = types.ChainSelector(cselectors.SUI_TESTNET.Selector)
73+
}
74+
75+
func (s *SuiTestSuite) DeployMCMSContract() {
76+
gasBudget := uint64(300_000_000)
77+
mcmsPackage, tx, err := mcms.PublishMCMS(s.T().Context(), &bind.CallOpts{
78+
Signer: s.signer,
79+
GasBudget: &gasBudget,
80+
WaitForExecution: true,
81+
}, s.client)
82+
s.Require().NoError(err, "Failed to publish MCMS package")
83+
s.mcmsPackageID = mcmsPackage.Address()
84+
s.mcms = mcmsPackage.MCMS()
85+
86+
mcmsObject, err := bind.FindObjectIdFromPublishTx(*tx, "mcms", "MultisigState")
87+
s.Require().NoError(err, "Failed to find object IDs in publish tx")
88+
timelockObj, err := bind.FindObjectIdFromPublishTx(*tx, "mcms", "Timelock")
89+
s.Require().NoError(err, "Failed to find object IDs in publish tx")
90+
depState, err := bind.FindObjectIdFromPublishTx(*tx, "mcms_deployer", "DeployerState")
91+
s.Require().NoError(err, "Failed to find object IDs in publish tx")
92+
reg, err := bind.FindObjectIdFromPublishTx(*tx, "mcms_registry", "Registry")
93+
s.Require().NoError(err, "Failed to find object IDs in publish tx")
94+
acc, err := bind.FindObjectIdFromPublishTx(*tx, "mcms_account", "AccountState")
95+
s.Require().NoError(err, "Failed to find object IDs in publish tx")
96+
ownCap, err := bind.FindObjectIdFromPublishTx(*tx, "mcms_account", "OwnerCap")
97+
s.Require().NoError(err, "Failed to find object IDs in publish tx")
98+
99+
s.mcmsObj = mcmsObject
100+
s.timelockObj = timelockObj
101+
s.depStateObj = depState
102+
s.registryObj = reg
103+
s.accountObj = acc
104+
s.ownerCapObj = ownCap
105+
106+
s.mcmsAccount, err = modulemcmsaccount.NewMcmsAccount(s.mcmsPackageID, s.client)
107+
s.Require().NoError(err, "Failed to create MCMS account instance")
108+
}
109+
110+
func (s *SuiTestSuite) DeployMCMSUserContract() {
111+
gasBudget := uint64(300_000_000)
112+
signerAddress, err := s.signer.GetAddress()
113+
s.Require().NoError(err, "Failed to get address")
114+
115+
mcmsUserPackage, tx, err := mcmsuser.PublishMCMSUser(s.T().Context(), &bind.CallOpts{
116+
Signer: s.signer,
117+
GasBudget: &gasBudget,
118+
WaitForExecution: true,
119+
}, s.client, s.mcmsPackageID, signerAddress)
120+
s.Require().NoError(err, "Failed to publish MCMS user package")
121+
122+
s.mcmsUserPackageId = mcmsUserPackage.Address()
123+
s.mcmsUser = mcmsUserPackage.MCMSUser()
124+
125+
userDataObj, err := bind.FindObjectIdFromPublishTx(*tx, "mcms_user", "UserData")
126+
s.Require().NoError(err, "Failed to find object IDs in publish tx")
127+
mcmsUserOwnerCapObj, err := bind.FindObjectIdFromPublishTx(*tx, "mcms_user", "OwnerCap")
128+
s.Require().NoError(err, "Failed to find object IDs in publish tx")
129+
130+
s.mcmsUserOwnerCapObj = mcmsUserOwnerCapObj
131+
s.stateObj = userDataObj
132+
133+
// For executing, We need to register OwnerCap with MCMS
134+
{
135+
tx, err := s.mcmsUser.RegisterMcmsEntrypoint(
136+
s.T().Context(),
137+
&bind.CallOpts{
138+
Signer: s.signer,
139+
WaitForExecution: true,
140+
},
141+
bind.Object{Id: s.mcmsUserOwnerCapObj},
142+
bind.Object{Id: s.registryObj},
143+
bind.Object{Id: s.stateObj},
144+
)
145+
s.Require().NoError(err, "Failed to register with MCMS")
146+
s.Require().NotEmpty(tx, "Transaction should not be empty")
147+
148+
s.T().Logf("✅ Registered with MCMS in tx: %s", tx.Digest)
149+
}
150+
}
151+
152+
func Must[T any](t T, err error) T {
153+
if err != nil {
154+
panic(err)
155+
}
156+
157+
return t
158+
}

0 commit comments

Comments
 (0)