Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Highload v3 support #294

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading