From fcf2d4dd49a8e48cc364bae8aaf5e7c9f5067efb Mon Sep 17 00:00:00 2001 From: "aleksej.paschenko" Date: Fri, 13 Sep 2024 12:59:43 +0300 Subject: [PATCH] Get state init from GetWalletStateInitAndSalt --- go.mod | 3 +- go.sum | 6 ++- pkg/api/handler.go | 111 ++++++++++++++++++++++++++-------------- pkg/api/handler_test.go | 47 +++++++++++++++++ 4 files changed, 127 insertions(+), 40 deletions(-) create mode 100644 pkg/api/handler_test.go diff --git a/go.mod b/go.mod index 321292c..5d92173 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.0 require ( github.com/Code-Hex/go-generics-cache v1.5.1 + github.com/avast/retry-go v3.0.0+incompatible github.com/caarlos0/env/v6 v6.10.1 github.com/go-faster/errors v0.7.1 github.com/go-faster/jx v1.1.0 @@ -11,7 +12,7 @@ require ( github.com/prometheus/client_golang v1.19.1 github.com/rs/cors v1.11.0 github.com/stretchr/testify v1.9.0 - github.com/tonkeeper/tongo v1.9.2-0.20240813090128-215bb67f8160 + github.com/tonkeeper/tongo v1.9.6-0.20240913095748-e4fe80db484b go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/metric v1.28.0 go.opentelemetry.io/otel/trace v1.28.0 diff --git a/go.sum b/go.sum index 07f7754..03dfe6e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU= github.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= @@ -62,8 +64,8 @@ github.com/snksoft/crc v1.1.0 h1:HkLdI4taFlgGGG1KvsWMpz78PkOC9TkPVpTV/cuWn48= github.com/snksoft/crc v1.1.0/go.mod h1:5/gUOsgAm7OmIhb6WJzw7w5g2zfJi4FrHYgGPdshE+A= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tonkeeper/tongo v1.9.2-0.20240813090128-215bb67f8160 h1:QW00z3p7ALYKq++3cXrepKEQhPXtS0QcunMTLd60p9w= -github.com/tonkeeper/tongo v1.9.2-0.20240813090128-215bb67f8160/go.mod h1:MjgIgAytFarjCoVjMLjYEtpZNN1f2G/pnZhKjr28cWs= +github.com/tonkeeper/tongo v1.9.6-0.20240913095748-e4fe80db484b h1:5w+JddBpt3Eb6Eam4mwjdR4eC4A3ZwQE7i2eWiDwHyQ= +github.com/tonkeeper/tongo v1.9.6-0.20240913095748-e4fe80db484b/go.mod h1:MjgIgAytFarjCoVjMLjYEtpZNN1f2G/pnZhKjr28cWs= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= diff --git a/pkg/api/handler.go b/pkg/api/handler.go index bccae64..9bb13d2 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -5,7 +5,10 @@ import ( "fmt" "strconv" "strings" + "time" + "github.com/avast/retry-go" + "github.com/tonkeeper/tongo/abi" boc "github.com/tonkeeper/tongo/boc" "github.com/tonkeeper/tongo/contract/jetton" "github.com/tonkeeper/tongo/liteapi" @@ -24,6 +27,7 @@ type Handler struct { prover *prover.Prover jettonMaster ton.AccountID + cli *liteapi.Client } type Config struct { @@ -38,7 +42,6 @@ func NewHandler(logger *zap.Logger, config Config) (*Handler, error) { if err != nil { return nil, err } - _ = cli jettonMaster := jetton.New(config.JettonMaster, cli) _ = jettonMaster @@ -51,6 +54,7 @@ func NewHandler(logger *zap.Logger, config Config) (*Handler, error) { } return &Handler{ prover: p, + cli: cli, logger: logger, jettonMaster: config.JettonMaster, }, nil @@ -69,16 +73,15 @@ func (h *Handler) Run(ctx context.Context) { go h.prover.Run(ctx) } -func (h *Handler) convertToWalletInfo(airdrop prover.WalletAirdrop) (*oas.WalletInfo, error) { +func (h *Handler) convertToWalletInfo(ctx context.Context, airdrop prover.WalletAirdrop) (*oas.WalletInfo, error) { customPayload, err := createCustomPayload(airdrop.Proof) if err != nil { return nil, err } - stateInit, err := createStateInit(airdrop.AccountID, h.jettonMaster, h.prover.MerkleRoot()) + stateInit, err := h.getStateInit(ctx, airdrop.AccountID) if err != nil { return nil, err } - compressedInfo := oas.WalletInfoCompressedInfo{ Amount: strconv.FormatUint(uint64(airdrop.Data.Amount), 10), StartFrom: strconv.FormatUint(uint64(airdrop.Data.StartFrom), 10), @@ -112,7 +115,7 @@ func (h *Handler) GetWalletInfo(ctx context.Context, params oas.GetWalletInfoPar if resp.Err != nil { return nil, InternalError(resp.Err) } - info, err := h.convertToWalletInfo(resp.WalletAirdrop) + info, err := h.convertToWalletInfo(ctx, resp.WalletAirdrop) if err != nil { return nil, InternalError(err) } @@ -146,38 +149,6 @@ type JettonData struct { MerkleRoot tlb.Bits256 } -func createStateInit(owner, minter ton.AccountID, merkleRoot tlb.Bits256) (string, error) { - data := JettonData{ - Status: 0, - Balance: 0, - OwnerAddress: owner.ToMsgAddress(), - JettonMasterAddress: minter.ToMsgAddress(), - MerkleRoot: merkleRoot, - } - - dataCell := boc.NewCell() - if err := tlb.Marshal(dataCell, data); err != nil { - return "", err - } - jettonWalletCodeCells, err := boc.DeserializeBocHex("b5ee9c720101010100230008420259c02d4546e62393684b9ec55ae8b1c9d169415ff94502a93a63b0566c27ba15") - if err != nil { - return "", err - } - if len(jettonWalletCodeCells) != 1 { - return "", fmt.Errorf("unexpected number of cells") - } - - state := tlb.StateInit{ - Code: tlb.Maybe[tlb.Ref[boc.Cell]]{Exists: true, Value: tlb.Ref[boc.Cell]{Value: *jettonWalletCodeCells[0]}}, - Data: tlb.Maybe[tlb.Ref[boc.Cell]]{Exists: true, Value: tlb.Ref[boc.Cell]{Value: *dataCell}}, - } - c := boc.NewCell() - if err := tlb.Marshal(c, state); err != nil { - return "", err - } - return c.ToBocBase64() -} - func (h *Handler) GetWallets(ctx context.Context, params oas.GetWalletsParams) (*oas.WalletList, error) { next, err := ton.ParseAccountID(params.NextFrom) if err != nil { @@ -217,5 +188,71 @@ func (h *Handler) GetWallets(ctx context.Context, params oas.GetWalletsParams) ( } return &oas.WalletList{Wallets: items, NextFrom: nextFrom}, nil } +} + +func (h *Handler) getStateInit(ctx context.Context, owner ton.AccountID) (string, error) { + // TODO: add cache + var stateInit boc.Cell + err := retry.Do(func() error { + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + + _, value, err := GetWalletStateInitAndSalt(ctx, h.cli, h.jettonMaster, owner.ToMsgAddress()) + if err != nil { + return err + } + result, ok := value.(GetWalletStateInitAndSaltResult) + if !ok { + return fmt.Errorf("failed to get state init") + } + stateInit = boc.Cell(result.StateInit) + return nil + }, retry.Attempts(3), retry.Delay(1*time.Second)) + if err != nil { + return "", err + } + return stateInit.ToBocBase64() +} + +type GetWalletStateInitAndSaltResult struct { + StateInit tlb.Any + Salt int64 +} +func GetWalletStateInitAndSalt(ctx context.Context, executor abi.Executor, reqAccountID ton.AccountID, ownerAddress tlb.MsgAddress) (string, any, error) { + stack := tlb.VmStack{} + var ( + val tlb.VmStackValue + err error + ) + val, err = tlb.TlbStructToVmCellSlice(ownerAddress) + if err != nil { + return "", nil, err + } + stack.Put(val) + + // MethodID = 69258 for "get_wallet_state_init_and_salt" method + errCode, stack, err := executor.RunSmcMethodByID(ctx, reqAccountID, 69258, stack) + if err != nil { + return "", nil, err + } + if errCode != 0 && errCode != 1 { + return "", nil, fmt.Errorf("method execution failed with code: %v", errCode) + } + for _, f := range []func(tlb.VmStack) (string, any, error){DecodeGetWalletStateInitAndSaltResult} { + s, r, err := f(stack) + if err == nil { + return s, r, nil + } + } + return "", nil, fmt.Errorf("can not decode outputs") +} + +func DecodeGetWalletStateInitAndSaltResult(stack tlb.VmStack) (resultType string, resultAny any, err error) { + if len(stack) != 2 || (stack[0].SumType != "VmStkCell") || (stack[1].SumType != "VmStkTinyInt" && stack[1].SumType != "VmStkInt") { + return "", nil, fmt.Errorf("invalid stack format") + } + var result GetWalletStateInitAndSaltResult + err = stack.Unmarshal(&result) + return "GetWalletStateInitAndSaltResult", result, err } diff --git a/pkg/api/handler_test.go b/pkg/api/handler_test.go new file mode 100644 index 0000000..9c4f30a --- /dev/null +++ b/pkg/api/handler_test.go @@ -0,0 +1,47 @@ +package api + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tonkeeper/tongo/boc" + "github.com/tonkeeper/tongo/liteapi" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" + "go.uber.org/zap" +) + +func TestHandler_getStateInit(t *testing.T) { + tests := []struct { + name string + owner ton.AccountID + wantErr bool + }{ + { + owner: ton.MustParseAccountID("0:6ccd325a858c379693fae2bcaab1c2906831a4e10a6c3bb44ee8b615bca1d220"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, _ := zap.NewDevelopment() + cli, err := liteapi.NewClient(liteapi.Mainnet()) + require.Nil(t, err) + h := &Handler{ + cli: cli, + logger: logger, + jettonMaster: ton.MustParseAccountID("EQD6Z9DHc5Mx-8PI8I4BjGX0d2NhapaRAK12CgstweNoMint"), + } + stateInit, err := h.getStateInit(context.Background(), tt.owner) + require.Nil(t, err) + cells, err := boc.DeserializeBocBase64(stateInit) + require.Nil(t, err) + require.Equal(t, 1, len(cells)) + + var init tlb.StateInit + require.Nil(t, tlb.Unmarshal(cells[0], &init)) + fmt.Printf("stateInit: %v\n", stateInit) + }) + } +}