Skip to content

Commit 9e501d5

Browse files
authored
Fix Sui timelock decoder (#723)
- Add small fix to properly decode proposals that go through timelock
1 parent 3c2357d commit 9e501d5

File tree

5 files changed

+86
-112
lines changed

5 files changed

+86
-112
lines changed

.changeset/shaky-ads-scream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": patch
3+
---
4+
5+
Fix sui timelock decoder

experimental/analyzer/sui_analyzer.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ func AnalyzeSuiTransaction(ctx ProposalContext, decoder *mcmssuisdk.Decoder, cha
2929
return nil, fmt.Errorf("failed to unmarshal Sui additional fields: %w", err)
3030
}
3131

32+
// Return the method name directly for MCMS transactions, since the inner transactions will be decoded separately
33+
if additionalFields.ModuleName == "mcms" {
34+
methodName := fmt.Sprintf("%s::%s", additionalFields.ModuleName, additionalFields.Function)
35+
return &DecodedCall{
36+
Address: mcmsTx.To,
37+
Method: methodName,
38+
Inputs: []NamedField{},
39+
Outputs: []NamedField{},
40+
}, nil
41+
}
42+
3243
functionInfo, ok := generated.FunctionInfoByModule[additionalFields.ModuleName]
3344
if !ok {
3445
// Don't return an error to not block the whole proposal decoding because of a single missing method

experimental/analyzer/sui_analyzer_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,17 @@ func TestAnalyzeSuiTransactionWithErrors(t *testing.T) {
206206
wantMethod: "no function info found for module unknown_module on chain selector",
207207
wantError: false,
208208
},
209+
{
210+
name: "mcms module transaction returns method name directly",
211+
mcmsTx: types.Transaction{
212+
To: suiTestAddress,
213+
Data: []byte{0x01, 0x02, 0x03}, // Data doesn't matter for MCMS module
214+
AdditionalFields: json.RawMessage(`{"module_name":"mcms","function":"timelock_schedule_batch","state_obj":"0x123"}`),
215+
},
216+
wantAddress: suiTestAddress,
217+
wantMethod: "mcms::timelock_schedule_batch",
218+
wantError: false,
219+
},
209220
{
210221
name: "decoder decode failure with empty data",
211222
mcmsTx: types.Transaction{
@@ -276,6 +287,11 @@ func TestAnalyzeSuiTransactionWithErrors(t *testing.T) {
276287
// For decode failure, just check that it starts with the expected prefix
277288
require.True(t, hasPrefix(result.Method, tt.wantMethod),
278289
"Method %q should start with prefix %q", result.Method, tt.wantMethod)
290+
case "mcms module transaction returns method name directly":
291+
// For MCMS module, verify the method is correct and inputs are empty
292+
require.Equal(t, tt.wantMethod, result.Method, "Method mismatch")
293+
require.Empty(t, result.Inputs, "MCMS module transactions should have empty inputs")
294+
require.Empty(t, result.Outputs, "MCMS module transactions should have empty outputs")
279295
default:
280296
require.Equal(t, tt.wantMethod, result.Method, "Method mismatch")
281297
}

experimental/analyzer/upf/upf.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ func UpfConvertTimelockProposal(
4646
if batch.Metadata == nil || batch.Metadata.DecodedCalldata == nil {
4747
continue
4848
}
49-
if batch.Metadata.ContractType == "RBACTimelock" && isTimelockBatchFunction(batch.Metadata.DecodedCalldata.FunctionName) {
49+
// Check for both RBACTimelock (EVM, Solana) and MCMS (Sui, Aptos, TON) contract types
50+
if (batch.Metadata.ContractType == "RBACTimelock" || batch.Metadata.ContractType == "MCMS") && isTimelockBatchFunction(batch.Metadata.DecodedCalldata.FunctionName) {
5051
batch.Metadata.DecodedCalldata.FunctionArgs["calls"] = decodedBatches[decodedBatchesIndex]
5152
decodedBatchesIndex++
5253
}

experimental/analyzer/upf/upf_test.go

Lines changed: 52 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func TestUpfConvertTimelockProposalWithSui(t *testing.T) {
123123

124124
// ---- Sui: testnet
125125
dsAddContract(t, ds, chainsel.SUI_TESTNET.Selector, "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d", "MCMSUser 1.0.0")
126+
dsAddContract(t, ds, chainsel.SUI_TESTNET.Selector, "0xa363028c36d9b7ade44dfe4c317893bf86a4a1ce69293b6cb1569928fcf55e63", "burn_mint_token_pool 1.0.0")
126127

127128
env := deployment.Environment{
128129
DataStore: ds.Seal(),
@@ -139,8 +140,8 @@ func TestUpfConvertTimelockProposalWithSui(t *testing.T) {
139140
assertion func(*testing.T, string, error)
140141
}{
141142
{
142-
name: "Sui proposal with valid transaction",
143-
timelockProposal: timelockProposalSui,
143+
name: "Sui burn_mint_token_pool ownership transfer",
144+
timelockProposal: timelockProposalSuiBurnMintTokenPool,
144145
signers: map[mcmstypes.ChainSelector][]common.Address{
145146
mcmstypes.ChainSelector(chainsel.SUI_TESTNET.Selector): {
146147
common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"),
@@ -149,21 +150,9 @@ func TestUpfConvertTimelockProposalWithSui(t *testing.T) {
149150
assertion: func(t *testing.T, gotUpf string, err error) {
150151
t.Helper()
151152
require.NoError(t, err)
152-
require.YAMLEq(t, upfProposalSui, gotUpf)
153-
},
154-
},
155-
{
156-
name: "Sui proposal with unknown module",
157-
timelockProposal: timelockProposalSuiUnknownModule,
158-
signers: map[mcmstypes.ChainSelector][]common.Address{
159-
mcmstypes.ChainSelector(chainsel.SUI_TESTNET.Selector): {
160-
common.HexToAddress("0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"),
161-
},
162-
},
163-
assertion: func(t *testing.T, gotUpf string, err error) {
164-
t.Helper()
165-
require.NoError(t, err)
166-
require.YAMLEq(t, upfProposalSuiUnknownModule, gotUpf)
153+
require.NotEmpty(t, gotUpf)
154+
// Verify that the proposal was successfully converted
155+
require.Equal(t, suiUPFProposal, gotUpf)
167156
},
168157
},
169158
}
@@ -587,139 +576,91 @@ signers:
587576
- "0x5f077BCeE6e285154473F65699d6F46Fd03D105A"
588577
`
589578

590-
var timelockProposalSui = `{
579+
//nolint:gosec // G101 all test values
580+
var timelockProposalSuiBurnMintTokenPool = `{
591581
"version": "v1",
592582
"kind": "TimelockProposal",
593583
"validUntil": 1999999999,
594-
"signatures": [],
584+
"signatures": null,
595585
"overridePreviousRoot": false,
596586
"chainMetadata": {
597587
"9762610643973837292": {
598-
"startingOpCount": 1,
599-
"mcmAddress": "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d",
600-
"additionalFields": null
601-
}
602-
},
603-
"description": "simple Sui proposal",
604-
"action": "schedule",
605-
"delay": "5m0s",
606-
"timelockAddresses": {
607-
"9762610643973837292": "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d"
608-
},
609-
"operations": [
610-
{
611-
"chainSelector": 9762610643973837292,
612-
"transactions": [
613-
{
614-
"contractType": "MCMSUser",
615-
"tags": [],
616-
"to": "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d",
617-
"data": "i8WcKEL0NsEiFpGjWdxClBwfJeyhP0ut55f7Dg2PS2W5dbWeXl19LUYyEaeuZRHbtJS9IbqY1GNZHOkUhofVcGRhdGVkIEZpZWxkIEEKAQIDBAUGBwgJCg==",
618-
"additionalFields": {
619-
"module_name": "mcms_user",
620-
"function": "function_one",
621-
"state_obj": "0x8bc59c2842f436c1221691a359dc42941c1f25eca13f4bad79f7b00e8df4b968"
622-
}
623-
}
624-
]
625-
}
626-
]
627-
}`
628-
629-
var upfProposalSui = `---
630-
msigType: mcms
631-
proposalHash: "0x1c733d9d09e9d41e1651596078df88b00c68e085cc6bf14b8f346866b1741a28"
632-
mcmsParams:
633-
validUntil: 1999999999
634-
merkleRoot: "0xeeaa854482fdd28dec1ca358c4ba9c7399560b580683c7fa372e9a69eab8ba1d"
635-
asciiProposalHash: '\x93>\x07\xb8>\xce3\xfa\xa7\xccZ\x1e\xea\xf8|\xb39\x9c\x10s\xd7\x98\xc8\xa6\x1d\xe13\x99\xa1u\xe2.'
636-
overridePreviousRoot: false
637-
transactions:
638-
- index: 0
639-
chainFamily: sui
640-
chainId: "2"
641-
chainName: sui-testnet
642-
chainShortName: sui-testnet
643-
msigAddress: "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d"
644-
timelockAddress: "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d"
645-
to: ""
646-
value: 0
647-
data: AU6CWkdYBk33E3YuQxw6FrgQWFcZUhRGnbDWmFt9cCZtAQltY21zX3VzZXIBDGZ1bmN0aW9uX29uZQFYi8WcKEL0NsEiFpGjWdxClBwfJeyhP0ut55f7Dg2PS2W5dbWeXl19LUYyEaeuZRHbtJS9IbqY1GNZHOkUhofVcGRhdGVkIEZpZWxkIEEKAQIDBAUGBwgJCiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACB3NZP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwBAAAAAAAA
648-
txNonce: 1
649-
metadata:
650-
contractType: MCMS
651-
decodedCalldata:
652-
functionName: "failed to decode Sui transaction: could not find function in contractInterfaces for mcms::timelock_schedule_batch"
653-
functionArgs: {}
654-
signers:
655-
9762610643973837292:
656-
- "0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"
657-
`
658-
659-
var timelockProposalSuiUnknownModule = `{
660-
"version": "v1",
661-
"kind": "TimelockProposal",
662-
"validUntil": 1999999999,
663-
"signatures": [],
664-
"overridePreviousRoot": false,
665-
"chainMetadata": {
666-
"9762610643973837292": {
667-
"startingOpCount": 1,
668-
"mcmAddress": "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d",
669-
"additionalFields": null
588+
"startingOpCount": 4,
589+
"mcmAddress": "0x7418a4d56580cb2eac68025af4c928de007fa093f711d838a139fb3675a2ef5a",
590+
"additionalFields": {
591+
"role": 2,
592+
"mcms_package_id": "0x832b7fd3b7f03d2fd55811cd565d675c09d938f2dc8c24dfd5e73bae4ca118df",
593+
"account_obj": "0x0ad2d032fe62f567a8cb545200629a92bbd1033d84a64350d0c9f178afe3f998",
594+
"registry_obj": "0x4d06d9106ae26847cab08eaa6ff4eb977c699f0ed90dacc7cdb9575bee92ad20",
595+
"timelock_obj": "0xa514be3fe446f654389c1bd2dc4ce9dcbd85753fe537c0c64a34298607ee33b6",
596+
"deployer_state_obj": "0xb1879297d851a448c923982c9d3efaf51612e18bb394d20aab496199f5d6ec4d"
597+
}
670598
}
671599
},
672-
"description": "Sui proposal with unknown module",
600+
"description": "Invoke",
673601
"action": "schedule",
674-
"delay": "5m0s",
602+
"delay": "10s",
675603
"timelockAddresses": {
676-
"9762610643973837292": "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d"
604+
"9762610643973837292": "0xa514be3fe446f654389c1bd2dc4ce9dcbd85753fe537c0c64a34298607ee33b6"
677605
},
678606
"operations": [
679607
{
680608
"chainSelector": 9762610643973837292,
681609
"transactions": [
682610
{
683-
"contractType": "MCMSUser",
611+
"contractType": "burn_mint_token_pool",
684612
"tags": [],
685-
"to": "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d",
686-
"data": "c29tZSBkYXRh",
613+
"to": "0xa363028c36d9b7ade44dfe4c317893bf86a4a1ce69293b6cb1569928fcf55e63",
614+
"data": "gIiW6Fws+lnexlxd5E2Te3gDfR3J97yFPBcYjHk3ZNYhFzSEY0DsnIRNNMzPknW8ZlwHHO5Wz097aXjSs7D1800G2RBq4mhHyrCOqm/065d8aZ8O2Q2sx825V1vukq0ggyt/07fwPS/VWBHNVl1nXAnZOPLcjCTf1ec7rkyhGN8=",
687615
"additionalFields": {
688-
"module_name": "unknown_module",
689-
"function": "some_function",
690-
"state_obj": "0x123"
616+
"module_name": "burn_mint_token_pool",
617+
"function": "execute_ownership_transfer_to_mcms",
618+
"state_obj": "0x211734846340ec9c844d34cccf9275bc665c071cee56cf4f7b6978d2b3b0f5f3",
619+
"type_args": [
620+
"0x0ade2872306bc9346f3576bfb6c45db1a590f00330b810e4f7084ff9efdc5da2::link::LINK"
621+
]
691622
}
692623
}
693624
]
694625
}
695626
]
696627
}`
697628

698-
var upfProposalSuiUnknownModule = `---
629+
var suiUPFProposal = `---
699630
msigType: mcms
700-
proposalHash: "0x5433c70ce0b94602235ae03d5485a3ff991b90d35b90f3474af5455f1105c198"
631+
proposalHash: "0x6676342371fba5bf02bfe07457797fc0dfa51b85eec23bf08ae5114f365865db"
701632
mcmsParams:
702633
validUntil: 1999999999
703-
merkleRoot: "0x0104cddb47805604d82eeab0e02cb33c4374c1e635ab038d2a1ed9038c48e4a9"
704-
asciiProposalHash: 'L\xb9E\x9d\xfeMY\x83\xec3\xba\x00\xa6F0@\x82 \xd4\xc0\x9bj-"C\xcb\xf6\xb6v\xc0B\xbc'
634+
merkleRoot: "0x093c18a1ae222c48c735c2d8f231fc8892060cc299d2a949d0c5b2bb830a1dbe"
635+
asciiProposalHash: 'G\x80\xda\xeb\x95\xf5\xf5\x8d\xd4W\x9a\x04R\x92y\xd8\x19\x0e` + "`" + `6\xd0\x851k\xbc\xad\x193?\xcdr\xb9'
705636
overridePreviousRoot: false
706637
transactions:
707638
- index: 0
708639
chainFamily: sui
709640
chainId: "2"
710641
chainName: sui-testnet
711642
chainShortName: sui-testnet
712-
msigAddress: "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d"
713-
timelockAddress: "0x4e825a4758064df713762e431c3a16b8105857195214469db0d6985b7d70266d"
714-
to: ""
643+
msigAddress: "0x7418a4d56580cb2eac68025af4c928de007fa093f711d838a139fb3675a2ef5a"
644+
timelockAddress: "0xa514be3fe446f654389c1bd2dc4ce9dcbd85753fe537c0c64a34298607ee33b6"
645+
to: "0x832b7fd3b7f03d2fd55811cd565d675c09d938f2dc8c24dfd5e73bae4ca118df"
715646
value: 0
716-
data: AU6CWkdYBk33E3YuQxw6FrgQWFcZUhRGnbDWmFt9cCZtAQ51bmtub3duX21vZHVsZQENc29tZV9mdW5jdGlvbgEJc29tZSBkYXRhIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIHc1k/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAEAAAAAAAA=
717-
txNonce: 1
647+
data: AaNjAow22bet5E3+TDF4k7+GpKHOaSk7bLFWmSj89V5jARRidXJuX21pbnRfdG9rZW5fcG9vbAEiZXhlY3V0ZV9vd25lcnNoaXBfdHJhbnNmZXJfdG9fbWNtcwGAAYCIluhcLPpZ3sZcXeRNk3t4A30dyfe8hTwXGIx5N2TWIRc0hGNA7JyETTTMz5J1vGZcBxzuVs9Pe2l40rOw9fNNBtkQauJoR8qwjqpv9OuXfGmfDtkNrMfNuVdb7pKtIIMrf9O38D0v1VgRzVZdZ1wJ2Tjy3Iwk39XnO65MoRjfIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIHc1k/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAA=
648+
txNonce: 4
718649
metadata:
719650
contractType: MCMS
720651
decodedCalldata:
721-
functionName: "failed to decode Sui transaction: could not find function in contractInterfaces for mcms::timelock_schedule_batch"
722-
functionArgs: {}
652+
functionName: mcms::timelock_schedule_batch
653+
functionArgs:
654+
calls:
655+
- to: "0xa363028c36d9b7ade44dfe4c317893bf86a4a1ce69293b6cb1569928fcf55e63"
656+
value: 0
657+
data:
658+
functionName: burn_mint_token_pool::execute_ownership_transfer_to_mcms
659+
functionArgs:
660+
owner_cap: "0x808896e85c2cfa59dec65c5de44d937b78037d1dc9f7bc853c17188c793764d6"
661+
registry: "0x4d06d9106ae26847cab08eaa6ff4eb977c699f0ed90dacc7cdb9575bee92ad20"
662+
state: "0x211734846340ec9c844d34cccf9275bc665c071cee56cf4f7b6978d2b3b0f5f3"
663+
to: "0x832b7fd3b7f03d2fd55811cd565d675c09d938f2dc8c24dfd5e73bae4ca118df"
723664
signers:
724665
9762610643973837292:
725666
- "0xA5D5B0B844c8f11B61F28AC98BBA84dEA9b80953"

0 commit comments

Comments
 (0)