diff --git a/abi/jetton.go b/abi/jetton.go index e9984dd..71964c3 100644 --- a/abi/jetton.go +++ b/abi/jetton.go @@ -147,3 +147,99 @@ func (j *JettonPayload) UnmarshalTLB(cell *boc.Cell, decoder *tlb.Decoder) error return nil } + +func (j *JettonNotifyMsgBody) UnmarshalTLB(cell *boc.Cell, decoder *tlb.Decoder) error { + var prefix struct { + QueryId uint64 + Amount tlb.VarUInteger16 + Sender tlb.MsgAddress + } + err := decoder.Unmarshal(cell, &prefix) + if err != nil { + return err + } + j.QueryId = prefix.QueryId + j.Amount = prefix.Amount + j.Sender = prefix.Sender + j.ForwardPayload = failsafeJettonPayloadEitherRef(cell, decoder) + return nil +} + +func (j *JettonTransferMsgBody) UnmarshalTLB(cell *boc.Cell, decoder *tlb.Decoder) error { + var prefix struct { + QueryId uint64 + Amount tlb.VarUInteger16 + Destination tlb.MsgAddress + ResponseDestination tlb.MsgAddress + } + err := decoder.Unmarshal(cell, &prefix) + if err != nil { + return err + } + isCustomPayload, err := cell.ReadUint(1) + if err != nil { + return err + } + var ( + customPayload *tlb.Any + forwardTonAmount tlb.VarUInteger16 + ) + if isCustomPayload == 1 && cell.RefsAvailableForRead() > 0 { + ref, _ := cell.NextRef() + a := tlb.Any(*ref) + customPayload = &a + } + err = decoder.Unmarshal(cell, &forwardTonAmount) + if err != nil { + return err + } + j.QueryId = prefix.QueryId + j.Amount = prefix.Amount + j.Destination = prefix.Destination + j.ResponseDestination = prefix.ResponseDestination + j.CustomPayload = customPayload + j.ForwardTonAmount = forwardTonAmount + j.ForwardPayload = failsafeJettonPayloadEitherRef(cell, decoder) + return nil +} + +func (j *JettonInternalTransferMsgBody) UnmarshalTLB(cell *boc.Cell, decoder *tlb.Decoder) error { + var res struct { + QueryId uint64 + Amount tlb.VarUInteger16 + From tlb.MsgAddress + ResponseAddress tlb.MsgAddress + ForwardTonAmount tlb.VarUInteger16 + } + err := decoder.Unmarshal(cell, &res) + if err != nil { + return err + } + j.QueryId = res.QueryId + j.Amount = res.Amount + j.From = res.From + j.ResponseAddress = res.ResponseAddress + j.ForwardTonAmount = res.ForwardTonAmount + j.ForwardPayload = failsafeJettonPayloadEitherRef(cell, decoder) + return nil +} + +func failsafeJettonPayloadEitherRef(cell *boc.Cell, decoder *tlb.Decoder) tlb.EitherRef[JettonPayload] { + isRight, err := cell.ReadUint(1) + switch { + case err != nil: + return tlb.EitherRef[JettonPayload]{} // empty either + case isRight == 1 && cell.RefsAvailableForRead() < 1: // invalid either + return tlb.EitherRef[JettonPayload]{ + IsRight: true, + } + case isRight == 1: // cell.RefsAvailableForRead() >= 1 + cell, _ = cell.NextRef() + } + var res JettonPayload + err = decoder.Unmarshal(cell, &res) + return tlb.EitherRef[JettonPayload]{ + IsRight: isRight == 1, + Value: res, + } +} diff --git a/abi/jetton_test.go b/abi/jetton_test.go index 1b6eb3e..6fbd029 100644 --- a/abi/jetton_test.go +++ b/abi/jetton_test.go @@ -1,9 +1,11 @@ package abi import ( + "bytes" "encoding/json" "github.com/tonkeeper/tongo/boc" "github.com/tonkeeper/tongo/tlb" + "math/big" "reflect" "testing" ) @@ -14,6 +16,19 @@ func assert(t *testing.T, err error) { } } +func mustToAny(t *testing.T, s string) *tlb.Any { + b, err := boc.DeserializeBocHex(s) + if err != nil { + t.Fatal(err) + } + var r tlb.Any + err = tlb.Unmarshal(b[0], &r) + if err != nil { + t.Fatal(err) + } + return &r +} + func TestJettonPayloadJSMarshaling(t *testing.T) { tests := []struct { name string @@ -84,3 +99,202 @@ func TestJettonPayloadJSMarshaling(t *testing.T) { }) } } + +func TestJettonCustomUnmarshalling(t *testing.T) { + tests := []struct { + name string + boc string + want any + }{ + { + name: "Valid jetton transfer with forward and without custom payload", + boc: "b5ee9c720101020100690001ac0f8a7ea5546de4ef59be1a6b5cdf061db67801465aa59db01447fc9fd217528b27ded0dbc07f3f7b540d3cc8504d52a46973050037ef56fa125ff70327f2f7f19da19210377e2b5908f5b5595f66c3a09c35b22b020301001c0000000031383437333938303832", + want: JettonTransferMsgBody{ + QueryId: 6083770388301355627, + Amount: tlb.VarUInteger16(*big.NewInt(884501240679)), + Destination: mustToMsgAddress("0:a32d52ced80a23fe4fe90ba94593ef686de03f9fbdaa069e642826a95234b982"), + ResponseDestination: mustToMsgAddress("0:dfbd5be8497fdc0c9fcbdfc676864840ddf8ad6423d6d5657d9b0e8270d6c8ac"), + CustomPayload: nil, + ForwardTonAmount: tlb.VarUInteger16(*big.NewInt(1)), + ForwardPayload: tlb.EitherRef[JettonPayload]{ + IsRight: true, + Value: JettonPayload{ + OpCode: pointer(uint32(0)), + SumType: TextCommentJettonOp, + Value: TextCommentJettonPayload{ + Text: "1847398082", + }, + }, + }, + }, + }, + { + name: "Valid jetton transfer with forward and custom payload", + boc: "b5ee9c72010237010004fb0002ac0f8a7ea57361fe02fbd836f2539a1681a7a801f37b81bc8c38345c005f5b52aa177612b91f3ee6cd1ae18a2f5398fc0a1f1cb500333c7998a8b9d8e669d81830ad3daa01a1837f72c28c01cbc78cd0ee6ea9a054a203010201080df602d603001600000000393832303132310946034234ad7214de4fd3e58a483bad658e2daa61aa004ef0fa541c844732e283a311001e042205817002050628480101ac7196a16449b7019e14749fdd3eb33bf15d1d45abc74292fa781f7b013ea576001d22012007082848010113ee0db49bc066f2fcdf5ebc32a1b6fae201a50d9c4912d027ce0343f55a9e41001b220120090a2201200b0c284801017397e54bdf4c77f32d712cc2b102320a00bfcc5cb955319b812ab38a7b2a2db6001a2201200d0e28480101dce12c77b9d7c1eff024a3373a7fa1f7191bcba2405879b8cd82510b5fdf81060018284801017df47110b15a482d75e1465a7557c7f929a2a878cc179263ea4fba65baa9296700192201200f10284801016fafe2d5e2580903c9fdd59968e2a71941c528f90676069c334186cc5466ea7100162201201112220120131428480101f95a8c9a4b45ea9e1640eb1f01c0a903e2f08550233cccf5b8a6ffc00bf20c39001522012015162848010135c7b7aece7eb6d8746cbc5efe957cf2f58f20edccb0e1e87ae4899cba13f698001428480101d87d5ec47172689a0dea012d00897d083fc62e3f2dc79f5d9f30d08b9a6ef91b001322012017182848010162ecbcf1dc67b60938d74ad0623f31d23c243c939ea599cb649563e9f3fe850a0012220120191a284801017a494e49cad5216a39a3a47aea711055dfb18aee23cf30ab7d298a88a59b53fc00112201201b1c28480101bbd8d2fe0e489c6f749992d17892ec54a3f7ab659f063fb774866ee3f2efab1200102201201d1e2201201f2028480101c89f69f387d22673062227be63d7661d49ee6169bd512ffe50957f6c8ffcb4fc000e22012021222848010109c1df2f152428c52898acc302528493b8120c498f1069ad66fe810c5a708a05000d220120232428480101c8a2a5686729b0d23761a3bc1f8443c25a98c981b50e50e8952b03c185aa9eb3000c284801013ee950c82bb9203ca2b32db01512c74d238d0637b941dc282d5e96b7aea119bc000a220120252628480101fa7278136d1be8945413d1976eaefe2993175082fb2d1c847477ce4e240bb11d0008220120272828480101e49d0e7dd1cc094082c9f7937a7680ba9c4937412f1e06973fa1f8c3b2a2213b0007220120292a28480101e55e41515fc9ec90f8c5b0c29d3bcccbe8afadfcfc74e59458a82c5823cfb35800072201202b2c2201202d2e2848010190e8a0b6b7552c01212fcf1c2efde23961a411f538f0ceb7880c43ac96483c2300052201202f3028480101f0223ccf937da2f7102a14a0e452dccfd0971c99e481d141c685ec820d2cc8b600042848010179d4ddfaf086a46dae7a3e44674dce72629e213253440e99185326da781dbcd30002220120313228480101bc7bf637739ebf54b7d80b4473b7f7783a6e5f544cb72eedff9b5a4ebf09d48a0000220120333422047f3135362848010174b89492389e48103c1b14bc73b9ae6f86b1a4d7b3e235f11489a9cdaf7472060000005db828b9d8e669d81830ad3daa01a1837f72c28c01cbc78cd0ee6ea9a05494e685a069e800019bd4c2800001b2632c8228480101d9e142e6d1c8cd21136dafd313171a0b04de94f314a6f381f0bcd50e323fbd870000", + want: JettonTransferMsgBody{ + QueryId: 8314205675871287026, + Amount: tlb.VarUInteger16(*big.NewInt(247521090170)), + Destination: mustToMsgAddress("0:f9bdc0de461c1a2e002fada9550bbb095c8f9f73668d70c517a9cc7e050f8e5a"), + ResponseDestination: mustToMsgAddress("0:ccf1e662a2e76399a76060c2b4f6a806860dfdcb0a30072f1e3343b9baa68152"), + CustomPayload: mustToAny(t, "b5ee9c72010235010004940001080df602d6010946034234ad7214de4fd3e58a483bad658e2daa61aa004ef0fa541c844732e283a311001e022205817002030428480101ac7196a16449b7019e14749fdd3eb33bf15d1d45abc74292fa781f7b013ea576001d22012005062848010113ee0db49bc066f2fcdf5ebc32a1b6fae201a50d9c4912d027ce0343f55a9e41001b2201200708220120090a284801017397e54bdf4c77f32d712cc2b102320a00bfcc5cb955319b812ab38a7b2a2db6001a2201200b0c28480101dce12c77b9d7c1eff024a3373a7fa1f7191bcba2405879b8cd82510b5fdf81060018284801017df47110b15a482d75e1465a7557c7f929a2a878cc179263ea4fba65baa9296700192201200d0e284801016fafe2d5e2580903c9fdd59968e2a71941c528f90676069c334186cc5466ea7100162201200f10220120111228480101f95a8c9a4b45ea9e1640eb1f01c0a903e2f08550233cccf5b8a6ffc00bf20c39001522012013142848010135c7b7aece7eb6d8746cbc5efe957cf2f58f20edccb0e1e87ae4899cba13f698001428480101d87d5ec47172689a0dea012d00897d083fc62e3f2dc79f5d9f30d08b9a6ef91b001322012015162848010162ecbcf1dc67b60938d74ad0623f31d23c243c939ea599cb649563e9f3fe850a00122201201718284801017a494e49cad5216a39a3a47aea711055dfb18aee23cf30ab7d298a88a59b53fc0011220120191a28480101bbd8d2fe0e489c6f749992d17892ec54a3f7ab659f063fb774866ee3f2efab1200102201201b1c2201201d1e28480101c89f69f387d22673062227be63d7661d49ee6169bd512ffe50957f6c8ffcb4fc000e2201201f202848010109c1df2f152428c52898acc302528493b8120c498f1069ad66fe810c5a708a05000d220120212228480101c8a2a5686729b0d23761a3bc1f8443c25a98c981b50e50e8952b03c185aa9eb3000c284801013ee950c82bb9203ca2b32db01512c74d238d0637b941dc282d5e96b7aea119bc000a220120232428480101fa7278136d1be8945413d1976eaefe2993175082fb2d1c847477ce4e240bb11d0008220120252628480101e49d0e7dd1cc094082c9f7937a7680ba9c4937412f1e06973fa1f8c3b2a2213b0007220120272828480101e55e41515fc9ec90f8c5b0c29d3bcccbe8afadfcfc74e59458a82c5823cfb3580007220120292a2201202b2c2848010190e8a0b6b7552c01212fcf1c2efde23961a411f538f0ceb7880c43ac96483c2300052201202d2e28480101f0223ccf937da2f7102a14a0e452dccfd0971c99e481d141c685ec820d2cc8b600042848010179d4ddfaf086a46dae7a3e44674dce72629e213253440e99185326da781dbcd300022201202f3028480101bc7bf637739ebf54b7d80b4473b7f7783a6e5f544cb72eedff9b5a4ebf09d48a0000220120313222047f3133342848010174b89492389e48103c1b14bc73b9ae6f86b1a4d7b3e235f11489a9cdaf7472060000005db828b9d8e669d81830ad3daa01a1837f72c28c01cbc78cd0ee6ea9a05494e685a069e800019bd4c2800001b2632c8228480101d9e142e6d1c8cd21136dafd313171a0b04de94f314a6f381f0bcd50e323fbd870000"), + ForwardTonAmount: tlb.VarUInteger16(*big.NewInt(1)), + ForwardPayload: tlb.EitherRef[JettonPayload]{ + IsRight: true, + Value: JettonPayload{ + OpCode: pointer(uint32(0)), + SumType: TextCommentJettonOp, + Value: TextCommentJettonPayload{ + Text: "9820121", + }, + }, + }, + }, + }, + { + name: "Jetton transfer with invalid custom payload", + boc: "b5ee9c720101010100630000c20f8a7ea57361fe02fbd836f2539a1681a7a801f37b81bc8c38345c005f5b52aa177612b91f3ee6cd1ae18a2f5398fc0a1f1cb500333c7998a8b9d8e669d81830ad3daa01a1837f72c28c01cbc78cd0ee6ea9a054a2020000000039383230313231", + want: JettonTransferMsgBody{ + QueryId: 8314205675871287026, + Amount: tlb.VarUInteger16(*big.NewInt(247521090170)), + Destination: mustToMsgAddress("0:f9bdc0de461c1a2e002fada9550bbb095c8f9f73668d70c517a9cc7e050f8e5a"), + ResponseDestination: mustToMsgAddress("0:ccf1e662a2e76399a76060c2b4f6a806860dfdcb0a30072f1e3343b9baa68152"), + CustomPayload: nil, + ForwardTonAmount: tlb.VarUInteger16(*big.NewInt(1)), + ForwardPayload: tlb.EitherRef[JettonPayload]{ + IsRight: false, + Value: JettonPayload{ + OpCode: pointer(uint32(0)), + SumType: TextCommentJettonOp, + Value: TextCommentJettonPayload{ + Text: "9820121", + }, + }, + }, + }, + }, + { + name: "Jetton transfer with invalid forward payload", + boc: "b5ee9c720101010100580000ac0f8a7ea57361fe02fbd836f2539a1681a7a801f37b81bc8c38345c005f5b52aa177612b91f3ee6cd1ae18a2f5398fc0a1f1cb500333c7998a8b9d8e669d81830ad3daa01a1837f72c28c01cbc78cd0ee6ea9a0548203", + want: JettonTransferMsgBody{ + QueryId: 8314205675871287026, + Amount: tlb.VarUInteger16(*big.NewInt(247521090170)), + Destination: mustToMsgAddress("0:f9bdc0de461c1a2e002fada9550bbb095c8f9f73668d70c517a9cc7e050f8e5a"), + ResponseDestination: mustToMsgAddress("0:ccf1e662a2e76399a76060c2b4f6a806860dfdcb0a30072f1e3343b9baa68152"), + CustomPayload: nil, + ForwardTonAmount: tlb.VarUInteger16(*big.NewInt(1)), + ForwardPayload: tlb.EitherRef[JettonPayload]{ + IsRight: true, + Value: JettonPayload{}, + }, + }, + }, + { + name: "Jetton transfer without forward payload", + boc: "b5ee9c720101010100580000ab0f8a7ea57361fe02fbd836f2539a1681a7a801f37b81bc8c38345c005f5b52aa177612b91f3ee6cd1ae18a2f5398fc0a1f1cb500333c7998a8b9d8e669d81830ad3daa01a1837f72c28c01cbc78cd0ee6ea9a0548203", + want: JettonTransferMsgBody{ + QueryId: 8314205675871287026, + Amount: tlb.VarUInteger16(*big.NewInt(247521090170)), + Destination: mustToMsgAddress("0:f9bdc0de461c1a2e002fada9550bbb095c8f9f73668d70c517a9cc7e050f8e5a"), + ResponseDestination: mustToMsgAddress("0:ccf1e662a2e76399a76060c2b4f6a806860dfdcb0a30072f1e3343b9baa68152"), + CustomPayload: nil, + ForwardTonAmount: tlb.VarUInteger16(*big.NewInt(1)), + ForwardPayload: tlb.EitherRef[JettonPayload]{}, + }, + }, + { + name: "Jetton transfer with empty payload", + boc: "b5ee9c720101010100580000ac0f8a7ea57361fe02fbd836f2539a1681a7a801f37b81bc8c38345c005f5b52aa177612b91f3ee6cd1ae18a2f5398fc0a1f1cb500333c7998a8b9d8e669d81830ad3daa01a1837f72c28c01cbc78cd0ee6ea9a0548202", + want: JettonTransferMsgBody{ + QueryId: 8314205675871287026, + Amount: tlb.VarUInteger16(*big.NewInt(247521090170)), + Destination: mustToMsgAddress("0:f9bdc0de461c1a2e002fada9550bbb095c8f9f73668d70c517a9cc7e050f8e5a"), + ResponseDestination: mustToMsgAddress("0:ccf1e662a2e76399a76060c2b4f6a806860dfdcb0a30072f1e3343b9baa68152"), + CustomPayload: nil, + ForwardTonAmount: tlb.VarUInteger16(*big.NewInt(1)), + ForwardPayload: tlb.EitherRef[JettonPayload]{}, + }, + }, + { + name: "Jetton notify", + boc: "b5ee9c720101020100470001687362d09c546de4ef0759cac2601fe55ff623c80013d1ae5e0177ce7efcc4d75d6b2140dfcc27b135b340d93453031837bd2f8a5f01001c0000000034373035303031333230", + want: JettonNotifyMsgBody{ + QueryId: 6083770386919049922, + Sender: mustToMsgAddress("0:09e8d72f00bbe73f7e626baeb590a06fe613d89ad9a06c9a29818c1bde97c52f"), + Amount: tlb.VarUInteger16(*big.NewInt(2191876121148)), + ForwardPayload: tlb.EitherRef[JettonPayload]{ + IsRight: true, + Value: JettonPayload{ + OpCode: pointer(uint32(0)), + SumType: TextCommentJettonOp, + Value: TextCommentJettonPayload{ + Text: "4705001320", + }, + }, + }, + }, + }, + // + { + name: "Jetton internal transfer", + boc: "b5ee9c720101020100600001ad178d45190000000067065c7e66532ecb20c5f80187e3b51d9b51f19a85759c449d47e36d2e3d54fea98925a9a320a4f69eae7ef90030fc76a3b36a3e3350aeb38893a8fc6da5c7aa9fd53124b53464149ed3d5cfdf040701000800000000", + want: JettonInternalTransferMsgBody{ + QueryId: 1728470142, + From: mustToMsgAddress("0:c3f1da8ecda8f8cd42bace224ea3f1b6971eaa7f54c492d4d190527b4f573f7c"), + ResponseAddress: mustToMsgAddress("0:c3f1da8ecda8f8cd42bace224ea3f1b6971eaa7f54c492d4d190527b4f573f7c"), + Amount: tlb.VarUInteger16(*big.NewInt(111269393861727)), + ForwardTonAmount: tlb.VarUInteger16(*big.NewInt(1)), + ForwardPayload: tlb.EitherRef[JettonPayload]{ + IsRight: true, + Value: JettonPayload{ + OpCode: pointer(uint32(0)), + SumType: TextCommentJettonOp, + Value: TextCommentJettonPayload{ + Text: "", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, err := boc.DeserializeBocHex(tt.boc) + assert(t, err) + switch tt.want.(type) { + case JettonTransferMsgBody: + var j JettonTransferMsgBody + err = c[0].Skip(32) + assert(t, err) + err = tlb.Unmarshal(c[0], &j) + assert(t, err) + r1, _ := json.Marshal(j) + r2, _ := json.Marshal(tt.want) + if !bytes.Equal(r1, r2) { + t.Errorf("\nMsg = %v\n want %v", string(r1), string(r2)) + } + case JettonNotifyMsgBody: + var j JettonNotifyMsgBody + err = c[0].Skip(32) + assert(t, err) + err = tlb.Unmarshal(c[0], &j) + assert(t, err) + r1, _ := json.Marshal(j) + r2, _ := json.Marshal(tt.want) + if !bytes.Equal(r1, r2) { + t.Errorf("\nMsg = %v\n want %v", string(r1), string(r2)) + } + case JettonInternalTransferMsgBody: + var j JettonInternalTransferMsgBody + err = c[0].Skip(32) + assert(t, err) + err = tlb.Unmarshal(c[0], &j) + assert(t, err) + r1, _ := json.Marshal(j) + r2, _ := json.Marshal(tt.want) + if !bytes.Equal(r1, r2) { + t.Errorf("\nMsg = %v\n want %v", string(r1), string(r2)) + } + } + }) + } +} diff --git a/abi/messages_generated.go b/abi/messages_generated.go index 921d247..796984f 100644 --- a/abi/messages_generated.go +++ b/abi/messages_generated.go @@ -1432,6 +1432,7 @@ type JettonInternalTransferMsgBody struct { From tlb.MsgAddress ResponseAddress tlb.MsgAddress ForwardTonAmount tlb.VarUInteger16 + ForwardPayload tlb.EitherRef[JettonPayload] } type WhalesNominatorsWithdrawUnownedResponseMsgBody struct { diff --git a/abi/schemas/jettons.xml b/abi/schemas/jettons.xml index ebf2b06..bf92076 100644 --- a/abi/schemas/jettons.xml +++ b/abi/schemas/jettons.xml @@ -98,7 +98,7 @@ internal_transfer#178d4519 query_id:uint64 amount:(VarUInteger 16) from:MsgAddress response_address:MsgAddress forward_ton_amount:(VarUInteger 16) - + forward_payload:(Either JettonPayload ^JettonPayload) = InternalMsgBody;