From f9feb54ddb371d47d83a4e92bd389d62513bb36f Mon Sep 17 00:00:00 2001 From: "aleksej.paschenko" Date: Wed, 10 Apr 2024 21:43:06 +0300 Subject: [PATCH] Decode ext in msg for highload wallet v3r1 --- abi/generated_test.go | 132 +++++++++++++++++++++++++++++++++++++- abi/get_methods.go | 72 +++++++++++++++++++++ abi/interfaces.go | 18 +++++- abi/messages.md | 1 + abi/messages_generated.go | 22 +++++-- abi/schemas/wallets.xml | 31 +++++++++ abi/types.go | 34 ++++++++++ 7 files changed, 302 insertions(+), 8 deletions(-) diff --git a/abi/generated_test.go b/abi/generated_test.go index 52b1f81..779d551 100644 --- a/abi/generated_test.go +++ b/abi/generated_test.go @@ -1062,6 +1062,136 @@ func TestDecodeExternalIn(t *testing.T) { wantOpName string wantValue func() any }{ + { + name: "highload wallet v3 - jetton transfer", + interfaces: []ContractInterface{WalletHighloadV3R1}, + wantOpName: "HighloadWalletSignedV3", + boc: "te6ccgECBAEAAQwAAcWIAdJ6F/XoVT62daLt612xzAWuJPA9cAk+F5bifcMnw3uuBJ4krVRznuQJVWa7b5YZXZbyhQ9ypfRice+gJkPTVJGL6qLnQi57P4N9vIIvjbTxPvBBeSn5Vdk4jbhh2DI5iDQBASUAAAABA//61AAAAADMMooIAB9EAgFoYgBch4rJB2cPeC7H+qWhc5NL4Tn35ZEO/PqhjjJtLxbjISC+vCAAAAAAAAAAAAAAAAAAAQMArg+KfqUAAAAAAAAAADD0JAgA2ZpktQsYby0n9cV5VWOFINBjScIU2HdondFsK3lDpEEAGzNMlqFjDeWk/rivKqxwpBoMaThCmw7tE7othW8odIgII8NGAA==", + wantValue: func() any { + b := HighloadWalletSignedV3ExtInMsgBody{ + Signature: tlb.Bits512{}, + Msg: HighloadV3MsgInner{ + SubwalletId: 1, + SendMode: 3, + MessageToSend: MessageRelaxed{ + SumType: "MessageInternal", + MessageInternal: struct { + IhrDisabled bool + Bounce bool + Bounced bool + Src tlb.MsgAddress + Dest tlb.MsgAddress + Value tlb.CurrencyCollection + IhrFee tlb.Grams + FwdFee tlb.Grams + CreatedLt uint64 + CreatedAt uint32 + Init *tlb.EitherRef[tlb.StateInit] `tlb:"maybe"` + Body tlb.EitherRef[InMsgBody] + }{ + IhrDisabled: true, + Bounce: true, + Src: tlb.MsgAddress{SumType: "AddrNone"}, + Dest: mustToMsgAddress("0:b90f15920ece1ef05d8ff54b42e72697c273efcb221df9f5431c64da5e2dc642"), + Value: tlb.CurrencyCollection{ + Grams: tlb.Grams(400000000), + }, + IhrFee: 0, + FwdFee: 0, + CreatedLt: 0, + CreatedAt: 0, + Init: nil, + Body: tlb.EitherRef[InMsgBody]{ + IsRight: true, + Value: InMsgBody{ + SumType: "JettonTransfer", + OpCode: pointer(uint32(260734629)), + Value: JettonTransferMsgBody{ + QueryId: 0, + Amount: mustToVarUInteger16("1000000"), + Destination: pointer(ton.MustParseAccountID("0:6ccd325a858c379693fae2bcaab1c2906831a4e10a6c3bb44ee8b615bca1d220")).ToMsgAddress(), + ResponseDestination: pointer(ton.MustParseAccountID("0:6ccd325a858c379693fae2bcaab1c2906831a4e10a6c3bb44ee8b615bca1d220")).ToMsgAddress(), + CustomPayload: nil, + ForwardTonAmount: mustToVarUInteger16("300000000"), + ForwardPayload: tlb.EitherRef[JettonPayload]{ + IsRight: false, + }, + }, + }, + }, + }, + }, + QueryId: HighloadV3QueryId{ + Shift: 8191, + BitNumber: 362, + }, + CreatedAt: 1712932100, + Timeout: 1000, + }, + } + sig, _ := hex.DecodeString("8801d27a17f5e8553eb675a2edeb5db1cc05ae24f03d70093e1796e27dc327c37bae049e24ad54739ee4095566bb6f96195d96f2850f72a5f46271efa02643d3") + copy(b.Signature[:], sig) + return b + }, + }, + { + name: "highload wallet v3 - ton transfer", + interfaces: []ContractInterface{WalletHighloadV3R1}, + wantOpName: "HighloadWalletSignedV3", + boc: "b5ee9c720101030100b10001c588004a3c91f996eb4f3f3799bde7f22005b44a622abcd3ab5ef4b793bf996422542c02b30423be3ecfd979bb1d41e0e8bf5154e58af1e3dea23e35edfd837919c34bb530f91a7c3ae7adc9aac317a5a630b2c52a82bf889a83a9a4a48471247bacd874010125000010ad0300219600000000cc27b04c0070840200664200126de9fd8617ede66d3dd8eb09d01259144017ca508a603776919e6a83ed24581cc4b40000000000000000000000000000", + wantValue: func() any { + b := HighloadWalletSignedV3ExtInMsgBody{ + Signature: tlb.Bits512{}, + Msg: HighloadV3MsgInner{ + SubwalletId: 4269, + SendMode: 3, + MessageToSend: MessageRelaxed{ + SumType: "MessageInternal", + MessageInternal: struct { + IhrDisabled bool + Bounce bool + Bounced bool + Src tlb.MsgAddress + Dest tlb.MsgAddress + Value tlb.CurrencyCollection + IhrFee tlb.Grams + FwdFee tlb.Grams + CreatedLt uint64 + CreatedAt uint32 + Init *tlb.EitherRef[tlb.StateInit] `tlb:"maybe"` + Body tlb.EitherRef[InMsgBody] + }{ + IhrDisabled: true, + Src: tlb.MsgAddress{ + SumType: "AddrNone", + }, + Dest: mustToMsgAddress("0:24dbd3fb0c2fdbccda7bb1d613a024b228802f94a114c06eed233cd507da48b0"), + Value: tlb.CurrencyCollection{ + Grams: tlb.Grams(10000000), + }, + IhrFee: 0, + FwdFee: 0, + CreatedLt: 0, + CreatedAt: 0, + Init: nil, + Body: tlb.EitherRef[InMsgBody]{ + IsRight: false, + }, + }, + }, + QueryId: HighloadV3QueryId{ + Shift: 4, + BitNumber: 203, + }, + CreatedAt: 1712576550, + Timeout: 3600, + }, + } + sig, _ := hex.DecodeString("88004a3c91f996eb4f3f3799bde7f22005b44a622abcd3ab5ef4b793bf996422542c02b30423be3ecfd979bb1d41e0e8bf5154e58af1e3dea23e35edfd837919") + copy(b.Signature[:], sig) + return b + }, + }, { name: "wallet v4", interfaces: []ContractInterface{WalletV4R2}, @@ -1210,7 +1340,7 @@ func TestDecodeExternalIn(t *testing.T) { t.Fatalf("MessageDecoder() error: %v", err) } if *opName != tt.wantOpName { - t.Fatalf("got opname: %v, want: %v", opName, tt.wantOpName) + t.Fatalf("got opname: %v, want: %v", *opName, tt.wantOpName) } b, err := json.Marshal(value) if err != nil { diff --git a/abi/get_methods.go b/abi/get_methods.go index 215ad02..f3891a1 100644 --- a/abi/get_methods.go +++ b/abi/get_methods.go @@ -25,6 +25,7 @@ var KnownGetMethodsDecoder = map[string][]func(tlb.VmStack) (string, any, error) "get_editor": {DecodeGetEditorResult}, "get_full_domain": {DecodeGetFullDomainResult}, "get_jetton_data": {DecodeGetJettonDataResult}, + "get_last_clean_time": {DecodeGetLastCleanTimeResult}, "get_last_fill_up_time": {DecodeGetLastFillUpTimeResult}, "get_locker_bill_data": {DecodeGetLockerBillDataResult}, "get_locker_data": {DecodeGetLockerDataResult}, @@ -61,6 +62,7 @@ var KnownGetMethodsDecoder = map[string][]func(tlb.VmStack) (string, any, error) "get_telemint_auction_config": {DecodeGetTelemintAuctionConfigResult}, "get_telemint_auction_state": {DecodeGetTelemintAuctionStateResult}, "get_telemint_token_name": {DecodeGetTelemintTokenNameResult}, + "get_timeout": {DecodeGetTimeoutResult}, "get_torrent_hash": {DecodeGetTorrentHashResult}, "get_validator_controller_data": {DecodeGetValidatorControllerDataResult}, "get_wallet_address": {DecodeGetWalletAddressResult}, @@ -86,6 +88,7 @@ var KnownSimpleGetMethods = map[int][]func(ctx context.Context, executor Executo 78748: {GetPublicKey}, 80035: {GetLpData}, 80697: {GetAuctionInfo}, + 80822: {GetLastCleanTime}, 81467: {GetSubwalletId}, 81490: {GetNextProofInfo}, 81689: {GetPoolData}, @@ -110,6 +113,7 @@ var KnownSimpleGetMethods = map[int][]func(ctx context.Context, executor Executo 103232: {GetValidatorControllerData}, 104122: {GetLpMiningData}, 104346: {GetStorageParams}, + 105070: {GetTimeout}, 106029: {GetJettonData}, 107305: {GetLockupData}, 107307: {GetMultisigData}, @@ -146,6 +150,7 @@ var resultTypes = []interface{}{ &GetEditorResult{}, &GetFullDomainResult{}, &GetJettonDataResult{}, + &GetLastCleanTimeResult{}, &GetLastFillUpTimeResult{}, &GetLockerBillDataResult{}, &GetLockerDataResult{}, @@ -185,6 +190,7 @@ var resultTypes = []interface{}{ &GetTelemintAuctionConfigResult{}, &GetTelemintAuctionStateResult{}, &GetTelemintTokenNameResult{}, + &GetTimeoutResult{}, &GetTorrentHashResult{}, &GetValidatorControllerDataResult{}, &GetWalletAddressResult{}, @@ -724,6 +730,39 @@ func DecodeGetJettonDataResult(stack tlb.VmStack) (resultType string, resultAny return "GetJettonDataResult", result, err } +type GetLastCleanTimeResult struct { + Timestamp uint64 +} + +func GetLastCleanTime(ctx context.Context, executor Executor, reqAccountID ton.AccountID) (string, any, error) { + stack := tlb.VmStack{} + + // MethodID = 80822 for "get_last_clean_time" method + errCode, stack, err := executor.RunSmcMethodByID(ctx, reqAccountID, 80822, stack) + if err != nil { + return "", nil, err + } + if errCode != 0 && errCode != 1 { + return "", nil, fmt.Errorf("method execution failed with code: %v", errCode) + } + for _, f := range []func(tlb.VmStack) (string, any, error){DecodeGetLastCleanTimeResult} { + s, r, err := f(stack) + if err == nil { + return s, r, nil + } + } + return "", nil, fmt.Errorf("can not decode outputs") +} + +func DecodeGetLastCleanTimeResult(stack tlb.VmStack) (resultType string, resultAny any, err error) { + if len(stack) < 1 || (stack[0].SumType != "VmStkTinyInt" && stack[0].SumType != "VmStkInt") { + return "", nil, fmt.Errorf("invalid stack format") + } + var result GetLastCleanTimeResult + err = stack.Unmarshal(&result) + return "GetLastCleanTimeResult", result, err +} + type GetLastFillUpTimeResult struct { LastFillUpTime int64 } @@ -2222,6 +2261,39 @@ func DecodeGetTelemintTokenNameResult(stack tlb.VmStack) (resultType string, res return "GetTelemintTokenNameResult", result, err } +type GetTimeoutResult struct { + Timeout uint32 +} + +func GetTimeout(ctx context.Context, executor Executor, reqAccountID ton.AccountID) (string, any, error) { + stack := tlb.VmStack{} + + // MethodID = 105070 for "get_timeout" method + errCode, stack, err := executor.RunSmcMethodByID(ctx, reqAccountID, 105070, stack) + if err != nil { + return "", nil, err + } + if errCode != 0 && errCode != 1 { + return "", nil, fmt.Errorf("method execution failed with code: %v", errCode) + } + for _, f := range []func(tlb.VmStack) (string, any, error){DecodeGetTimeoutResult} { + s, r, err := f(stack) + if err == nil { + return s, r, nil + } + } + return "", nil, fmt.Errorf("can not decode outputs") +} + +func DecodeGetTimeoutResult(stack tlb.VmStack) (resultType string, resultAny any, err error) { + if len(stack) < 1 || (stack[0].SumType != "VmStkTinyInt" && stack[0].SumType != "VmStkInt") { + return "", nil, fmt.Errorf("invalid stack format") + } + var result GetTimeoutResult + err = stack.Unmarshal(&result) + return "GetTimeoutResult", result, err +} + type GetTorrentHashResult struct { TorrentHash tlb.Int257 } diff --git a/abi/interfaces.go b/abi/interfaces.go index 989987a..63b6d6e 100644 --- a/abi/interfaces.go +++ b/abi/interfaces.go @@ -338,6 +338,10 @@ var methodInvocationOrder = []MethodDescription{ Name: "get_jetton_data", InvokeFn: GetJettonData, }, + { + Name: "get_last_clean_time", + InvokeFn: GetLastCleanTime, + }, { Name: "get_last_fill_up_time", InvokeFn: GetLastFillUpTime, @@ -462,6 +466,10 @@ var methodInvocationOrder = []MethodDescription{ Name: "get_telemint_token_name", InvokeFn: GetTelemintTokenName, }, + { + Name: "get_timeout", + InvokeFn: GetTimeout, + }, { Name: "get_torrent_hash", InvokeFn: GetTorrentHash, @@ -747,7 +755,11 @@ var knownContracts = map[ton.Bits256]knownContractDescription{ }, ton.MustParseHash("11acad7955844090f283bf238bc1449871f783e7cc0979408d3f4859483e8525"): { contractInterfaces: []ContractInterface{WalletHighloadV3R1}, - getMethods: []InvokeFn{}, + getMethods: []InvokeFn{ + GetPublicKey, + GetSubwalletId, + GetTimeout, + }, }, ton.MustParseHash("1bd9c5a39bffb7a0f341588b5dd92b813a842bf65ef14109382200ceaf8f72df"): { contractInterfaces: []ContractInterface{NftAuctionGetgemsV3}, @@ -933,6 +945,10 @@ func (c ContractInterface) IntMsgs() []msgDecoderFunc { func (c ContractInterface) ExtInMsgs() []msgDecoderFunc { switch c { + case WalletHighloadV3R1: + return []msgDecoderFunc{ + decodeFuncHighloadWalletSignedV3ExtInMsgBody, + } case WalletV3R1: return []msgDecoderFunc{ decodeFuncWalletSignedV3ExtInMsgBody, diff --git a/abi/messages.md b/abi/messages.md index a755a03..38ccaec 100644 --- a/abi/messages.md +++ b/abi/messages.md @@ -41,6 +41,7 @@ The list below contains the supported message operations, their names and opcode | FinishUncooperativeChannelClose| 0x25432a91 | | GetRoyaltyParams| 0x693d3950 | | GetStaticData| 0x2fcb26a2 | +| HighloadWalletSignedV3| 0x00000000 | | InitPaymentChannel| 0x0e0620c2 | | JettonBurn| 0x595f07bc | | JettonBurnNotification| 0x7bdd97de | diff --git a/abi/messages_generated.go b/abi/messages_generated.go index ba50e04..44d744a 100644 --- a/abi/messages_generated.go +++ b/abi/messages_generated.go @@ -1877,18 +1877,22 @@ var ( decodeFuncWalletSignedV3ExtInMsgBody = decodeMsg(tlb.Tag{Val: 0x00000000, Len: 0}, WalletSignedV3ExtInMsgOp, WalletSignedV3ExtInMsgBody{}) // 0x00000000 decodeFuncWalletSignedV4ExtInMsgBody = decodeMsg(tlb.Tag{Val: 0x00000000, Len: 0}, WalletSignedV4ExtInMsgOp, WalletSignedV4ExtInMsgBody{}) + // 0x00000000 + decodeFuncHighloadWalletSignedV3ExtInMsgBody = decodeMsg(tlb.Tag{Val: 0x00000000, Len: 0}, HighloadWalletSignedV3ExtInMsgOp, HighloadWalletSignedV3ExtInMsgBody{}) ) var opcodedMsgExtInDecodeFunctions = map[uint32]msgDecoderFunc{} const ( - WalletSignedV3ExtInMsgOp MsgOpName = "WalletSignedV3" - WalletSignedV4ExtInMsgOp MsgOpName = "WalletSignedV4" + WalletSignedV3ExtInMsgOp MsgOpName = "WalletSignedV3" + WalletSignedV4ExtInMsgOp MsgOpName = "WalletSignedV4" + HighloadWalletSignedV3ExtInMsgOp MsgOpName = "HighloadWalletSignedV3" ) const ( - WalletSignedV3ExtInMsgOpCode MsgOpCode = 0x00000000 - WalletSignedV4ExtInMsgOpCode MsgOpCode = 0x00000000 + WalletSignedV3ExtInMsgOpCode MsgOpCode = 0x00000000 + WalletSignedV4ExtInMsgOpCode MsgOpCode = 0x00000000 + HighloadWalletSignedV3ExtInMsgOpCode MsgOpCode = 0x00000000 ) type WalletSignedV3ExtInMsgBody struct { @@ -1908,9 +1912,15 @@ type WalletSignedV4ExtInMsgBody struct { Payload WalletV1ToV4Payload } +type HighloadWalletSignedV3ExtInMsgBody struct { + Signature tlb.Bits512 + Msg HighloadV3MsgInner `tlb:"^"` +} + var KnownMsgExtInTypes = map[string]any{ - WalletSignedV3ExtInMsgOp: WalletSignedV3ExtInMsgBody{}, - WalletSignedV4ExtInMsgOp: WalletSignedV4ExtInMsgBody{}, + WalletSignedV3ExtInMsgOp: WalletSignedV3ExtInMsgBody{}, + WalletSignedV4ExtInMsgOp: WalletSignedV4ExtInMsgBody{}, + HighloadWalletSignedV3ExtInMsgOp: HighloadWalletSignedV3ExtInMsgBody{}, } var ( diff --git a/abi/schemas/wallets.xml b/abi/schemas/wallets.xml index 36c3de0..0b37281 100644 --- a/abi/schemas/wallets.xml +++ b/abi/schemas/wallets.xml @@ -13,6 +13,18 @@ created_lt:uint64 created_at:uint32 init:(Maybe (Either StateInit ^StateInit)) body:(Either ExtOutMsgBody ^ExtOutMsgBody) = MessageRelaxed; + + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + + message$_ {X:Type} info:CommonMsgInfoRelaxed + init:(Maybe (Either StateInit ^StateInit)) + body:(Either MessageRelaxed ^MessageRelaxed) = HighloadWalletV3MessageRelaxed; + + query_id#_ shift:uint13 bit_number:(## 10) = HighloadV3QueryId; + msg_inner#_ {n:#} subwallet_id:uint32 message_to_send:^MessageRelaxed send_mode:uint8 query_id:HighloadV3QueryId created_at:uint64 timeout:uint22 = HighloadV3MsgInner; @@ -95,6 +107,12 @@ 11acad7955844090f283bf238bc1449871f783e7cc0979408d3f4859483e8525 + + + + + + @@ -129,6 +147,16 @@ bool + + + uint32 + + + + + uint64 + + signed#_ signature:bits512 subwallet_id:uint32 valid_until:uint32 seqno:uint32 payload:WalletV1toV4Payload = ExternalMsgBody; @@ -137,4 +165,7 @@ signed#_ signature:bits512 subwallet_id:uint32 valid_until:uint32 seqno:uint32 op:int8 payload:WalletV1toV4Payload = ExternalMsgBody; + + signed#_ {n:#} signature:bits512 msg:^HighloadV3MsgInner = ExternalMsgBody; + \ No newline at end of file diff --git a/abi/types.go b/abi/types.go index 7574326..df2cd92 100644 --- a/abi/types.go +++ b/abi/types.go @@ -301,6 +301,40 @@ type AccountLists struct { List tlb.Hashmap[tlb.Bits256, tlb.Any] } +type CommonMsgInfoRelaxed struct { + Magic tlb.Magic `tlb:"$0"` + IhrDisabled bool + Bounce bool + Bounced bool + Src tlb.MsgAddress + Dest tlb.MsgAddress + Value tlb.CurrencyCollection + IhrFee tlb.Grams + FwdFee tlb.Grams + CreatedLt uint64 + CreatedAt uint32 +} + +type HighloadV3MsgInner struct { + SubwalletId uint32 + MessageToSend MessageRelaxed `tlb:"^"` + SendMode uint8 + QueryId HighloadV3QueryId + CreatedAt uint64 + Timeout tlb.Uint22 +} + +type HighloadV3QueryId struct { + Shift tlb.Uint13 + BitNumber tlb.Uint10 +} + +type HighloadWalletV3MessageRelaxed struct { + Info CommonMsgInfoRelaxed + Init *tlb.EitherRef[tlb.StateInit] `tlb:"maybe"` + Body tlb.EitherRef[MessageRelaxed] +} + type MessageRelaxed struct { tlb.SumType MessageInternal struct {