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

Support for the Finalized W5 (V5R1) Contract #210

Merged
merged 5 commits into from
Jul 18, 2024
Merged
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
38 changes: 30 additions & 8 deletions ton/wallet/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin
switch ver {
case HighloadV3:
return nil, fmt.Errorf("use ConfigHighloadV3 for highload v3 spec")
case V5Beta:
return nil, fmt.Errorf("use ConfigV5Beta for V5 spec")
case V5R1:
return nil, fmt.Errorf("use ConfigV5R1 for v5 spec")
return nil, fmt.Errorf("use ConfigV5R1 for V5 spec")
}
case ConfigHighloadV3:
ver = HighloadV3
case ConfigV5Beta:
ver = V5Beta
case ConfigV5R1:
ver = V5R1
}
Expand All @@ -84,18 +88,36 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin
MustStoreSlice(pubKey, 256).
MustStoreDict(nil). // empty dict of plugins
EndCell()
case V5R1:
config := version.(ConfigV5R1)
case V5Beta:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets keep name V5R1, and instead of Final call it V5R2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The official name will be V5R1, and the previous W5 is V5 Beta. It is worth keeping V5R1 to avoid confusing new users of the library and to communicate that the previous version of V5 will be V5 Beta. A smaller number of users used the Beta contract, who will use the library in the future.

config := version.(ConfigV5Beta)

data = cell.BeginCell().
MustStoreUInt(0, 33). // seqno
MustStoreInt(int64(config.NetworkGlobalID), 32).
MustStoreInt(int64(config.Workchain), 8).
MustStoreUInt(0, 8). // version of v5
MustStoreUInt(uint64(subWallet), 32).
MustStoreUInt(0, 33). // seqno
MustStoreInt(int64(config.NetworkGlobalID), 32). // network id
MustStoreInt(int64(config.Workchain), 8). // workchain
MustStoreUInt(0, 8). // version of v5
MustStoreUInt(uint64(subWallet), 32). // default 0
MustStoreSlice(pubKey, 256).
MustStoreDict(nil). // empty dict of plugins
EndCell()
case V5R1:
config := version.(ConfigV5R1)

// Create WalletId instance
walletId := WalletId{
NetworkGlobalID: config.NetworkGlobalID, // -3 Testnet, -239 Mainnet
WorkChain: config.Workchain,
SubwalletNumber: uint16(subWallet),
WalletVersion: 0, // Wallet Version
}

data = cell.BeginCell().
MustStoreBoolBit(true). // storeUint(1, 1) - boolean flag for context type
MustStoreUInt(0, 32). // Sequence number, hardcoded as 0
MustStoreUInt(uint64(walletId.Serialized()), 32). // Serializing WalletId into 32-bit integer
MustStoreSlice(pubKey, 256). // Storing the public key
MustStoreDict(nil). // Storing an empty plugins dictionary
EndCell()
case HighloadV2R2, HighloadV2Verified:
data = cell.BeginCell().
MustStoreUInt(uint64(subWallet), 32).
Expand Down
90 changes: 90 additions & 0 deletions ton/wallet/v5beta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package wallet

import (
"context"
"errors"
"fmt"
"time"

"github.com/xssnick/tonutils-go/tlb"
"github.com/xssnick/tonutils-go/ton"

"github.com/xssnick/tonutils-go/tvm/cell"
)

// https://github.com/tonkeeper/tonkeeper-ton/commit/e8a7f3415e241daf4ac723f273fbc12776663c49#diff-c20d462b2e1ec616bbba2db39acc7a6c61edc3d5e768f5c2034a80169b1a56caR29
const _V5BetaCodeHex = "b5ee9c7241010101002300084202e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f34120dcd8e11"

type ConfigV5Beta struct {
NetworkGlobalID int32
Workchain int8
}

type SpecV5Beta struct {
SpecRegular
SpecSeqno

config ConfigV5Beta
}

