1
- //go:build ledger && ! e2e
2
- // +build ledger,! e2e
1
+ //go:build e2e
2
+ // +build e2e
3
3
4
4
package ledger
5
5
6
6
import (
7
+ "context"
7
8
"log"
9
+ "math/big"
8
10
"os"
9
11
"testing"
10
12
11
13
"github.com/ethereum/go-ethereum/accounts"
14
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
12
15
"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"
14
22
15
23
"github.com/smartcontractkit/mcms"
24
+ e2e "github.com/smartcontractkit/mcms/e2e/tests"
25
+ solanae2e "github.com/smartcontractkit/mcms/e2e/tests/solana"
16
26
testutils "github.com/smartcontractkit/mcms/e2e/utils"
17
27
"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"
18
31
"github.com/smartcontractkit/mcms/types"
19
32
)
20
33
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
+
21
145
// 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 )
24
159
25
160
// 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..." )
27
162
ledgerHub , err := usbwallet .NewLedgerHub ()
28
- require . NoError (t , err , "Failed to initialize Ledger Hub" )
163
+ s . Require (). NoError (err , "Failed to initialize Ledger Hub" )
29
164
30
165
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." )
32
167
33
168
// Use the first available wallet
34
169
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 )
36
171
37
172
// Open the wallet
38
- t .Log ("Opening Ledger wallet..." )
173
+ s . T () .Log ("Opening Ledger wallet..." )
39
174
err = wallet .Open ("" )
40
- require . NoError (t , err , "Failed to open Ledger wallet" )
175
+ s . Require (). NoError (err , "Failed to open Ledger wallet" )
41
176
42
- t .Log ("Ledger wallet opened successfully." )
177
+ s . T () .Log ("Ledger wallet opened successfully." )
43
178
44
179
// Define the derivation path
45
180
derivationPath := accounts .DefaultBaseDerivationPath
@@ -49,52 +184,82 @@ func TestManualLedgerSigning(t *testing.T) { //nolint:paralleltest
49
184
if err != nil {
50
185
log .Fatalf ("Failed to derive account: %v" , err )
51
186
}
52
- t .Logf ("Derived account: %s\n " , account .Address .Hex ())
187
+ s . T () .Logf ("Derived account: %s\n " , account .Address .Hex ())
53
188
accountPublicKey := account .Address .Hex ()
54
189
wallet .Close ()
55
190
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..." )
58
197
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
60
199
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" )
65
203
}(file )
66
- require . NoError (t , err )
204
+ s . Require (). NoError (err )
67
205
68
206
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
+ }
71
217
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
+ }
75
237
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." )
78
240
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..." )
81
243
ledgerSigner := mcms .NewLedgerSigner (derivationPath )
82
244
83
- // Step 5: Sign the proposal
84
- t .Log ("Signing the proposal..." )
245
+ s .T ().Log ("Signing the proposal..." )
85
246
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 )
89
250
90
251
// Step 6: Validate the signature
91
- t .Log ("Validating the signature..." )
252
+ s . T () .Log ("Validating the signature..." )
92
253
hash , err := proposal .SigningHash ()
93
- require . NoError (t , err , "Failed to compute proposal hash" )
254
+ s . Require (). NoError (err , "Failed to compute proposal hash" )
94
255
95
256
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 ())
97
261
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 )
100
265
}
0 commit comments