Skip to content

Commit eda3457

Browse files
author
Alexey Kostenko
committed
highload wallet V3
1 parent b4682cd commit eda3457

File tree

10 files changed

+641
-1
lines changed

10 files changed

+641
-1
lines changed

abi/generated_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,6 +1876,27 @@ func TestMessageDecoder(t *testing.T) {
18761876
},
18771877
interfaces: []ContractInterface{WalletV5R1},
18781878
},
1879+
{
1880+
name: "highload V3 internal transfer",
1881+
boc: "te6ccgEBBQEAWQABGK5C5aQAAAAAADVe6gECCg7DyG0DAgQCCg7DyG0DAwQAAABoQgA2ZpktQsYby0n9cV5VWOFINBjScIU2HdondFsK3lDpECAvrwgAAAAAAAAAAAAAAAAAAA==",
1882+
wantOpName: HighloadWalletInternalTransferMsgOp,
1883+
wantValue: HighloadWalletInternalTransferMsgBody{
1884+
QueryId: 3497706,
1885+
Actions: W5Actions{
1886+
W5SendMessageAction{
1887+
Magic: 0xec3c86d,
1888+
Mode: 3,
1889+
Msg: mustBocToMessageRelaxed("b5ee9c7201010101003600006842003666992d42c61bcb49fd715e5558e1483418d27085361dda27745b0ade50e910202faf080000000000000000000000000000"),
1890+
},
1891+
W5SendMessageAction{
1892+
Magic: 0xec3c86d,
1893+
Mode: 3,
1894+
Msg: mustBocToMessageRelaxed("b5ee9c7201010101003600006842003666992d42c61bcb49fd715e5558e1483418d27085361dda27745b0ade50e910202faf080000000000000000000000000000"),
1895+
},
1896+
},
1897+
},
1898+
interfaces: []ContractInterface{WalletHighloadV3R1},
1899+
},
18791900
}
18801901
for _, tt := range tests {
18811902
t.Run(tt.name, func(t *testing.T) {

abi/interfaces.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,10 @@ func (c ContractInterface) IntMsgs() []msgDecoderFunc {
12991299
decodeFuncStormVaultInitMsgBody,
13001300
decodeFuncStormVaultRequestWithdrawPositionMsgBody,
13011301
}
1302+
case WalletHighloadV3R1:
1303+
return []msgDecoderFunc{
1304+
decodeFuncHighloadWalletInternalTransferMsgBody,
1305+
}
13021306
case WalletV5R1:
13031307
return []msgDecoderFunc{
13041308
decodeFuncWalletSignedInternalV5R1MsgBody,

abi/messages.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ The list below contains the supported message operations, their names and opcode
5050
| GetStaticData| 0x2fcb26a2 |
5151
| GramSubmitProofOfWork| 0x4d696e65 |
5252
| HighloadWalletSignedV2| 0x00000000 |
53+
| HighloadWalletInternalTransfer| 0xae42e5a4 |
5354
| HighloadWalletSignedV3| 0x00000000 |
5455
| InitPaymentChannel| 0x0e0620c2 |
5556
| JettonBurn| 0x595f07bc |

abi/messages_generated.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ var (
251251
decodeFuncStorageRewardWithdrawalMsgBody = decodeMsg(tlb.Tag{Val: 0xa91baf56, Len: 32}, StorageRewardWithdrawalMsgOp, StorageRewardWithdrawalMsgBody{})
252252
// 0xad4eb6f5
253253
decodeFuncDedustPayoutFromPoolMsgBody = decodeMsg(tlb.Tag{Val: 0xad4eb6f5, Len: 32}, DedustPayoutFromPoolMsgOp, DedustPayoutFromPoolMsgBody{})
254+
// 0xae42e5a4
255+
decodeFuncHighloadWalletInternalTransferMsgBody = decodeMsg(tlb.Tag{Val: 0xae42e5a4, Len: 32}, HighloadWalletInternalTransferMsgOp, HighloadWalletInternalTransferMsgBody{})
254256
// 0xafaf283e
255257
decodeFuncMultisigApproveRejectedMsgBody = decodeMsg(tlb.Tag{Val: 0xafaf283e, Len: 32}, MultisigApproveRejectedMsgOp, MultisigApproveRejectedMsgBody{})
256258
// 0xb1ebae06
@@ -709,6 +711,9 @@ var opcodedMsgInDecodeFunctions = map[uint32]msgDecoderFunc{
709711
// 0xad4eb6f5
710712
DedustPayoutFromPoolMsgOpCode: decodeFuncDedustPayoutFromPoolMsgBody,
711713

714+
// 0xae42e5a4
715+
HighloadWalletInternalTransferMsgOpCode: decodeFuncHighloadWalletInternalTransferMsgBody,
716+
712717
// 0xafaf283e
713718
MultisigApproveRejectedMsgOpCode: decodeFuncMultisigApproveRejectedMsgBody,
714719

@@ -962,6 +967,7 @@ const (
962967
ReportRoyaltyParamsMsgOp MsgOpName = "ReportRoyaltyParams"
963968
StorageRewardWithdrawalMsgOp MsgOpName = "StorageRewardWithdrawal"
964969
DedustPayoutFromPoolMsgOp MsgOpName = "DedustPayoutFromPool"
970+
HighloadWalletInternalTransferMsgOp MsgOpName = "HighloadWalletInternalTransfer"
965971
MultisigApproveRejectedMsgOp MsgOpName = "MultisigApproveRejected"
966972
TonstakeImanagerRequestNotificationMsgOp MsgOpName = "TonstakeImanagerRequestNotification"
967973
TonstakePoolDeployControllerMsgOp MsgOpName = "TonstakePoolDeployController"
@@ -1130,6 +1136,7 @@ const (
11301136
ReportRoyaltyParamsMsgOpCode MsgOpCode = 0xa8cb00ad
11311137
StorageRewardWithdrawalMsgOpCode MsgOpCode = 0xa91baf56
11321138
DedustPayoutFromPoolMsgOpCode MsgOpCode = 0xad4eb6f5
1139+
HighloadWalletInternalTransferMsgOpCode MsgOpCode = 0xae42e5a4
11331140
MultisigApproveRejectedMsgOpCode MsgOpCode = 0xafaf283e
11341141
TonstakeImanagerRequestNotificationMsgOpCode MsgOpCode = 0xb1ebae06
11351142
TonstakePoolDeployControllerMsgOpCode MsgOpCode = 0xb27edcad
@@ -1902,6 +1909,11 @@ type DedustPayoutFromPoolMsgBody struct {
19021909
Payload *tlb.Any `tlb:"maybe^"`
19031910
}
19041911

1912+
type HighloadWalletInternalTransferMsgBody struct {
1913+
QueryId uint64
1914+
Actions W5Actions `tlb:"^"`
1915+
}
1916+
19051917
type MultisigApproveRejectedMsgBody struct {
19061918
QueryId uint64
19071919
ExitCode uint32
@@ -2277,6 +2289,7 @@ var KnownMsgInTypes = map[string]any{
22772289
ReportRoyaltyParamsMsgOp: ReportRoyaltyParamsMsgBody{},
22782290
StorageRewardWithdrawalMsgOp: StorageRewardWithdrawalMsgBody{},
22792291
DedustPayoutFromPoolMsgOp: DedustPayoutFromPoolMsgBody{},
2292+
HighloadWalletInternalTransferMsgOp: HighloadWalletInternalTransferMsgBody{},
22802293
MultisigApproveRejectedMsgOp: MultisigApproveRejectedMsgBody{},
22812294
TonstakeImanagerRequestNotificationMsgOp: TonstakeImanagerRequestNotificationMsgBody{},
22822295
TonstakePoolDeployControllerMsgOp: TonstakePoolDeployControllerMsgBody{},

abi/schemas/wallets.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
<get_method name="get_timeout"/>
137137
<msg_in>
138138
<ext_in name="highload_wallet_signed_v3"/>
139+
<internal name="highload_wallet_internal_transfer"/>
139140
</msg_in>
140141
</interface>
141142

@@ -216,6 +217,9 @@
216217
<internal name="wallet_extension_action_v5r1" >
217218
extension_action#6578746e query_id:uint64 actions:(Maybe ^W5Actions) extended:(Maybe W5ExtendedActions) = InternalMsgBody;
218219
</internal>
220+
<internal name="highload_wallet_internal_transfer" >
221+
internal_transfer#ae42e5a4 query_id:uint64 actions:^W5Actions = InternalMsgBody;
222+
</internal>
219223
<ext_in name="highload_wallet_signed_v2">
220224
signed#_ signature:bits512 subwallet_id:uint32 query_id:uint64 payload:(HashmapE 16 SendMessageAction) = ExternalMsgBody;
221225
</ext_in>

wallet/messages.go

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"crypto/ed25519"
55
"errors"
66
"fmt"
7+
"github.com/tonkeeper/tongo/ton"
78

89
"github.com/tonkeeper/tongo/boc"
910
"github.com/tonkeeper/tongo/tlb"
@@ -245,6 +246,12 @@ func ExtractRawMessages(ver Version, msg *boc.Cell) ([]RawMessage, error) {
245246
return nil, err
246247
}
247248
return hl.RawMessages, nil
249+
case HighLoadV3R1:
250+
hl, err := DecodeHighloadV3Message(msg)
251+
if err != nil {
252+
return nil, err
253+
}
254+
return hl.Messages, nil
248255
default:
249256
return nil, fmt.Errorf("wallet version is not supported: %v", ver)
250257
}
@@ -552,3 +559,208 @@ func (extendedActions W5ExtendedActions) MarshalTLB(c *boc.Cell, encoder *tlb.En
552559
}
553560
return nil
554561
}
562+
563+
// HighloadV3InternalTransfer TLB: internal_transfer#ae42e5a4 {n:#} query_id:uint64 actions:^(OutList n) = InternalMsgBody n;
564+
type HighloadV3InternalTransfer struct {
565+
Magic tlb.Magic `tlb:"#ae42e5a4"`
566+
QueryId uint64
567+
Actions W5Actions `tlb:"^"`
568+
}
569+
570+
type HighloadV3MsgInner struct {
571+
SubwalletID uint32
572+
MessageToSend *boc.Cell `tlb:"^"`
573+
SendMode uint8
574+
QueryID tlb.Uint23 // _ shift:uint13 bit_number:(## 10) { bit_number >= 0 } { bit_number < 1023 } = QueryId;
575+
CreatedAt uint64
576+
Timeout tlb.Uint22
577+
}
578+
579+
type HighloadV3Message struct {
580+
SubwalletID uint32
581+
Messages []RawMessage
582+
SendMode uint8
583+
QueryID tlb.Uint23
584+
CreatedAt uint64
585+
Timeout tlb.Uint22
586+
wallet ton.AccountID // technical field for storing the wallet address for forming attached messages
587+
}
588+
589+
func (p HighloadV3Message) MarshalTLB(c *boc.Cell, encoder *tlb.Encoder) error {
590+
var (
591+
msg RawMessage
592+
err error
593+
)
594+
ln := len(p.Messages)
595+
switch {
596+
case ln > 254*254:
597+
return fmt.Errorf("PayloadHighloadV3 supports only up to 254*254 messages")
598+
case ln < 1:
599+
return fmt.Errorf("must be at least one message")
600+
case ln == 1:
601+
var m tlb.Message
602+
err := tlb.Unmarshal(p.Messages[0].Message, &m)
603+
if err != nil {
604+
return err
605+
}
606+
// IntMsg with state init and extOutMsg must be packed because of message validation
607+
// throw_if(error::invalid_message_to_send, maybe_state_init); ;; throw if state-init included (state-init not supported)
608+
// throw_if(error::invalid_message_to_send, message_slice~load_uint(1)); ;; int_msg_info$0
609+
if !m.Init.Exists && m.Info.SumType == "IntMsgInfo" { // no need to pack
610+
msg = p.Messages[0]
611+
} else {
612+
msg, err = packHighloadV3Messages(uint64(p.QueryID), p.wallet, p.Messages, p.SendMode)
613+
if err != nil {
614+
return err
615+
}
616+
}
617+
default:
618+
msg, err = packHighloadV3Messages(uint64(p.QueryID), p.wallet, p.Messages, p.SendMode)
619+
if err != nil {
620+
return err
621+
}
622+
}
623+
return tlb.Marshal(c, HighloadV3MsgInner{
624+
SubwalletID: p.SubwalletID,
625+
MessageToSend: msg.Message,
626+
SendMode: msg.Mode,
627+
QueryID: p.QueryID,
628+
CreatedAt: p.CreatedAt,
629+
Timeout: p.Timeout,
630+
})
631+
}
632+
633+
func packHighloadV3Messages(queryID uint64, wallet ton.AccountID, msgs []RawMessage, mode uint8) (RawMessage, error) {
634+
const messagesPerPack = 253
635+
var (
636+
totalAmount uint64 = 0
637+
actions W5Actions
638+
)
639+
rawMsgs := make([]RawMessage, len(msgs))
640+
copy(rawMsgs, msgs) // to prevent corruption of msgs
641+
if len(rawMsgs) > messagesPerPack {
642+
rest, err := packHighloadV3Messages(queryID, wallet, rawMsgs[messagesPerPack:], mode)
643+
if err != nil {
644+
return RawMessage{}, err
645+
}
646+
rawMsgs = append(rawMsgs[:messagesPerPack], rest)
647+
}
648+
for _, rawMsg := range rawMsgs {
649+
var m tlb.Message
650+
err := tlb.Unmarshal(rawMsg.Message, &m)
651+
if err != nil {
652+
return RawMessage{}, err
653+
}
654+
if m.Info.SumType == "IntMsgInfo" {
655+
totalAmount += uint64(m.Info.IntMsgInfo.Value.Grams)
656+
} else {
657+
totalAmount += uint64(1_000_000) // add some amount for execution
658+
}
659+
actions = append(actions, W5SendMessageAction{
660+
Mode: rawMsg.Mode,
661+
Msg: rawMsg.Message,
662+
})
663+
}
664+
body := boc.NewCell()
665+
err := tlb.Marshal(body, HighloadV3InternalTransfer{
666+
QueryId: queryID,
667+
Actions: actions,
668+
})
669+
if err != nil {
670+
return RawMessage{}, err
671+
}
672+
msgInt, _, err := Message{
673+
Amount: tlb.Grams(totalAmount),
674+
Bounce: false,
675+
Address: wallet,
676+
Body: body,
677+
}.ToInternal()
678+
if err != nil {
679+
return RawMessage{}, err
680+
}
681+
c := boc.NewCell()
682+
err = tlb.Marshal(c, msgInt)
683+
if err != nil {
684+
return RawMessage{}, err
685+
}
686+
return RawMessage{
687+
Mode: mode,
688+
Message: c,
689+
}, nil
690+
}
691+
692+
const highloadV3InternalTransferOp = 0xae42e5a4
693+
694+
func (p *HighloadV3Message) UnmarshalTLB(c *boc.Cell, decoder *tlb.Decoder) error {
695+
var msgInner HighloadV3MsgInner
696+
err := tlb.Unmarshal(c, &msgInner)
697+
if err != nil {
698+
return err
699+
}
700+
res := HighloadV3Message{
701+
SubwalletID: msgInner.SubwalletID,
702+
SendMode: msgInner.SendMode,
703+
QueryID: msgInner.QueryID,
704+
CreatedAt: msgInner.CreatedAt,
705+
Timeout: msgInner.Timeout,
706+
}
707+
var msgs []RawMessage
708+
msgs, err = unpackHighloadV3Messages(msgInner.MessageToSend, msgInner.QueryID, msgInner.SendMode, msgs)
709+
if err != nil {
710+
return err
711+
}
712+
res.Messages = msgs
713+
*p = res
714+
return nil
715+
}
716+
717+
func unpackHighloadV3Messages(msg *boc.Cell, queryID tlb.Uint23, mode uint8, messages []RawMessage) ([]RawMessage, error) {
718+
var m tlb.Message
719+
err := tlb.Unmarshal(msg, &m)
720+
if err != nil {
721+
return nil, err
722+
}
723+
if m.Info.SumType != "IntMsgInfo" {
724+
// TODO: reset counters for msgInner.MessageToSend ?
725+
messages = append(messages, RawMessage{msg, mode})
726+
return messages, nil
727+
}
728+
body := boc.Cell(m.Body.Value)
729+
op, err := body.PickUint(32)
730+
if err != nil || op != highloadV3InternalTransferOp {
731+
messages = append(messages, RawMessage{msg, mode})
732+
return messages, nil
733+
}
734+
var intTransfer HighloadV3InternalTransfer
735+
err = tlb.Unmarshal(&body, &intTransfer)
736+
if err != nil {
737+
return nil, err
738+
}
739+
if intTransfer.QueryId != uint64(queryID) {
740+
return nil, errors.New("mismatch queryID for internal transfer") // TODO: need to check?
741+
}
742+
for _, a := range intTransfer.Actions {
743+
messages, err = unpackHighloadV3Messages(a.Msg, queryID, a.Mode, messages)
744+
if err != nil {
745+
return nil, err
746+
}
747+
}
748+
return messages, nil
749+
}
750+
751+
func DecodeHighloadV3Message(msg *boc.Cell) (*HighloadV3Message, error) {
752+
var m tlb.Message
753+
if err := tlb.Unmarshal(msg, &m); err != nil {
754+
return nil, err
755+
}
756+
c := boc.Cell(m.Body.Value)
757+
payloadCell, err := c.NextRef()
758+
if err != nil {
759+
return nil, err
760+
}
761+
var res HighloadV3Message
762+
if err := tlb.Unmarshal(payloadCell, &res); err != nil {
763+
return nil, err
764+
}
765+
return &res, nil
766+
}

0 commit comments

Comments
 (0)