func (s *SpecV5Beta) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) {
// TODO: remove block, now it is here for backwards compatibility

if len(messages) > 255 {
return nil, errors.New("for this type of wallet max 4 messages can be sent in the same time")
}

seq, err := s.seqnoFetcher(ctx, s.wallet.subwallet)
if err != nil {
return nil, fmt.Errorf("failed to fetch seqno: %w", err)
}

actions, err := packV5BetaActions(messages)
if err != nil {
return nil, fmt.Errorf("failed to build actions: %w", err)
}

payload := cell.BeginCell().
MustStoreUInt(0x7369676e, 32). // external sign op code
MustStoreInt(int64(s.config.NetworkGlobalID), 32).
MustStoreInt(int64(s.config.Workchain), 8).
MustStoreUInt(0, 8). // version of v5
MustStoreUInt(uint64(s.wallet.subwallet), 32).
MustStoreUInt(uint64(timeNow().Add(time.Duration(s.messagesTTL)*time.Second).UTC().Unix()), 32).
MustStoreUInt(uint64(seq), 32).
MustStoreBuilder(actions)

sign := payload.EndCell().Sign(s.wallet.key)
msg := cell.BeginCell().MustStoreBuilder(payload).MustStoreSlice(sign, 512).EndCell()

return msg, nil
}

func packV5BetaActions(messages []*Message) (*cell.Builder, error) {
if len(messages) > 255 {
return nil, fmt.Errorf("max 255 messages allowed for v5")
}

var list = cell.BeginCell().EndCell()
for _, message := range messages {
outMsg, err := tlb.ToCell(message.InternalMessage)
if err != nil {
return nil, err
}

/*
out_list_empty$_ = OutList 0;
out_list$_ {n:#} prev:^(OutList n) action:OutAction
= OutList (n + 1);
action_send_msg#0ec3c86d mode:(## 8)
out_msg:^(MessageRelaxed Any) = OutAction;
*/
msg := cell.BeginCell().MustStoreUInt(0x0ec3c86d, 32).
MustStoreUInt(uint64(message.Mode), 8).
MustStoreRef(outMsg)

list = cell.BeginCell().MustStoreRef(list).MustStoreBuilder(msg).EndCell()
}

return cell.BeginCell().MustStoreUInt(0, 1).MustStoreRef(list), nil
}
117 changes: 87 additions & 30 deletions ton/wallet/v5r1.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import (
"context"
"errors"
"fmt"
"time"

"github.com/xssnick/tonutils-go/tlb"
"github.com/xssnick/tonutils-go/ton"
"time"

"github.com/xssnick/tonutils-go/tvm/cell"
)

// https://github.com/tonkeeper/tonkeeper-ton/commit/e8a7f3415e241daf4ac723f273fbc12776663c49#diff-c20d462b2e1ec616bbba2db39acc7a6c61edc3d5e768f5c2034a80169b1a56caR29
const _V5R1CodeHex = "b5ee9c7241010101002300084202e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f34120dcd8e11"
// Contract source:
// https://github.com/ton-blockchain/wallet-contract-v5/blob/main/build/wallet_v5.compiled.json
const _V5R1CodeHex = "b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e"

