Skip to content

Commit 7ce6b88

Browse files
committed
removed dependency on substrate
1 parent 2b6f10d commit 7ce6b88

File tree

10 files changed

+1679
-428
lines changed

10 files changed

+1679
-428
lines changed

blockchain/abi/contract.go

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
package abi
2+
3+
import (
4+
"encoding/hex"
5+
"fmt"
6+
"math/big"
7+
"strconv"
8+
"strings"
9+
)
10+
11+
// ContractError represents a custom error from the contract
12+
type ContractError struct {
13+
Code string
14+
Message string
15+
}
16+
17+
func (e ContractError) Error() string {
18+
return fmt.Sprintf("contract error %s: %s", e.Code, e.Message)
19+
}
20+
21+
// Contract error signatures (4-byte selectors)
22+
var ContractErrorSignatures = map[string]ContractError{
23+
"0x12345678": {Code: "PNF", Message: "PoolNotFound"},
24+
"0x87654321": {Code: "AIP", Message: "AlreadyInPool"},
25+
"0x11111111": {Code: "ARQ", Message: "AlreadyRequested"},
26+
"0x22222222": {Code: "PNF2", Message: "PeerNotFound"},
27+
"0x33333333": {Code: "CR", Message: "CapacityReached"},
28+
"0x44444444": {Code: "UF", Message: "UserFlagged"},
29+
"0x55555555": {Code: "NM", Message: "NotMember"},
30+
"0x66666666": {Code: "AV", Message: "AlreadyVoted"},
31+
"0x77777777": {Code: "NAR", Message: "NoActiveRequest"},
32+
"0x88888888": {Code: "OCA", Message: "OnlyCreatorOrAdmin"},
33+
"0x99999999": {Code: "PNE", Message: "PoolNotEmpty"},
34+
"0xAAAAAAAA": {Code: "PRE", Message: "PendingRequestsExist"},
35+
"0xBBBBBBBB": {Code: "ITA", Message: "InvalidTokenAmount"},
36+
"0xCCCCCCCC": {Code: "IA", Message: "InsufficientAllowance"},
37+
}
38+
39+
// ParseContractError attempts to parse a contract error from the response
40+
func ParseContractError(data string) error {
41+
if !strings.HasPrefix(data, "0x") || len(data) < 10 {
42+
return nil
43+
}
44+
45+
// Extract the first 4 bytes (8 hex chars + 0x prefix)
46+
errorSig := data[:10]
47+
48+
if contractErr, exists := ContractErrorSignatures[errorSig]; exists {
49+
return contractErr
50+
}
51+
52+
return nil
53+
}
54+
55+
// Pool represents the Pool struct from the contract
56+
type Pool struct {
57+
Creator string `json:"creator"`
58+
ID uint32 `json:"id"`
59+
MaxChallengeResponsePeriod uint32 `json:"maxChallengeResponsePeriod"`
60+
MemberCount uint32 `json:"memberCount"`
61+
MaxMembers uint32 `json:"maxMembers"`
62+
RequiredTokens *big.Int `json:"requiredTokens"`
63+
MinPingTime *big.Int `json:"minPingTime"`
64+
Name string `json:"name"`
65+
Region string `json:"region"`
66+
}
67+
68+
// IsMemberOfPoolResult represents the result of isPeerIdMemberOfPool call
69+
type IsMemberOfPoolResult struct {
70+
IsMember bool `json:"isMember"`
71+
MemberAddress string `json:"memberAddress"`
72+
}
73+
74+
// GetMemberPeerIdsResult represents the result of getMemberPeerIds call
75+
type GetMemberPeerIdsResult struct {
76+
PeerIds []string `json:"peerIds"` // Array of bytes32 peer IDs
77+
}
78+
79+
// MethodSignatures contains the 4-byte method signatures
80+
var MethodSignatures = struct {
81+
Pools string
82+
IsPeerIdMemberOfPool string
83+
GetMemberPeerIds string
84+
RemoveMemberPeerId string
85+
}{
86+
Pools: "0xced08b2d", // pools(uint32) - updated signature
87+
IsPeerIdMemberOfPool: "0x95c5eb1a", // isPeerIdMemberOfPool(uint32,bytes32)
88+
GetMemberPeerIds: "0x31db3ae8", // getMemberPeerIds(uint32,address)
89+
RemoveMemberPeerId: "0x12345678", // removeMemberPeerId(uint32,bytes32) - TODO: Update with actual signature
90+
}
91+
92+
// DecodePoolsResult decodes the result from pools(uint32) contract call
93+
func DecodePoolsResult(data string) (*Pool, error) {
94+
// Remove 0x prefix
95+
if strings.HasPrefix(data, "0x") {
96+
data = data[2:]
97+
}
98+
99+
// Check if we have enough data (minimum 9 * 32 bytes = 288 hex chars)
100+
if len(data) < 288 {
101+
return nil, fmt.Errorf("insufficient data length: %d, expected at least 288", len(data))
102+
}
103+
104+
// Decode each 32-byte slot
105+
slots := make([]string, 0)
106+
for i := 0; i < len(data); i += 64 {
107+
if i+64 <= len(data) {
108+
slots = append(slots, data[i:i+64])
109+
}
110+
}
111+
112+
if len(slots) < 9 {
113+
return nil, fmt.Errorf("insufficient slots: %d, expected at least 9", len(slots))
114+
}
115+
116+
pool := &Pool{}
117+
118+
// According to the struct layout:
119+
// Slot 0: creator (20 bytes) + id (4 bytes) + maxChallengeResponsePeriod (4 bytes) + memberCount (4 bytes)
120+
slot0 := slots[0]
121+
122+
// Creator address (first 20 bytes, left-padded to 32 bytes)
123+
creatorHex := slot0[24:64] // Last 40 hex chars = 20 bytes
124+
pool.Creator = "0x" + creatorHex
125+
126+
// Check if creator is zero address (pool doesn't exist)
127+
isZeroAddress := true
128+
for _, char := range creatorHex {
129+
if char != '0' {
130+
isZeroAddress = false
131+
break
132+
}
133+
}
134+
if isZeroAddress {
135+
return nil, fmt.Errorf("pool not found (zero creator address)")
136+
}
137+
138+
// Parse packed fields from slot0 (before the address)
139+
// id (4 bytes), maxChallengeResponsePeriod (4 bytes), memberCount (4 bytes)
140+
packedFields := slot0[0:24] // First 24 hex chars = 12 bytes
141+
142+
// Parse ID (first 4 bytes)
143+
if len(packedFields) >= 8 {
144+
idHex := packedFields[0:8]
145+
id, err := strconv.ParseUint(idHex, 16, 32)
146+
if err != nil {
147+
return nil, fmt.Errorf("failed to parse pool ID: %w", err)
148+
}
149+
pool.ID = uint32(id)
150+
}
151+
152+
// Parse maxChallengeResponsePeriod (next 4 bytes)
153+
if len(packedFields) >= 16 {
154+
maxChallengeHex := packedFields[8:16]
155+
maxChallenge, err := strconv.ParseUint(maxChallengeHex, 16, 32)
156+
if err != nil {
157+
return nil, fmt.Errorf("failed to parse maxChallengeResponsePeriod: %w", err)
158+
}
159+
pool.MaxChallengeResponsePeriod = uint32(maxChallenge)
160+
}
161+
162+
// Parse memberCount (next 4 bytes)
163+
if len(packedFields) >= 24 {
164+
memberCountHex := packedFields[16:24]
165+
memberCount, err := strconv.ParseUint(memberCountHex, 16, 32)
166+
if err != nil {
167+
return nil, fmt.Errorf("failed to parse memberCount: %w", err)
168+
}
169+
pool.MemberCount = uint32(memberCount)
170+
}
171+
172+
// Slot 1: maxMembers (4 bytes) + padding
173+
slot1 := slots[1]
174+
175+
// Parse maxMembers from first 8 hex chars (4 bytes) of slot1
176+
maxMembersHex := slot1[56:64] // Last 8 hex chars for right-aligned uint32
177+
maxMembers, err := strconv.ParseUint(maxMembersHex, 16, 32)
178+
if err != nil {
179+
return nil, fmt.Errorf("failed to parse maxMembers: %w", err)
180+
}
181+
pool.MaxMembers = uint32(maxMembers)
182+
183+
// Slot 2: requiredTokens (uint256)
184+
slot2 := slots[2]
185+
requiredTokens := new(big.Int)
186+
requiredTokens.SetString(slot2, 16)
187+
pool.RequiredTokens = requiredTokens
188+
189+
// Slot 3: minPingTime (uint256)
190+
slot3 := slots[3]
191+
minPingTime := new(big.Int)
192+
minPingTime.SetString(slot3, 16)
193+
pool.MinPingTime = minPingTime
194+
195+
// Slot 4: offset to name string
196+
slot4 := slots[4]
197+
nameOffset, err := strconv.ParseUint(slot4, 16, 64)
198+
if err != nil {
199+
return nil, fmt.Errorf("failed to parse name offset: %w", err)
200+
}
201+
202+
// Slot 5: offset to region string
203+
slot5 := slots[5]
204+
regionOffset, err := strconv.ParseUint(slot5, 16, 64)
205+
if err != nil {
206+
return nil, fmt.Errorf("failed to parse region offset: %w", err)
207+
}
208+
209+
// Parse name string
210+
nameSlotIndex := int(nameOffset / 32)
211+
if nameSlotIndex < len(slots) {
212+
nameLength, err := strconv.ParseUint(slots[nameSlotIndex], 16, 64)
213+
if err != nil {
214+
return nil, fmt.Errorf("failed to parse name length: %w", err)
215+
}
216+
217+
if nameLength > 0 && nameSlotIndex+1 < len(slots) {
218+
nameHex := slots[nameSlotIndex+1]
219+
nameBytes, err := hex.DecodeString(nameHex)
220+
if err != nil {
221+
return nil, fmt.Errorf("failed to decode name: %w", err)
222+
}
223+
// Trim null bytes
224+
pool.Name = strings.TrimRight(string(nameBytes), "\x00")
225+
}
226+
}
227+
228+
// Parse region string
229+
regionSlotIndex := int(regionOffset / 32)
230+
if regionSlotIndex < len(slots) {
231+
regionLength, err := strconv.ParseUint(slots[regionSlotIndex], 16, 64)
232+
if err != nil {
233+
return nil, fmt.Errorf("failed to parse region length: %w", err)
234+
}
235+
236+
if regionLength > 0 && regionSlotIndex+1 < len(slots) {
237+
regionHex := slots[regionSlotIndex+1]
238+
regionBytes, err := hex.DecodeString(regionHex)
239+
if err != nil {
240+
return nil, fmt.Errorf("failed to decode region: %w", err)
241+
}
242+
// Trim null bytes
243+
pool.Region = strings.TrimRight(string(regionBytes), "\x00")
244+
}
245+
}
246+
247+
// Parse remaining fields from the packed slot0
248+
// Extract id, maxChallengeResponsePeriod, memberCount from slot0
249+
// These are packed in the first part of slot0 before the address
250+
251+
// For now, we'll extract what we can from the available data
252+
// In a full implementation, you'd need to properly parse the packed struct
253+
254+
return pool, nil
255+
}
256+
257+
// DecodeIsMemberOfPoolResult decodes the result from isPeerIdMemberOfPool(uint32,bytes32) contract call
258+
func DecodeIsMemberOfPoolResult(data string) (*IsMemberOfPoolResult, error) {
259+
// Remove 0x prefix
260+
if strings.HasPrefix(data, "0x") {
261+
data = data[2:]
262+
}
263+
264+
// Check if we have enough data (2 * 32 bytes = 128 hex chars)
265+
if len(data) < 128 {
266+
return nil, fmt.Errorf("insufficient data length: %d, expected 128", len(data))
267+
}
268+
269+
result := &IsMemberOfPoolResult{}
270+
271+
// First 32 bytes: isMember (bool)
272+
isMemberHex := data[0:64]
273+
isMember := false
274+
for _, char := range isMemberHex {
275+
if char != '0' {
276+
isMember = true
277+
break
278+
}
279+
}
280+
result.IsMember = isMember
281+
282+
// Next 32 bytes: memberAddress (address, right-aligned)
283+
memberAddressHex := data[64:128]
284+
// Extract the last 20 bytes (40 hex chars) for the address
285+
result.MemberAddress = "0x" + memberAddressHex[24:64]
286+
287+
return result, nil
288+
}
289+
290+
// DecodeGetMemberPeerIdsResult decodes the result from getMemberPeerIds(uint32,address) contract call
291+
func DecodeGetMemberPeerIdsResult(data string) (*GetMemberPeerIdsResult, error) {
292+
// Remove 0x prefix
293+
if strings.HasPrefix(data, "0x") {
294+
data = data[2:]
295+
}
296+
297+
// Check if we have enough data (minimum 64 hex chars for offset + length)
298+
if len(data) < 64 {
299+
return &GetMemberPeerIdsResult{PeerIds: []string{}}, nil
300+
}
301+
302+
// First 32 bytes: offset to array data
303+
offsetHex := data[0:64]
304+
offset, err := strconv.ParseUint(offsetHex, 16, 64)
305+
if err != nil {
306+
return nil, fmt.Errorf("failed to parse array offset: %w", err)
307+
}
308+
309+
// Calculate the position in hex chars (each byte = 2 hex chars)
310+
arrayStartPos := int(offset * 2)
311+
312+
if arrayStartPos >= len(data) {
313+
return &GetMemberPeerIdsResult{PeerIds: []string{}}, nil
314+
}
315+
316+
// Array length (32 bytes at the offset position)
317+
if arrayStartPos+64 > len(data) {
318+
return &GetMemberPeerIdsResult{PeerIds: []string{}}, nil
319+
}
320+
321+
arrayLengthHex := data[arrayStartPos : arrayStartPos+64]
322+
arrayLength, err := strconv.ParseUint(arrayLengthHex, 16, 64)
323+
if err != nil {
324+
return nil, fmt.Errorf("failed to parse array length: %w", err)
325+
}
326+
327+
result := &GetMemberPeerIdsResult{PeerIds: make([]string, 0, arrayLength)}
328+
329+
// Parse each bytes32 element (32 bytes = 64 hex chars each)
330+
dataStartPos := arrayStartPos + 64
331+
for i := uint64(0); i < arrayLength; i++ {
332+
elementStartPos := dataStartPos + int(i*64)
333+
elementEndPos := elementStartPos + 64
334+
335+
if elementEndPos > len(data) {
336+
break // Not enough data for this element
337+
}
338+
339+
peerIdHex := data[elementStartPos:elementEndPos]
340+
result.PeerIds = append(result.PeerIds, "0x"+peerIdHex)
341+
}
342+
343+
return result, nil
344+
}
345+
346+
// EncodePoolsCall encodes the pools(uint32) method call
347+
func EncodePoolsCall(poolID uint32) string {
348+
return fmt.Sprintf("%s%064x", MethodSignatures.Pools, poolID)
349+
}
350+
351+
// EncodeIsPeerIdMemberOfPoolCall encodes the isPeerIdMemberOfPool(uint32,bytes32) method call
352+
func EncodeIsPeerIdMemberOfPoolCall(poolID uint32, peerIDBytes32 string) string {
353+
// Remove 0x prefix from peerIDBytes32 if present
354+
if strings.HasPrefix(peerIDBytes32, "0x") {
355+
peerIDBytes32 = peerIDBytes32[2:]
356+
}
357+
358+
return fmt.Sprintf("%s%064x%s", MethodSignatures.IsPeerIdMemberOfPool, poolID, peerIDBytes32)
359+
}
360+
361+
// EncodeGetMemberPeerIdsCall encodes the getMemberPeerIds(uint32,address) method call
362+
func EncodeGetMemberPeerIdsCall(poolID uint32, memberAddress string) string {
363+
// Remove 0x prefix from memberAddress if present
364+
if strings.HasPrefix(memberAddress, "0x") {
365+
memberAddress = memberAddress[2:]
366+
}
367+
368+
// Pad address to 32 bytes (64 hex chars)
369+
paddedAddress := fmt.Sprintf("%064s", memberAddress)
370+
371+
return fmt.Sprintf("%s%064x%s", MethodSignatures.GetMemberPeerIds, poolID, paddedAddress)
372+
}
373+
374+
// EncodeRemoveMemberPeerIdCall encodes the removeMemberPeerId(uint32,bytes32) method call
375+
func EncodeRemoveMemberPeerIdCall(poolID uint32, peerIDBytes32 string) string {
376+
// Remove 0x prefix from peerIDBytes32 if present
377+
if strings.HasPrefix(peerIDBytes32, "0x") {
378+
peerIDBytes32 = peerIDBytes32[2:]
379+
}
380+
381+
return fmt.Sprintf("%s%064x%s", MethodSignatures.RemoveMemberPeerId, poolID, peerIDBytes32)
382+
}

0 commit comments

Comments
 (0)