Skip to content

Commit 57cc1c0

Browse files
authored
Continue with contract state inspector (#399)
* MerkleRoot and OffRamp * self-review * fix * lint * fix * fixes * fixes * lint * lint * copilot * copilot
1 parent ae6a4d1 commit 57cc1c0

9 files changed

Lines changed: 1210 additions & 25 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package model
2+
3+
import (
4+
"testing"
5+
6+
"github.com/xssnick/tonutils-go/address"
7+
8+
"github.com/smartcontractkit/chainlink-ton/pkg/ccip/bindings/merkleroot"
9+
"github.com/smartcontractkit/chainlink-ton/pkg/ccip/model"
10+
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func countState(states []model.ExecutionState, target model.ExecutionState) int {
15+
count := 0
16+
for _, s := range states {
17+
if s == target {
18+
count++
19+
}
20+
}
21+
return count
22+
}
23+
24+
func TestDecodeMerkleRootData(t *testing.T) {
25+
// GIVEN: a merkle root storage model
26+
ownerAddress := address.MustParseAddr("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu8e")
27+
28+
storage, err := model.NewMerkleRootStorageBuilder().
29+
WithRoot("7f3c9e12a4d8b0f16c27e5aa91f4cb8d3e0fa7c6b28d54ef1c93b72a0de4589f").
30+
WithOwner(ownerAddress).
31+
WithMinMsgNr(10).
32+
WithMaxMsgNr(19).
33+
WithDeliveredMessageCount(3).
34+
WithMessageStates([]model.ExecutionState{
35+
model.Untouched, model.Failure, model.InProgress, model.Success, model.Success, model.Untouched, model.Untouched, model.Untouched, model.Untouched, model.Untouched,
36+
}).
37+
WithTimestamp(mustTimestamp("2025-11-18T17:59:46Z")).
38+
Build()
39+
require.NoError(t, err)
40+
41+
t.Run("TestModel", func(t *testing.T) {
42+
t.Parallel()
43+
44+
require.Equal(t, "7f3c9e12a4d8b0f16c27e5aa91f4cb8d3e0fa7c6b28d54ef1c93b72a0de4589f", storage.Root)
45+
require.Equal(t, ownerAddress, storage.Owner)
46+
require.Equal(t, uint64(10), storage.MinMsgNr)
47+
require.Equal(t, uint64(19), storage.MaxMsgNr)
48+
require.Equal(t, uint16(3), storage.DeliveredMessageCount)
49+
require.Len(t, storage.MessageStates, 10)
50+
require.Equal(t, 6, countState(storage.MessageStates, model.Untouched))
51+
require.Equal(t, 1, countState(storage.MessageStates, model.InProgress))
52+
require.Equal(t, 2, countState(storage.MessageStates, model.Success))
53+
require.Equal(t, 1, countState(storage.MessageStates, model.Failure))
54+
require.Equal(t, mustTimestamp("2025-11-18T17:59:46Z"), storage.Timestamp)
55+
})
56+
57+
t.Run("TestMapper", func(t *testing.T) {
58+
t.Parallel()
59+
60+
hexData := "b5ee9c7241010101006e0000d77f3c9e12a4d8b0f16c27e5aa91f4cb8d3e0fa7c6b28d54ef1c93b72a0de4589f800000000000000000000000000000000000000000000000000000000000000000400000000d23968240000000000000014000000000000002600000000000000000000000000000538000700de1c6ea"
61+
62+
AssertHexMappingRoundTrip[merkleroot.Storage](
63+
t,
64+
hexData,
65+
storage, // original
66+
func() model.Mapper[merkleroot.Storage] {
67+
// empty instance to decode into
68+
return &model.MerkleRootStorage{}
69+
},
70+
)
71+
})
72+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package model
2+
3+
import (
4+
"math/big"
5+
"testing"
6+
7+
"github.com/xssnick/tonutils-go/address"
8+
9+
"github.com/smartcontractkit/chainlink-ton/pkg/ccip/bindings/offramp"
10+
"github.com/smartcontractkit/chainlink-ton/pkg/ccip/model"
11+
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestDecodeOffRampData(t *testing.T) {
16+
// GIVEN: a offramp storage model
17+
ownerAddress := address.MustParseAddr("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu8e")
18+
pendingOwnerAddress := address.MustParseAddr("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABZ_5")
19+
feeQuoter := address.MustParseAddr("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_8_")
20+
rmnRouter := address.MustParseAddr("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABI_Y")
21+
deployerCode := "b5ee9c72410102010061000114ff00f4a413f4bcf2c80b0100a4d3f891f240ed44d0fa403082009218f89258c705f2f4d72c25d233223c98d4d74c01fb04ed54e0d72c274af08ab48e1fd4d4fa00d74c03fb0401ed54f828c8cf8508ce01fa0271cf0b6accc971fb00e0f23fe2550eb4"
22+
merkleRootCode := "b5ee9c7241020e01000266000114ff00f4a413f4bcf2c80b0102016202030240d0f891f24020d72c201c76f48ce302d72c200cfa6694e30230840f01c700f2f40405020148060701f831ed44d0d3fffa40d33fd33fd33fd37fd70b0f8200bb1df89227c705f2f407d4d31fd3ffd3000193fa003092306de203d020d3ff31d33f31d33f31d70b3f8200bb215318be955317bbc3009170e2f2f45307a18200bb2121c140f2f47321aa00ac27b001aa00ad8200bb1f21c003917f9521c000c300e2f2f4256eb30801fe31ed44d0d3fffa40d33fd33fd33fd37fd70b0f8200bb1df89227c705f2f407d33fd70b0720c203f2458200bb215325be955324bbc3009170e2f2f45314a18200bb2121c140f2f47321aa00ac24b001aa00ad8200bb2001c302f2f48200bb215325be955324bbc3009170e2f2f45114a18200bb2121c140f2f47321aa00acb3090201200a0b000bb86858101df801fe8e17f8232aa15005bc8200bb1e01917f9524c003c300e2f2f49a348200bb1c24c000f2f4e28200bb215318be955317bbc3009170e2f2f427a18200bb2121c140f2f47321aa00acb316b005aa00ae15b104c8cec9c8cf931cf56a2acc29cf0bffcbff226e946c12cf8195cf8358fa02e2cb07c9c8cf858826cf1671cf0b6ecc0c018213b002aa005210ac12b101c0029306a406de5312a1a427ba8e9288c8cf858826cf1671cf0b6eccc98306fb00de05c8cbff14ce12cb3fcb3fcb3fcb7fcb0fc9ed540d005bb62bf1a10b1b7b69731b430b4b73634b735973a37b71731b1b4b81726b2b935b632a937b7ba4116a625c6c5c6110000fb5c51040176394100032c98040fb0005c8cbff14ce12cb3fcb3fcb3fcb7fcb0fc9ed540000f98aee45"
23+
receiveExecutorCode := "b5ee9c7241020f0100021c000114ff00f4a413f4bcf2c80b01020162020304f8d0f891f24020d72c2326697e948e6631ed44d0fa40d4fa40d3bfd30131d33f31d18200840bf89225c705f2f404d3000193fa003092306de2f823c8cf858825cf16821058cfcb02cf0b8e24cf1426cf0bbf226e946c12cf8195cf8358fa02e2c98040fb0003c8ce12ccce12cbbfcf8580cb3fc9ed54e089d727e30289040506070201480809000800e5dd9701fe31ed44d0fa40d4fa40d3bfd301d33fd18200840bf89227c705f2f406fa40308200840902c00112f2f423d0d3ffd33fd33fd33fd33fd431d431fa40fa0031f40431d1068200840a07c70516f2f4c8cf91679585c214cbff12cb3fcb3fcb3fcb3f21cf0bbf22cf16c9c8cf858825cf1671cf0b6eccc98306fb0003c8ce12ccce0a000805dee1bb0118d727e30230840f01c700f2f40b0201200c0d000bb868581015280014cbbfcf8780cb3fc9ed5401fe31ed44d0fa40d4fa40d3bfd301d33fd18200840bf89227c705f2f406fa40308200840902c00112f2f423d0d3ffd33fd33fd33fd33fd431d431fa40fa0031f40431d1068200840a07c70516f2f4c8cf905dfaf40e14cbff12cb3fcb3fcb3fcb3f21cf0bbf22cf16c9c8cf858825cf1671cf0b6eccc98040fb0003c8ce12ccce0e0065b62bf1a1331b7b69731b430b4b73634b735973a37b71731b1b4b8172932b1b2b4bb32a2bc32b1baba37b94116a625c6c5c6110000fb5c51040108114100014cbbfcf8680cb3fc9ed541fda8d59"
24+
commitSigners := []string{
25+
"031a8e6486f688260ac6c785158bf55a890b5c084286135910d0d2c00ed3ffc1",
26+
"28701bcf04074c4ac8d967eb917c70182a1456d0f4e6475424c7b78c9628a044",
27+
"7d2bb364019d6b1c6797764ff0cbc389348e1a06295f526f3f45be1b86f807ec",
28+
"9f47d53bebac3400c00e2ae0cefda055b9b233cc5177e6dd0daabc0fb3990dc5",
29+
}
30+
transmitters := []*address.Address{
31+
ownerAddress,
32+
feeQuoter,
33+
rmnRouter,
34+
pendingOwnerAddress,
35+
}
36+
configDigest := "000a8a867649d8f27ab8131eee7667e2526465dc44e734004d4759a63a4409c9"
37+
var chainSelector1 uint64 = 14767482510784806043
38+
var chainSelector2 uint64 = 16015286601757825753
39+
var tonChainSelector uint64 = 1399300952838017768
40+
onRamp1 := "f3a9c7d8124efb669a21be88d5c4a91737deaa01"
41+
onRamp2 := "9bd62cee17a40f59c81203bb48c15ef3a5fdc47e"
42+
43+
storage, err := model.NewOffRampStorageBuilder().
44+
WithID(1000).
45+
WithOwnable(
46+
ownerAddress.Copy(),
47+
pendingOwnerAddress.Copy(),
48+
).
49+
WithRMNRouter(rmnRouter.Copy()).
50+
WithDeployerCode(deployerCode).
51+
WithMerkleRootCode(merkleRootCode).
52+
WithReceiveExecutorCode(receiveExecutorCode).
53+
WithFeeQuoter(feeQuoter.Copy()).
54+
WithOCR3BaseChainID(1).
55+
WithOCR3CommitConfig(&model.OCR3Config{
56+
Signers: commitSigners,
57+
Transmitters: transmitters,
58+
ConfigDigest: configDigest,
59+
F: 1,
60+
N: 4,
61+
}).
62+
WithOCR3ExecuteConfig(&model.OCR3Config{
63+
Transmitters: transmitters,
64+
ConfigDigest: configDigest,
65+
F: 1,
66+
N: 0,
67+
}).
68+
WithCursedSubject(new(big.Int).SetUint64(chainSelector1)).
69+
WithCursedSubject(new(big.Int).SetUint64(chainSelector2)).
70+
WithChainSelector(tonChainSelector).
71+
WithPermissionlessExecutionThresholdSeconds(uint32(120)).
72+
WithLatestPriceSequenceNumber(uint64(1000)).
73+
WithSourceChainConfig(chainSelector1, model.SourceChainConfig{
74+
Router: rmnRouter,
75+
IsEnabled: true,
76+
MinSeqNr: 1,
77+
IsRMNVerificationDisabled: false,
78+
OnRamp: onRamp1,
79+
}).
80+
WithSourceChainConfig(chainSelector2, model.SourceChainConfig{
81+
Router: rmnRouter,
82+
IsEnabled: false,
83+
MinSeqNr: 2,
84+
IsRMNVerificationDisabled: true,
85+
OnRamp: onRamp2,
86+
}).
87+
Build()
88+
require.NoError(t, err)
89+
90+
t.Run("TestModel", func(t *testing.T) {
91+
t.Parallel()
92+
93+
// ID
94+
require.Equal(t, uint32(1000), storage.ID)
95+
96+
// Ownable
97+
require.Equal(t, ownerAddress, storage.Ownable.Owner)
98+
require.Equal(t, pendingOwnerAddress, storage.Ownable.PendingOwner)
99+
100+
// RMN Router
101+
require.Equal(t, rmnRouter, storage.Deployables.RMNRouter)
102+
103+
// Code cells / code hashes
104+
require.Equal(t, deployerCode, storage.Deployables.Deployer)
105+
require.Equal(t, merkleRootCode, storage.Deployables.MerkleRootCode)
106+
require.Equal(t, receiveExecutorCode, storage.Deployables.ReceiveExecutorCode)
107+
108+
// FeeQuoter
109+
require.Equal(t, feeQuoter, storage.FeeQuoter)
110+
111+
// OCR3 base chain ID
112+
require.Equal(t, 1, storage.OCR3Base.ChainID)
113+
114+
// OCR3 Commit config
115+
require.NotNil(t, storage.OCR3Base.Commit)
116+
require.Equal(t, configDigest, storage.OCR3Base.Commit.ConfigDigest)
117+
require.Equal(t, 1, storage.OCR3Base.Commit.F)
118+
require.Equal(t, 4, storage.OCR3Base.Commit.N)
119+
require.Len(t, storage.OCR3Base.Commit.Signers, len(commitSigners))
120+
require.Equal(t, commitSigners, storage.OCR3Base.Commit.Signers)
121+
require.Len(t, storage.OCR3Base.Commit.Transmitters, len(transmitters))
122+
require.Equal(t, transmitters, storage.OCR3Base.Commit.Transmitters)
123+
124+
// OCR3 Execute config
125+
require.NotNil(t, storage.OCR3Base.Execute)
126+
require.Equal(t, configDigest, storage.OCR3Base.Execute.ConfigDigest)
127+
require.Equal(t, 1, storage.OCR3Base.Execute.F)
128+
require.Equal(t, 0, storage.OCR3Base.Execute.N)
129+
require.Empty(t, storage.OCR3Base.Execute.Signers)
130+
require.Len(t, storage.OCR3Base.Execute.Transmitters, len(transmitters))
131+
require.Equal(t, transmitters, storage.OCR3Base.Execute.Transmitters)
132+
133+
// Cursed subjects (2 entries)
134+
require.Len(t, storage.CursedSubjects, 2)
135+
136+
require.Equal(t, storage.CursedSubjects[0].Uint64(), chainSelector1)
137+
require.Equal(t, storage.CursedSubjects[1].Uint64(), chainSelector2)
138+
139+
// Chain selector
140+
require.Equal(t, tonChainSelector, storage.ChainSelector)
141+
142+
// Permissionless execution threshold
143+
require.Equal(t, uint32(120), storage.PermissionlessExecutionThresholdSeconds)
144+
145+
// Latest price sequence number
146+
require.Equal(t, uint64(1000), storage.LatestPriceSequenceNumber)
147+
148+
// Source chain configs map
149+
require.Len(t, storage.SourceChainConfigs, 2)
150+
151+
cfg1, ok := storage.SourceChainConfigs[chainSelector1]
152+
require.True(t, ok)
153+
require.Equal(t, rmnRouter, cfg1.Router)
154+
require.True(t, cfg1.IsEnabled)
155+
require.Equal(t, uint64(1), cfg1.MinSeqNr)
156+
require.False(t, cfg1.IsRMNVerificationDisabled)
157+
require.Equal(t, onRamp1, cfg1.OnRamp)
158+
159+
cfg2, ok := storage.SourceChainConfigs[chainSelector2]
160+
require.True(t, ok)
161+
require.Equal(t, rmnRouter, cfg2.Router)
162+
require.False(t, cfg2.IsEnabled)
163+
require.Equal(t, uint64(2), cfg2.MinSeqNr)
164+
require.True(t, cfg2.IsRMNVerificationDisabled)
165+
require.Equal(t, onRamp2, cfg2.OnRamp)
166+
})
167+
168+
t.Run("TestMapper", func(t *testing.T) {
169+
t.Parallel()
170+
171+
hexData := "b5ee9c724102350100076d0004f9000003e88000000000000000000000000000000000000000000000000000000000000000005800000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000000e26d6a19b078d25d0000000f100000000000003e8801020304034380000000000000000000000000000000000000000000000000000000000000000090050607020301e00809021390c000000000000000340a0b0202760c0d0114ff00f4a413f4bcf2c80b0e0114ff00f4a413f4bcf2c80b0f0114ff00f4a413f4bcf2c80b100245000a8a867649d8f27ab8131eee7667e2526465dc44e734004d4759a63a4409c901047011120145000a8a867649d8f27ab8131eee7667e2526465dc44e734004d4759a63a4409c9010030120011bccf0a31a221f3c9b80011bce41ba4fc9d91ad98008fbccf0a31a221f3c9b800000000000000000000000000000000000000000000000000000000000000000900000000000000010a79d4e3ec09277db34d10df446ae2548b9bef5500c0008fbce41ba4fc9d91ad9800000000000000000000000000000000000000000000000000000000000000000800000000000000028a4deb16770bd207ace40901dda460af79d2fee23f4000a4d3f891f240ed44d0fa403082009218f89258c705f2f4d72c25d233223c98d4d74c01fb04ed54e0d72c274af08ab48e1fd4d4fa00d74c03fb0401ed54f828c8cf8508ce01fa0271cf0b6accc971fb00e0f23f0201621314020162151602012017180245a110000000000000000000000000000000000000000000000000000000000000000010191a0240d0f891f24020d72c201c76f48ce302d72c200cfa6694e30230840f01c700f2f41b1c0201481d1e04f8d0f891f24020d72c2326697e948e6631ed44d0fa40d4fa40d3bfd30131d33f31d18200840bf89225c705f2f404d3000193fa003092306de2f823c8cf858825cf16821058cfcb02cf0b8e24cf1426cf0bbf226e946c12cf8195cf8358fa02e2c98040fb0003c8ce12ccce12cbbfcf8580cb3fc9ed54e089d727e302891f202122020148232402012025260043bfcfa3ea9df5d61a0060071570677ed02adcd919e628bbf36e86d55e07d9cc86e2c00201582727020148272701f831ed44d0d3fffa40d33fd33fd33fd37fd70b0f8200bb1df89227c705f2f407d4d31fd3ffd3000193fa003092306de203d020d3ff31d33f31d33f31d70b3f8200bb215318be955317bbc3009170e2f2f45307a18200bb2121c140f2f47321aa00ac27b001aa00ad8200bb1f21c003917f9521c000c300e2f2f4256eb32801fe31ed44d0d3fffa40d33fd33fd33fd37fd70b0f8200bb1df89227c705f2f407d33fd70b0720c203f2458200bb215325be955324bbc3009170e2f2f45314a18200bb2121c140f2f47321aa00ac24b001aa00ad8200bb2001c302f2f48200bb215325be955324bbc3009170e2f2f45114a18200bb2121c140f2f47321aa00acb3290201202a2b000bb86858101df8000800e5dd9701fe31ed44d0fa40d4fa40d3bfd301d33fd18200840bf89227c705f2f406fa40308200840902c00112f2f423d0d3ffd33fd33fd33fd33fd431d431fa40fa0031f40431d1068200840a07c70516f2f4c8cf91679585c214cbff12cb3fcb3fcb3fcb3f21cf0bbf22cf16c9c8cf858825cf1671cf0b6eccc98306fb0003c8ce12ccce2c000805dee1bb0118d727e30230840f01c700f2f42d0201202e2f000bb8685810152802012030310042bfbd2bb364019d6b1c6797764ff0cbc389348e1a06295f526f3f45be1b86f807ec00012001fe8e17f8232aa15005bc8200bb1e01917f9524c003c300e2f2f49a348200bb1c24c000f2f4e28200bb215318be955317bbc3009170e2f2f427a18200bb2121c140f2f47321aa00acb316b005aa00ae15b104c8cec9c8cf931cf56a2acc29cf0bffcbff226e946c12cf8195cf8358fa02e2cb07c9c8cf858826cf1671cf0b6ecc32018213b002aa005210ac12b101c0029306a406de5312a1a427ba8e9288c8cf858826cf1671cf0b6eccc98306fb00de05c8cbff14ce12cb3fcb3fcb3fcb7fcb0fc9ed5433005bb62bf1a10b1b7b69731b430b4b73634b735973a37b71731b1b4b81726b2b935b632a937b7ba4116a625c6c5c6110000fb5c51040176394100014cbbfcf8780cb3fc9ed5401fe31ed44d0fa40d4fa40d3bfd301d33fd18200840bf89227c705f2f406fa40308200840902c00112f2f423d0d3ffd33fd33fd33fd33fd431d431fa40fa0031f40431d1068200840a07c70516f2f4c8cf905dfaf40e14cbff12cb3fcb3fcb3fcb3f21cf0bbf22cf16c9c8cf858825cf1671cf0b6eccc98040fb0003c8ce12ccce340065b62bf1a1331b7b69731b430b4b73634b735973a37b71731b1b4b8172932b1b2b4bb32a2bc32b1baba37b94116a625c6c5c6110000fb5c51040108114100041bf46351cc90ded104c158d8f0a2b17eab51216b810850c26b221a1a5801da7ff830041bf50e0379e080e989591b2cfd722f8e0305428ada1e9cc8ea8498f6f192c5140890032c98040fb0005c8cbff14ce12cb3fcb3fcb3fcb7fcb0fc9ed5400000014cbbfcf8680cb3fc9ed547165b4ff"
172+
173+
AssertHexMappingRoundTrip[offramp.Storage](
174+
t,
175+
hexData,
176+
storage, // original
177+
func() model.Mapper[offramp.Storage] {
178+
// empty instance to decode into
179+
return &model.OffRampStorage{}
180+
},
181+
)
182+
})
183+
}

integration-tests/model/router_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ func TestDecodeRouterData(t *testing.T) {
4040
WithRMNRemoteForwardUpdates(offRamp2).
4141
WithRMNRemoteCursedSubject(new(big.Int).SetUint64(chainSelector1)).
4242
WithRMNRemoteCursedSubject(new(big.Int).SetUint64(chainSelector2)).
43-
// Add missing fields
4443
Build()
4544
require.NoError(t, err)
4645

@@ -90,8 +89,6 @@ func TestDecodeRouterData(t *testing.T) {
9089
require.Len(t, storage.RMNRemote.CursedSubjects, 2)
9190
require.Equal(t, storage.RMNRemote.CursedSubjects[0].Uint64(), chainSelector1)
9291
require.Equal(t, storage.RMNRemote.CursedSubjects[1].Uint64(), chainSelector2)
93-
94-
// Add missing fields
9592
})
9693

9794
t.Run("TestMapper", func(t *testing.T) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package merkleroot
2+
3+
import (
4+
"math/big"
5+
6+
"github.com/xssnick/tonutils-go/address"
7+
)
8+
9+
type Storage struct {
10+
Root *big.Int `tlb:"## 256"`
11+
Owner *address.Address `tlb:"addr"`
12+
Timestamp uint64 `tlb:"## 64"`
13+
MinMsgNr uint64 `tlb:"## 64"`
14+
MaxMsgNr uint64 `tlb:"## 64"`
15+
MessageStates *big.Int `tlb:"## 128"`
16+
DeliveredMessageCount uint16 `tlb:"## 16"`
17+
}

pkg/ccip/model/common.go

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/hex"
55
"errors"
66
"fmt"
7+
"math/big"
78

89
"github.com/xssnick/tonutils-go/address"
910
"github.com/xssnick/tonutils-go/tlb"
@@ -17,18 +18,9 @@ type Mapper[B any] interface {
1718

1819
// FromBindingDataHex loads a TL-B binding from hex and then populates the model.
1920
func FromBindingDataHex[B any, M Mapper[B]](m M, dataHex string) error {
20-
data, err := hex.DecodeString(dataHex)
21-
if err != nil {
22-
return fmt.Errorf("decode hex: %w", err)
23-
}
24-
25-
root, err := cell.FromBOC(data)
21+
root, err := loadCell(dataHex)
2622
if err != nil {
27-
return fmt.Errorf("decode BOC: %w", err)
28-
}
29-
30-
if root == nil {
31-
return errors.New("parse BOC: nil root cell")
23+
return err
3224
}
3325

3426
var raw B
@@ -54,6 +46,73 @@ func ToBindingDataHex[B any, M Mapper[B]](m M) (string, error) {
5446
return hex.EncodeToString(root.ToBOC()), nil
5547
}
5648

49+
// bigIntArrayToHexArray converts a []*big.Int to an array of hex string padded to exactly `size` bytes.
50+
func bigIntArrayToHexArray(arr []*big.Int, size int) ([]string, error) {
51+
if arr == nil {
52+
return nil, nil
53+
}
54+
55+
hexValues := make([]string, 0, len(arr))
56+
57+
for _, v := range arr {
58+
h, err := bigIntToHex(v, size)
59+
if err != nil {
60+
return hexValues, err
61+
}
62+
hexValues = append(hexValues, h)
63+
}
64+
65+
return hexValues, nil
66+
}
67+
68+
// bigIntToHex converts a big.Int to a hex string padded to exactly `size` bytes.
69+
func bigIntToHex(n *big.Int, size int) (string, error) {
70+
if n == nil {
71+
return "", errors.New("nil big.Int")
72+
}
73+
74+
bytes := n.Bytes()
75+
76+
// If integer does not fit in desired size
77+
if len(bytes) > size {
78+
return "", fmt.Errorf(
79+
"integer too large: needs %d bytes but size is %d",
80+
len(bytes), size,
81+
)
82+
}
83+
84+
rootBytes := n.FillBytes(make([]byte, size))
85+
return hex.EncodeToString(rootBytes), nil
86+
}
87+
88+
// hexToBigInt converts a hex string to a big int representation
89+
func hexToBigInt(n string) (*big.Int, error) {
90+
rootBytes, err := hex.DecodeString(n)
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
return new(big.Int).SetBytes(rootBytes), nil
96+
}
97+
98+
func loadCell(dataHex string) (*cell.Cell, error) {
99+
data, err := hex.DecodeString(dataHex)
100+
if err != nil {
101+
return nil, fmt.Errorf("decode hex: %w", err)
102+
}
103+
104+
root, err := cell.FromBOC(data)
105+
if err != nil {
106+
return nil, fmt.Errorf("decode BOC: %w", err)
107+
}
108+
109+
if root == nil {
110+
return nil, errors.New("parse BOC: nil root cell")
111+
}
112+
113+
return root, nil
114+
}
115+
57116
type Ownable2Step struct {
58117
Owner *address.Address `json:"owner"`
59118
PendingOwner *address.Address `json:"pendingOwner,omitempty"`

0 commit comments

Comments
 (0)