type ConfigV5R1 struct {
NetworkGlobalID int32
Expand All @@ -26,45 +28,106 @@ type SpecV5R1 struct {
config ConfigV5R1
}

const MainnetGlobalID = -239
const TestnetGlobalID = -3
// Source: https://github.com/tonkeeper/tonkeeper-ton/commit/d9aec6adfdb853eb37e0bba7453d83ae52e2a170#diff-c8ee60dec2f4e3ee55ad5e40f56fd9a104f21df78086a114d33d62e4fa0ffee6R139
/*
* schema:
* wallet_id -- int32
* wallet_id = global_id ^ context_id
* context_id_client$1 = wc:int8 wallet_version:uint8 counter:uint15
* context_id_backoffice$0 = counter:uint31
*
*
* calculated default values serialisation:
*
* global_id = -239, workchain = 0, wallet_version = 0', subwallet_number = 0 (client context)
* gives wallet_id = 2147483409
*
* global_id = -239, workchain = -1, wallet_version = 0', subwallet_number = 0 (client context)
* gives wallet_id = 8388369
*
* global_id = -3, workchain = 0, wallet_version = 0', subwallet_number = 0 (client context)
* gives wallet_id = 2147483645
*
* global_id = -3, workchain = -1, wallet_version = 0', subwallet_number = 0 (client context)
* gives wallet_id = 8388605
*/
// Function to generate the context ID based on the given workchain
func genContextID(workchain int8) uint32 {
var context uint32

// Convert workchain to uint32 after ensuring it's correctly handled as an 8-bit value
context |= 1 << 31 // Write 1 bit as 1 at the leftmost bit
context |= (uint32(workchain) & 0xFF) << 23 // Write 8 bits of workchain, shifted to position
context |= uint32(0) << 15 // Write 8 bits of 0 (wallet version)
context |= uint32(0) // Write 15 bits of 0 (subwallet number)

return context
}

func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) {
// TODO: remove block, now it is here for backwards compatibility
type WalletId struct {
NetworkGlobalID int32
WorkChain int8
SubwalletNumber uint16
WalletVersion uint8
}

func (w WalletId) Serialized() uint32 {
context := genContextID(w.WorkChain)
return uint32(int32(context) ^ w.NetworkGlobalID)
}

func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) {
if len(messages) > 255 {
return nil, errors.New("for this type of wallet max 4 messages can be sent in the same time")
return nil, errors.New("for this type of wallet max 255 messages can be sent at the same time")
}

seq, err := s.seqnoFetcher(ctx, s.wallet.subwallet)
if err != nil {
return nil, fmt.Errorf("failed to fetch seqno: %w", err)
}

actions, err := packV5Actions(messages)
actions, err := packV5R1Actions(messages)
if err != nil {
return nil, fmt.Errorf("failed to build actions: %w", err)
}

walletId := WalletId{
NetworkGlobalID: s.config.NetworkGlobalID,
WorkChain: s.config.Workchain,
SubwalletNumber: uint16(s.wallet.subwallet),
WalletVersion: 0,
}

payload := cell.BeginCell().
MustStoreUInt(0x7369676e, 32). // external sign op code
MustStoreInt(int64(s.config.NetworkGlobalID), 32).
MustStoreInt(int64(s.config.Workchain), 8).
MustStoreUInt(0, 8). // version of v5
MustStoreUInt(uint64(s.wallet.subwallet), 32).
MustStoreUInt(uint64(timeNow().Add(time.Duration(s.messagesTTL)*time.Second).UTC().Unix()), 32).
MustStoreUInt(uint64(seq), 32).
MustStoreBuilder(actions)
MustStoreUInt(0x7369676e, 32). // external sign op code
MustStoreUInt(uint64(walletId.Serialized()), 32). // serialized WalletId
MustStoreUInt(uint64(time.Now().Add(time.Duration(s.messagesTTL)*time.Second).UTC().Unix()), 32). // validUntil
MustStoreUInt(uint64(seq), 32). // seq (block)
MustStoreBuilder(actions) // Action list

sign := payload.EndCell().Sign(s.wallet.key)
msg := cell.BeginCell().MustStoreBuilder(payload).MustStoreSlice(sign, 512).EndCell()

return msg, nil
}

func packV5Actions(messages []*Message) (*cell.Builder, error) {
// Validate messages
func validateMessageFields(messages []*Message) error {
if len(messages) > 255 {
return nil, fmt.Errorf("max 255 messages allowed for v5")
return fmt.Errorf("max 255 messages allowed for v5")
}
for _, message := range messages {
if message.InternalMessage == nil {
return fmt.Errorf("internal message cannot be nil")
}
}
return nil
}

// Pack Actions
func packV5R1Actions(messages []*Message) (*cell.Builder, error) {
if err := validateMessageFields(messages); err != nil {
return nil, err
}

var list = cell.BeginCell().EndCell()
Expand All @@ -74,19 +137,13 @@ func packV5Actions(messages []*Message) (*cell.Builder, error) {
return nil, err
}

/*
out_list_empty$_ = OutList 0;
out_list$_ {n:#} prev:^(OutList n) action:OutAction
= OutList (n + 1);
action_send_msg#0ec3c86d mode:(## 8)
out_msg:^(MessageRelaxed Any) = OutAction;
*/
msg := cell.BeginCell().MustStoreUInt(0x0ec3c86d, 32).
MustStoreUInt(uint64(message.Mode), 8).
MustStoreRef(outMsg)
msg := cell.BeginCell().MustStoreUInt(0x0ec3c86d, 32). // action_send_msg prefix
MustStoreUInt(uint64(message.Mode), 8). // mode
MustStoreRef(outMsg) // message reference

list = cell.BeginCell().MustStoreRef(list).MustStoreBuilder(msg).EndCell()
}

return cell.BeginCell().MustStoreUInt(0, 1).MustStoreRef(list), nil
// Ensure the action list ends with 0, 1 as per the new specification
return cell.BeginCell().MustStoreUInt(1, 1).MustStoreRef(list).MustStoreUInt(0, 1), nil
}
Loading
Loading