Skip to content

Commit

Permalink
highload wallet V3
Browse files Browse the repository at this point in the history
  • Loading branch information
akos-tk committed Sep 27, 2024
1 parent b4682cd commit eda3457
Show file tree
Hide file tree
Showing 10 changed files with 641 additions and 1 deletion.
21 changes: 21 additions & 0 deletions abi/generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1876,6 +1876,27 @@ func TestMessageDecoder(t *testing.T) {
},
interfaces: []ContractInterface{WalletV5R1},
},
{
name: "highload V3 internal transfer",
boc: "te6ccgEBBQEAWQABGK5C5aQAAAAAADVe6gECCg7DyG0DAgQCCg7DyG0DAwQAAABoQgA2ZpktQsYby0n9cV5VWOFINBjScIU2HdondFsK3lDpECAvrwgAAAAAAAAAAAAAAAAAAA==",
wantOpName: HighloadWalletInternalTransferMsgOp,
wantValue: HighloadWalletInternalTransferMsgBody{
QueryId: 3497706,
Actions: W5Actions{
W5SendMessageAction{
Magic: 0xec3c86d,
Mode: 3,
Msg: mustBocToMessageRelaxed("b5ee9c7201010101003600006842003666992d42c61bcb49fd715e5558e1483418d27085361dda27745b0ade50e910202faf080000000000000000000000000000"),
},
W5SendMessageAction{
Magic: 0xec3c86d,
Mode: 3,
Msg: mustBocToMessageRelaxed("b5ee9c7201010101003600006842003666992d42c61bcb49fd715e5558e1483418d27085361dda27745b0ade50e910202faf080000000000000000000000000000"),
},
},
},
interfaces: []ContractInterface{WalletHighloadV3R1},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions abi/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,10 @@ func (c ContractInterface) IntMsgs() []msgDecoderFunc {
decodeFuncStormVaultInitMsgBody,
decodeFuncStormVaultRequestWithdrawPositionMsgBody,
}
case WalletHighloadV3R1:
return []msgDecoderFunc{
decodeFuncHighloadWalletInternalTransferMsgBody,
}
case WalletV5R1:
return []msgDecoderFunc{
decodeFuncWalletSignedInternalV5R1MsgBody,
Expand Down
1 change: 1 addition & 0 deletions abi/messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The list below contains the supported message operations, their names and opcode
| GetStaticData| 0x2fcb26a2 |
| GramSubmitProofOfWork| 0x4d696e65 |
| HighloadWalletSignedV2| 0x00000000 |
| HighloadWalletInternalTransfer| 0xae42e5a4 |
| HighloadWalletSignedV3| 0x00000000 |
| InitPaymentChannel| 0x0e0620c2 |
| JettonBurn| 0x595f07bc |
Expand Down
13 changes: 13 additions & 0 deletions abi/messages_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ var (
decodeFuncStorageRewardWithdrawalMsgBody = decodeMsg(tlb.Tag{Val: 0xa91baf56, Len: 32}, StorageRewardWithdrawalMsgOp, StorageRewardWithdrawalMsgBody{})
// 0xad4eb6f5
decodeFuncDedustPayoutFromPoolMsgBody = decodeMsg(tlb.Tag{Val: 0xad4eb6f5, Len: 32}, DedustPayoutFromPoolMsgOp, DedustPayoutFromPoolMsgBody{})
// 0xae42e5a4
decodeFuncHighloadWalletInternalTransferMsgBody = decodeMsg(tlb.Tag{Val: 0xae42e5a4, Len: 32}, HighloadWalletInternalTransferMsgOp, HighloadWalletInternalTransferMsgBody{})
// 0xafaf283e
decodeFuncMultisigApproveRejectedMsgBody = decodeMsg(tlb.Tag{Val: 0xafaf283e, Len: 32}, MultisigApproveRejectedMsgOp, MultisigApproveRejectedMsgBody{})
// 0xb1ebae06
Expand Down Expand Up @@ -709,6 +711,9 @@ var opcodedMsgInDecodeFunctions = map[uint32]msgDecoderFunc{
// 0xad4eb6f5
DedustPayoutFromPoolMsgOpCode: decodeFuncDedustPayoutFromPoolMsgBody,

// 0xae42e5a4
HighloadWalletInternalTransferMsgOpCode: decodeFuncHighloadWalletInternalTransferMsgBody,

// 0xafaf283e
MultisigApproveRejectedMsgOpCode: decodeFuncMultisigApproveRejectedMsgBody,

Expand Down Expand Up @@ -962,6 +967,7 @@ const (
ReportRoyaltyParamsMsgOp MsgOpName = "ReportRoyaltyParams"
StorageRewardWithdrawalMsgOp MsgOpName = "StorageRewardWithdrawal"
DedustPayoutFromPoolMsgOp MsgOpName = "DedustPayoutFromPool"
HighloadWalletInternalTransferMsgOp MsgOpName = "HighloadWalletInternalTransfer"
MultisigApproveRejectedMsgOp MsgOpName = "MultisigApproveRejected"
TonstakeImanagerRequestNotificationMsgOp MsgOpName = "TonstakeImanagerRequestNotification"
TonstakePoolDeployControllerMsgOp MsgOpName = "TonstakePoolDeployController"
Expand Down Expand Up @@ -1130,6 +1136,7 @@ const (
ReportRoyaltyParamsMsgOpCode MsgOpCode = 0xa8cb00ad
StorageRewardWithdrawalMsgOpCode MsgOpCode = 0xa91baf56
DedustPayoutFromPoolMsgOpCode MsgOpCode = 0xad4eb6f5
HighloadWalletInternalTransferMsgOpCode MsgOpCode = 0xae42e5a4
MultisigApproveRejectedMsgOpCode MsgOpCode = 0xafaf283e
TonstakeImanagerRequestNotificationMsgOpCode MsgOpCode = 0xb1ebae06
TonstakePoolDeployControllerMsgOpCode MsgOpCode = 0xb27edcad
Expand Down Expand Up @@ -1902,6 +1909,11 @@ type DedustPayoutFromPoolMsgBody struct {
Payload *tlb.Any `tlb:"maybe^"`
}

type HighloadWalletInternalTransferMsgBody struct {
QueryId uint64
Actions W5Actions `tlb:"^"`
}

type MultisigApproveRejectedMsgBody struct {
QueryId uint64
ExitCode uint32
Expand Down Expand Up @@ -2277,6 +2289,7 @@ var KnownMsgInTypes = map[string]any{
ReportRoyaltyParamsMsgOp: ReportRoyaltyParamsMsgBody{},
StorageRewardWithdrawalMsgOp: StorageRewardWithdrawalMsgBody{},
DedustPayoutFromPoolMsgOp: DedustPayoutFromPoolMsgBody{},
HighloadWalletInternalTransferMsgOp: HighloadWalletInternalTransferMsgBody{},
MultisigApproveRejectedMsgOp: MultisigApproveRejectedMsgBody{},
TonstakeImanagerRequestNotificationMsgOp: TonstakeImanagerRequestNotificationMsgBody{},
TonstakePoolDeployControllerMsgOp: TonstakePoolDeployControllerMsgBody{},
Expand Down
4 changes: 4 additions & 0 deletions abi/schemas/wallets.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
<get_method name="get_timeout"/>
<msg_in>
<ext_in name="highload_wallet_signed_v3"/>
<internal name="highload_wallet_internal_transfer"/>
</msg_in>
</interface>

Expand Down Expand Up @@ -216,6 +217,9 @@
<internal name="wallet_extension_action_v5r1" >
extension_action#6578746e query_id:uint64 actions:(Maybe ^W5Actions) extended:(Maybe W5ExtendedActions) = InternalMsgBody;
</internal>
<internal name="highload_wallet_internal_transfer" >
internal_transfer#ae42e5a4 query_id:uint64 actions:^W5Actions = InternalMsgBody;
</internal>
<ext_in name="highload_wallet_signed_v2">
signed#_ signature:bits512 subwallet_id:uint32 query_id:uint64 payload:(HashmapE 16 SendMessageAction) = ExternalMsgBody;
</ext_in>
Expand Down
212 changes: 212 additions & 0 deletions wallet/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/ed25519"
"errors"
"fmt"
"github.com/tonkeeper/tongo/ton"

"github.com/tonkeeper/tongo/boc"
"github.com/tonkeeper/tongo/tlb"
Expand Down Expand Up @@ -245,6 +246,12 @@ func ExtractRawMessages(ver Version, msg *boc.Cell) ([]RawMessage, error) {
return nil, err
}
return hl.RawMessages, nil
case HighLoadV3R1:
hl, err := DecodeHighloadV3Message(msg)
if err != nil {
return nil, err
}
return hl.Messages, nil
default:
return nil, fmt.Errorf("wallet version is not supported: %v", ver)
}
Expand Down Expand Up @@ -552,3 +559,208 @@ func (extendedActions W5ExtendedActions) MarshalTLB(c *boc.Cell, encoder *tlb.En
}
return nil
}

// HighloadV3InternalTransfer TLB: internal_transfer#ae42e5a4 {n:#} query_id:uint64 actions:^(OutList n) = InternalMsgBody n;
type HighloadV3InternalTransfer struct {
Magic tlb.Magic `tlb:"#ae42e5a4"`
QueryId uint64
Actions W5Actions `tlb:"^"`
}

type HighloadV3MsgInner struct {
SubwalletID uint32
MessageToSend *boc.Cell `tlb:"^"`
SendMode uint8
QueryID tlb.Uint23 // _ shift:uint13 bit_number:(## 10) { bit_number >= 0 } { bit_number < 1023 } = QueryId;
CreatedAt uint64
Timeout tlb.Uint22
}

type HighloadV3Message struct {
SubwalletID uint32
Messages []RawMessage
SendMode uint8
QueryID tlb.Uint23
CreatedAt uint64
Timeout tlb.Uint22
wallet ton.AccountID // technical field for storing the wallet address for forming attached messages
}

func (p HighloadV3Message) MarshalTLB(c *boc.Cell, encoder *tlb.Encoder) error {
var (
msg RawMessage
err error
)
ln := len(p.Messages)
switch {
case ln > 254*254:
return fmt.Errorf("PayloadHighloadV3 supports only up to 254*254 messages")
case ln < 1:
return fmt.Errorf("must be at least one message")
case ln == 1:
var m tlb.Message
err := tlb.Unmarshal(p.Messages[0].Message, &m)
if err != nil {
return err
}
// IntMsg with state init and extOutMsg must be packed because of message validation
// throw_if(error::invalid_message_to_send, maybe_state_init); ;; throw if state-init included (state-init not supported)
// throw_if(error::invalid_message_to_send, message_slice~load_uint(1)); ;; int_msg_info$0
if !m.Init.Exists && m.Info.SumType == "IntMsgInfo" { // no need to pack
msg = p.Messages[0]
} else {
msg, err = packHighloadV3Messages(uint64(p.QueryID), p.wallet, p.Messages, p.SendMode)
if err != nil {
return err
}
}
default:
msg, err = packHighloadV3Messages(uint64(p.QueryID), p.wallet, p.Messages, p.SendMode)
if err != nil {
return err
}
}
return tlb.Marshal(c, HighloadV3MsgInner{
SubwalletID: p.SubwalletID,
MessageToSend: msg.Message,
SendMode: msg.Mode,
QueryID: p.QueryID,
CreatedAt: p.CreatedAt,
Timeout: p.Timeout,
})
}

func packHighloadV3Messages(queryID uint64, wallet ton.AccountID, msgs []RawMessage, mode uint8) (RawMessage, error) {
const messagesPerPack = 253
var (
totalAmount uint64 = 0
actions W5Actions
)
rawMsgs := make([]RawMessage, len(msgs))
copy(rawMsgs, msgs) // to prevent corruption of msgs
if len(rawMsgs) > messagesPerPack {
rest, err := packHighloadV3Messages(queryID, wallet, rawMsgs[messagesPerPack:], mode)
if err != nil {
return RawMessage{}, err
}
rawMsgs = append(rawMsgs[:messagesPerPack], rest)
}
for _, rawMsg := range rawMsgs {
var m tlb.Message
err := tlb.Unmarshal(rawMsg.Message, &m)
if err != nil {
return RawMessage{}, err
}
if m.Info.SumType == "IntMsgInfo" {
totalAmount += uint64(m.Info.IntMsgInfo.Value.Grams)
} else {
totalAmount += uint64(1_000_000) // add some amount for execution
}
actions = append(actions, W5SendMessageAction{
Mode: rawMsg.Mode,
Msg: rawMsg.Message,
})
}
body := boc.NewCell()
err := tlb.Marshal(body, HighloadV3InternalTransfer{
QueryId: queryID,
Actions: actions,
})
if err != nil {
return RawMessage{}, err
}
msgInt, _, err := Message{
Amount: tlb.Grams(totalAmount),
Bounce: false,
Address: wallet,
Body: body,
}.ToInternal()
if err != nil {
return RawMessage{}, err
}
c := boc.NewCell()
err = tlb.Marshal(c, msgInt)
if err != nil {
return RawMessage{}, err
}
return RawMessage{
Mode: mode,
Message: c,
}, nil
}

const highloadV3InternalTransferOp = 0xae42e5a4

func (p *HighloadV3Message) UnmarshalTLB(c *boc.Cell, decoder *tlb.Decoder) error {
var msgInner HighloadV3MsgInner
err := tlb.Unmarshal(c, &msgInner)
if err != nil {
return err
}
res := HighloadV3Message{
SubwalletID: msgInner.SubwalletID,
SendMode: msgInner.SendMode,
QueryID: msgInner.QueryID,
CreatedAt: msgInner.CreatedAt,
Timeout: msgInner.Timeout,
}
var msgs []RawMessage
msgs, err = unpackHighloadV3Messages(msgInner.MessageToSend, msgInner.QueryID, msgInner.SendMode, msgs)
if err != nil {
return err
}
res.Messages = msgs
*p = res
return nil
}

func unpackHighloadV3Messages(msg *boc.Cell, queryID tlb.Uint23, mode uint8, messages []RawMessage) ([]RawMessage, error) {
var m tlb.Message
err := tlb.Unmarshal(msg, &m)
if err != nil {
return nil, err
}
if m.Info.SumType != "IntMsgInfo" {
// TODO: reset counters for msgInner.MessageToSend ?
messages = append(messages, RawMessage{msg, mode})
return messages, nil
}
body := boc.Cell(m.Body.Value)
op, err := body.PickUint(32)
if err != nil || op != highloadV3InternalTransferOp {
messages = append(messages, RawMessage{msg, mode})
return messages, nil
}
var intTransfer HighloadV3InternalTransfer
err = tlb.Unmarshal(&body, &intTransfer)
if err != nil {
return nil, err
}
if intTransfer.QueryId != uint64(queryID) {
return nil, errors.New("mismatch queryID for internal transfer") // TODO: need to check?
}
for _, a := range intTransfer.Actions {
messages, err = unpackHighloadV3Messages(a.Msg, queryID, a.Mode, messages)
if err != nil {
return nil, err
}
}
return messages, nil
}

func DecodeHighloadV3Message(msg *boc.Cell) (*HighloadV3Message, error) {
var m tlb.Message
if err := tlb.Unmarshal(msg, &m); err != nil {
return nil, err
}
c := boc.Cell(m.Body.Value)
payloadCell, err := c.NextRef()
if err != nil {
return nil, err
}
var res HighloadV3Message
if err := tlb.Unmarshal(payloadCell, &res); err != nil {
return nil, err
}
return &res, nil
}
Loading

0 comments on commit eda3457

Please sign in to comment.