Skip to content

Commit f3545dd

Browse files
authored
feat: improve ledger testing for solana (#272)
Improves ledger testing by adding calls to SetRoot and including a solana tx in the proposal. The SetRoot calls ensures that the signature was accepted by the contract and not only that the signature was appended to the proposal. ## AI Summary This pull request includes several changes to the `e2e` and `sdk/solana` directories, focusing on improving the handling of Solana and EVM chains in the MCMS (Many Chain Multi-Sig) system. The most important changes include adding support for Solana transactions in end-to-end tests, refactoring the initialization of the MCM program, and updating the executor creation for Solana chains. ### Support for Solana Transactions in E2E Tests: * Added a new Solana transaction to the `proposal-testing.json` fixture to support end-to-end tests involving Solana chains. * Updated the `ledger_test.go` file to include Solana-specific imports and added a new test suite `ManualLedgerSigningTestSuite` to test manual ledger signing functionality for both EVM and Solana chains. [[1]](diffhunk://#diff-046f7b9890699bc5c0782f4c739ef15b5f8be4727b0c2f7cf932fa171d9f514dL1-R158) [[2]](diffhunk://#diff-046f7b9890699bc5c0782f4c739ef15b5f8be4727b0c2f7cf932fa171d9f514dL67-R237) [[3]](diffhunk://#diff-046f7b9890699bc5c0782f4c739ef15b5f8be4727b0c2f7cf932fa171d9f514dR263-R266) ### Refactoring Initialization of MCM Program: * Refactored the `SetupMCM` function to a public helper function `InitializeMCMProgram` in `common.go` to initialize the MCM program with the given PDA seed. This change was applied across multiple test files to standardize the initialization process. [[1]](diffhunk://#diff-08c1332aeae8454816329010fb4af1c91528b8059a04739329f4cdf8218f06d0L133-R184) [[2]](diffhunk://#diff-e1fbbd2697b9af2cb0cd1138b6b678e36a35ed1271f94c24fe9489d1ed159cebL32-R32) [[3]](diffhunk://#diff-d45f8b1fc9ce32967491b3200326fd5fde2a829bc865c8e52cf6a09778fcc5f8L20-R20) [[4]](diffhunk://#diff-7e3c7e759e7d2c3b7900d53fd6411bbcbcead44a2d1e6ca7b14b85f32574bc64L21-R21) [[5]](diffhunk://#diff-9b550f2222fbbcfc8af5b5ed60c0b4567f815d9aeb1aecd54ab08b864fd483c0L25-R25) [[6]](diffhunk://#diff-715e94216372225cfc4c7393c82b553f0b784dcd30d21f067bade69b1ef7c38cL42-R42) ### Updating Executor Creation for Solana Chains: * Modified the `NewExecutor` function in `executor.go` to change the order of parameters, placing the encoder parameter first. Corresponding changes were made in the relevant test files to reflect this update. [[1]](diffhunk://#diff-2890f328acff17cf8abc91d22a143ac8a1d955b60ec675f6c57770df1d20f860L33-R33) [[2]](diffhunk://#diff-350968eef1e410e9af2dba47013590938a30c4391bed93d2329c376a33118331L32-R32) [[3]](diffhunk://#diff-350968eef1e410e9af2dba47013590938a30c4391bed93d2329c376a33118331L173-R173) These changes enhance the integration and testing capabilities for Solana chains within the MCMS system, ensuring better support for multi-chain operations.
1 parent 61c018e commit f3545dd

13 files changed

+279
-85
lines changed

e2e/fixtures/proposal-testing.json

+20-1
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,40 @@
88
"3379446385462418246": {
99
"startingOpCount": 0,
1010
"mcmAddress": "0x123"
11+
},
12+
"16423721717087811551": {
13+
"mcmAddress": "6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX.mcms-instance-1"
1114
}
1215
},
1316
"description": "proposal for e2e tests",
1417
"operations": [
1518
{
1619
"chainSelector": 3379446385462418246,
1720
"transaction": {
18-
"contractType": "some-contract",
21+
"contractType": "some-evm-contract",
1922
"tags": null,
2023
"to": "0x1c",
2124
"data": "MHgw",
2225
"additionalFields": {
2326
"value": 100
2427
}
2528
}
29+
},
30+
{
31+
"chainSelector": 16423721717087811551,
32+
"transaction": {
33+
"contractType": "some-solana-program",
34+
"tags": null,
35+
"to": "4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ",
36+
"data": "D2DZq3wEcfN0ZXN0LXRpbWVsb2NrY29udmVydGVyAAAAAAAAAAAAAFF3oPhB0oSxY3h5BmiszWNGktjysKAxcPliXCS0aGa3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6QyuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAA=",
37+
"additionalFields": {
38+
"accounts": [
39+
{
40+
"PublicKey": "4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ"
41+
}
42+
]
43+
}
44+
}
2645
}
2746
]
2847
}

e2e/ledger/ledger_test.go

+205-40
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,180 @@
1-
//go:build ledger && !e2e
2-
// +build ledger,!e2e
1+
//go:build e2e
2+
// +build e2e
33

44
package ledger
55

66
import (
7+
"context"
78
"log"
9+
"math/big"
810
"os"
911
"testing"
1012

1113
"github.com/ethereum/go-ethereum/accounts"
14+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
1215
"github.com/ethereum/go-ethereum/accounts/usbwallet"
13-
"github.com/stretchr/testify/require"
16+
"github.com/ethereum/go-ethereum/common"
17+
gethTypes "github.com/ethereum/go-ethereum/core/types"
18+
"github.com/ethereum/go-ethereum/crypto"
19+
"github.com/gagliardetto/solana-go"
20+
cselectors "github.com/smartcontractkit/chain-selectors"
21+
"github.com/stretchr/testify/suite"
1422

1523
"github.com/smartcontractkit/mcms"
24+
e2e "github.com/smartcontractkit/mcms/e2e/tests"
25+
solanae2e "github.com/smartcontractkit/mcms/e2e/tests/solana"
1626
testutils "github.com/smartcontractkit/mcms/e2e/utils"
1727
"github.com/smartcontractkit/mcms/sdk"
28+
"github.com/smartcontractkit/mcms/sdk/evm"
29+
"github.com/smartcontractkit/mcms/sdk/evm/bindings"
30+
solanamcms "github.com/smartcontractkit/mcms/sdk/solana"
1831
"github.com/smartcontractkit/mcms/types"
1932
)
2033

34+
func TestManualLedgerSigningSuite(t *testing.T) {
35+
var runLedgerSuite = os.Getenv("RUN_LEDGER_SUITE") == "true"
36+
if !runLedgerSuite {
37+
t.Skip("Skipping LedgerSuite. Set RUN_LEDGER_SUITE=true to run it.")
38+
}
39+
suite.Run(t, new(ManualLedgerSigningTestSuite))
40+
}
41+
42+
// ManualLedgerSigningTestSuite tests the manual ledger signing functionality
43+
type ManualLedgerSigningTestSuite struct {
44+
suite.Suite
45+
authEVM *bind.TransactOpts
46+
authSolana solana.PrivateKey
47+
chainSelectorEVM types.ChainSelector
48+
chainSelectorSolana types.ChainSelector
49+
e2e.TestSetup
50+
}
51+
52+
func (s *ManualLedgerSigningTestSuite) deployMCMContractEVM(ctx context.Context) (common.Address, *bindings.ManyChainMultiSig) {
53+
// Set auth keys
54+
chainID, ok := new(big.Int).SetString(s.BlockchainA.Out.ChainID, 10)
55+
privateKeyHex := s.Settings.PrivateKeys[0]
56+
privateKey, err := crypto.HexToECDSA(privateKeyHex[2:]) // Strip "0x" prefix
57+
s.Require().NoError(err, "Failed to parse private key")
58+
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
59+
s.Require().NoError(err, "Failed to create transactor")
60+
s.authEVM = auth
61+
62+
// Deploy MCMS contract
63+
s.Require().True(ok, "Failed to parse chain ID")
64+
mcmsAddress, tx, instance, err := bindings.DeployManyChainMultiSig(s.authEVM, s.Client)
65+
s.Require().NoError(err, "Failed to deploy contract")
66+
67+
// Wait for the transaction to be mined
68+
receipt, err := bind.WaitMined(ctx, s.Client, tx)
69+
s.Require().NoError(err, "Failed to mine deployment transaction")
70+
s.Require().Equal(gethTypes.ReceiptStatusSuccessful, receipt.Status)
71+
72+
return mcmsAddress, instance
73+
}
74+
func (s *ManualLedgerSigningTestSuite) initializeMCMSolana(ctx context.Context) (solana.PublicKey, string) {
75+
var MCMProgramID = solana.MustPublicKeyFromBase58(s.SolanaChain.SolanaPrograms["mcm"])
76+
solanae2e.InitializeMCMProgram(
77+
ctx,
78+
s.T(),
79+
s.SolanaClient,
80+
MCMProgramID,
81+
mcmInstanceSeed,
82+
uint64(s.chainSelectorSolana),
83+
)
84+
85+
programID := solana.MustPublicKeyFromBase58(s.SolanaChain.SolanaPrograms["mcm"])
86+
contractAddress := solanamcms.ContractAddress(programID, mcmInstanceSeed)
87+
88+
return programID, contractAddress
89+
}
90+
91+
// setRootEVM initializes and MCMS contract and calls set root on it
92+
func (s *ManualLedgerSigningTestSuite) setRootEVM(
93+
ctx context.Context,
94+
ledgerAccount common.Address,
95+
proposal *mcms.Proposal,
96+
instance *bindings.ManyChainMultiSig,
97+
executorsMap map[types.ChainSelector]sdk.Executor,
98+
) {
99+
mcmConfig := types.Config{Quorum: 1, Signers: []common.Address{ledgerAccount}}
100+
101+
// set config
102+
configurer := evm.NewConfigurer(s.Client, s.authEVM)
103+
tx, err := configurer.SetConfig(ctx, instance.Address().Hex(), &mcmConfig, true)
104+
s.Require().NoError(err, "Failed to set contract configuration")
105+
_, err = bind.WaitMined(ctx, s.Client, tx.RawTransaction.(*gethTypes.Transaction))
106+
s.Require().NoError(err, "Failed to mine set config transaction")
107+
108+
// set root
109+
executable, err := mcms.NewExecutable(proposal, executorsMap)
110+
s.Require().NoError(err)
111+
tx, err = executable.SetRoot(ctx, s.chainSelectorEVM)
112+
s.Require().NoError(err)
113+
s.Require().NotEmpty(tx.Hash)
114+
}
115+
116+
const privateKeyLedger = "DmPfeHBC8Brf8s5qQXi25bmJ996v6BHRtaLc6AH51yFGSqQpUMy1oHkbbXobPNBdgGH2F29PAmoq9ZZua4K9vCc"
117+
118+
var mcmInstanceSeed = [32]byte{'t', 'e', 's', 't', '-', 's', 'e', 't', 'r', 'o', 'o', 't', 'l', 'e', 'd', 'g', 'e', 'r'}
119+
120+
// setRootSolana initializes and MCMS contract and calls set root on it
121+
func (s *ManualLedgerSigningTestSuite) setRootSolana(
122+
ctx context.Context,
123+
mcmProgramID solana.PublicKey,
124+
ledgerAccount common.Address,
125+
proposal *mcms.Proposal,
126+
executorsMap map[types.ChainSelector]sdk.Executor) {
127+
// set config
128+
mcmAddress := solanamcms.ContractAddress(mcmProgramID, mcmInstanceSeed)
129+
mcmConfig := types.Config{Quorum: 1, Signers: []common.Address{ledgerAccount}}
130+
configurer := solanamcms.NewConfigurer(s.SolanaClient, s.authSolana, s.chainSelectorSolana)
131+
_, err := configurer.SetConfig(ctx, mcmAddress, &mcmConfig, true)
132+
s.Require().NoError(err)
133+
134+
// set root
135+
executable, err := mcms.NewExecutable(proposal, executorsMap)
136+
s.Require().NoError(err)
137+
tx, err := executable.SetRoot(ctx, s.chainSelectorSolana)
138+
s.Require().NoError(err)
139+
140+
// --- assert ---
141+
_, err = solana.SignatureFromBase58(tx.Hash)
142+
s.Require().NoError(err)
143+
}
144+
21145
// This test uses real ledger connected device. Remember to connect, unlock it and open ethereum app.
22-
func TestManualLedgerSigning(t *testing.T) { //nolint:paralleltest
23-
t.Log("Starting manual Ledger signing test...")
146+
func (s *ManualLedgerSigningTestSuite) TestManualLedgerSigning() {
147+
s.T().Log("Starting manual Ledger signing test...")
148+
149+
ctx := context.Background()
150+
s.TestSetup = *e2e.InitializeSharedTestSetup(s.T())
151+
152+
chainDetailsEVM, err := cselectors.GetChainDetailsByChainIDAndFamily(s.BlockchainA.Out.ChainID, s.BlockchainA.Out.Family)
153+
s.Require().NoError(err)
154+
chainDetailsSolana, err := cselectors.GetChainDetailsByChainIDAndFamily(s.SolanaChain.ChainID, s.SolanaChain.Out.Family)
155+
s.Require().NoError(err)
156+
157+
s.chainSelectorEVM = types.ChainSelector(chainDetailsEVM.ChainSelector)
158+
s.chainSelectorSolana = types.ChainSelector(chainDetailsSolana.ChainSelector)
24159

25160
// Step 1: Detect and connect to the Ledger device
26-
t.Log("Checking for connected Ledger devices...")
161+
s.T().Log("Checking for connected Ledger devices...")
27162
ledgerHub, err := usbwallet.NewLedgerHub()
28-
require.NoError(t, err, "Failed to initialize Ledger Hub")
163+
s.Require().NoError(err, "Failed to initialize Ledger Hub")
29164

30165
wallets := ledgerHub.Wallets()
31-
require.NotEmpty(t, wallets, "No Ledger devices found. Please connect your Ledger and unlock it.")
166+
s.Require().NotEmpty(wallets, "No Ledger devices found. Please connect your Ledger and unlock it.")
32167

33168
// Use the first available wallet
34169
wallet := wallets[0]
35-
t.Logf("Found Ledger device: %s\n", wallet.URL().Path)
170+
s.T().Logf("Found Ledger device: %s\n", wallet.URL().Path)
36171

37172
// Open the wallet
38-
t.Log("Opening Ledger wallet...")
173+
s.T().Log("Opening Ledger wallet...")
39174
err = wallet.Open("")
40-
require.NoError(t, err, "Failed to open Ledger wallet")
175+
s.Require().NoError(err, "Failed to open Ledger wallet")
41176

42-
t.Log("Ledger wallet opened successfully.")
177+
s.T().Log("Ledger wallet opened successfully.")
43178

44179
// Define the derivation path
45180
derivationPath := accounts.DefaultBaseDerivationPath
@@ -49,52 +184,82 @@ func TestManualLedgerSigning(t *testing.T) { //nolint:paralleltest
49184
if err != nil {
50185
log.Fatalf("Failed to derive account: %v", err)
51186
}
52-
t.Logf("Derived account: %s\n", account.Address.Hex())
187+
s.T().Logf("Derived account: %s\n", account.Address.Hex())
53188
accountPublicKey := account.Address.Hex()
54189
wallet.Close()
55190

56-
// Step 2: Load a proposal from a fixture
57-
t.Log("Loading proposal from fixture...")
191+
// Step 2: Deploy and initialize solana and EVM MCMs
192+
mcmsAddressEVM, mcmInstanceEVM := s.deployMCMContractEVM(ctx)
193+
mcmProgramID, contractIDSolana := s.initializeMCMSolana(ctx)
194+
195+
// Step 3: Load a proposal from a fixture
196+
s.T().Log("Loading proposal from fixture...")
58197
file, err := testutils.ReadFixture("proposal-testing.json")
59-
require.NoError(t, err, "Failed to read fixture") // Check immediately after ReadFixture
198+
s.Require().NoError(err, "Failed to read fixture") // Check immediately after ReadFixture
60199
defer func(file *os.File) {
61-
if file != nil {
62-
err = file.Close()
63-
require.NoError(t, err, "Failed to close file")
64-
}
200+
s.Require().NotNil(file)
201+
err = file.Close()
202+
s.Require().NoError(err, "Failed to close file")
65203
}(file)
66-
require.NoError(t, err)
204+
s.Require().NoError(err)
67205

68206
proposal, err := mcms.NewProposal(file)
69-
require.NoError(t, err, "Failed to parse proposal")
70-
t.Log("Proposal loaded successfully.")
207+
s.Require().NoError(err, "Failed to parse proposal")
208+
s.T().Log("Proposal loaded successfully.")
209+
proposal.ChainMetadata[s.chainSelectorEVM] = types.ChainMetadata{
210+
MCMAddress: mcmsAddressEVM.String(),
211+
StartingOpCount: 0,
212+
}
213+
proposal.ChainMetadata[s.chainSelectorSolana] = types.ChainMetadata{
214+
MCMAddress: contractIDSolana,
215+
StartingOpCount: 0,
216+
}
71217

72-
// Step 3: Create a Signable instance
73-
t.Log("Creating Signable instance...")
74-
inspectors := map[types.ChainSelector]sdk.Inspector{} // Provide required inspectors
218+
// Step 4: Create a Signable instance
219+
s.T().Log("Creating Signable instance...")
220+
inspectors := map[types.ChainSelector]sdk.Inspector{
221+
s.chainSelectorEVM: evm.NewInspector(s.Client),
222+
s.chainSelectorSolana: solanamcms.NewInspector(s.SolanaClient),
223+
}
224+
encoders, err := proposal.GetEncoders()
225+
s.Require().NoError(err)
226+
authSolana, err := solana.PrivateKeyFromBase58(privateKeyLedger)
227+
s.Require().NoError(err)
228+
s.authSolana = authSolana
229+
encoderEVM := encoders[s.chainSelectorEVM].(*evm.Encoder)
230+
encoderSolana := encoders[s.chainSelectorSolana].(*solanamcms.Encoder)
231+
executorEVM := evm.NewExecutor(encoderEVM, s.Client, s.authEVM)
232+
executorSolana := solanamcms.NewExecutor(encoderSolana, s.SolanaClient, authSolana)
233+
executorsMap := map[types.ChainSelector]sdk.Executor{
234+
s.chainSelectorEVM: executorEVM,
235+
s.chainSelectorSolana: executorSolana,
236+
}
75237
signable, err := mcms.NewSignable(proposal, inspectors)
76-
require.NoError(t, err, "Failed to create Signable instance")
77-
t.Log("Signable instance created successfully.")
238+
s.Require().NoError(err, "Failed to create Signable instance")
239+
s.T().Log("Signable instance created successfully.")
78240

79-
// Step 4: Create a LedgerSigner
80-
t.Log("Creating LedgerSigner...")
241+
// Step 5: Create a LedgerSigner and sign the proposal
242+
s.T().Log("Creating LedgerSigner...")
81243
ledgerSigner := mcms.NewLedgerSigner(derivationPath)
82244

83-
// Step 5: Sign the proposal
84-
t.Log("Signing the proposal...")
245+
s.T().Log("Signing the proposal...")
85246
signature, err := signable.SignAndAppend(ledgerSigner)
86-
require.NoError(t, err, "Failed to sign proposal with Ledger")
87-
t.Log("Proposal signed successfully.")
88-
t.Logf("Signature: R=%s, S=%s, V=%d\n", signature.R.Hex(), signature.S.Hex(), signature.V)
247+
s.Require().NoError(err, "Failed to sign proposal with Ledger")
248+
s.T().Log("Proposal signed successfully.")
249+
s.T().Logf("Signature: R=%s, S=%s, V=%d\n", signature.R.Hex(), signature.S.Hex(), signature.V)
89250

90251
// Step 6: Validate the signature
91-
t.Log("Validating the signature...")
252+
s.T().Log("Validating the signature...")
92253
hash, err := proposal.SigningHash()
93-
require.NoError(t, err, "Failed to compute proposal hash")
254+
s.Require().NoError(err, "Failed to compute proposal hash")
94255

95256
recoveredAddr, err := signature.Recover(hash)
96-
require.NoError(t, err, "Failed to recover signer address")
257+
s.Require().NoError(err, "Failed to recover signer address")
258+
259+
s.Require().Equal(accountPublicKey, recoveredAddr.Hex(), "Signature verification failed")
260+
s.T().Logf("Signature verified successfully. Signed by: %s\n", recoveredAddr.Hex())
97261

98-
require.Equal(t, accountPublicKey, recoveredAddr.Hex(), "Signature verification failed")
99-
t.Logf("Signature verified successfully. Signed by: %s\n", recoveredAddr.Hex())
262+
// Step 7: Call Set Root to verify signature
263+
s.setRootEVM(ctx, account.Address, proposal, mcmInstanceEVM, executorsMap)
264+
s.setRootSolana(ctx, mcmProgramID, account.Address, proposal, executorsMap)
100265
}

e2e/tests/evm/signing.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ func (s *SigningTestSuite) TestReadAndSign() {
6262
)
6363
s.Require().NoError(err)
6464
expected := mcmtypes.Signature{
65-
R: common.HexToHash("0x51c12e8721bf27f35a0006b3e3ebd0dac111c4bb62dce7b0bd7a3475b2f708a5"),
66-
S: common.HexToHash("0x28f29f2a32f4cd9322883fa252742894cc2796a6fbe9cdabd0c6d996eed452f9"),
65+
R: common.HexToHash("0x1ed7807767b09344df63797fa4986ce092730813922ce01563062cf51728ac34"),
66+
S: common.HexToHash("0x556721244f77182c1130a5ee8d78ac7067cef52662dbb57b4132c6ec567ecbc8"),
6767
V: 0,
6868
}
6969
s.Require().Equal(expected, signature)

0 commit comments

Comments
 (0)