Ctrl K
ComunidadGuías…

internal/app/wallet/transfer_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package wallet

import (
	"bytes"
	"crypto/ed25519"
	"encoding/base64"
	"encoding/json"
	"strings"
	"testing"
	"time"

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

// testAddress builds an address the way the wallet does, so the parser is
// tested against the addresses this deployment actually hands out.
func testAddress(t *testing.T, seed byte) string {
	t.Helper()
	hash := make([]byte, 32)
	for i := range hash {
		hash[i] = seed + byte(i)
	}
	addr := address.NewAddress(0, 0, hash)
	return addr.String()
}

// buildTransferBody builds the body a client sends for a transfer: a TON
// external message carrying a wallet transfer and, optionally, the note the
// sender wrote.
//
// The signature is over the unsigned body, so the body is built first, signed,
// and the signature is prepended. A client that does not hold the wallet's key
// cannot sign. Both legacy forms must now be rejected by the internal ledger.
func buildTransferBody(t *testing.T, recipient string, amount int64, comment string, sign bool, key ed25519.PrivateKey) []byte {
	t.Helper()
	dst, err := address.ParseAddr(recipient)
	if err != nil {
		t.Fatalf("parse the recipient address: %v", err)
	}
	coins := tlb.FromNanoTONU(uint64(amount))

	// The note is the text the sender wrote: the comment op and the text.
	var commentCell *cell.Cell
	if comment != "" {
		builder := cell.BeginCell()
		if err := builder.StoreUInt(0, 32); err != nil {
			t.Fatalf("store the comment op: %v", err)
		}
		if err := builder.StoreStringSnake(comment); err != nil {
			t.Fatalf("store the comment: %v", err)
		}
		commentCell = builder.EndCell()
	}
	msg := tlb.InternalMessage{DstAddr: dst, Amount: coins, Body: commentCell}
	msgCell, err := tlb.ToCell(msg)
	if err != nil {
		t.Fatalf("build the internal message: %v", err)
	}

	// The body: the opcode, the wallet id, the expiry, the sequence number and
	// the mode, then the message as a reference.
	body := cell.BeginCell()
	if err := body.StoreUInt(0x63896e75, 32); err != nil {
		t.Fatalf("store the opcode: %v", err)
	}
	if err := body.StoreUInt(0x7fff7f11, 32); err != nil {
		t.Fatalf("store the wallet id: %v", err)
	}
	if err := body.StoreUInt(uint64(time.Now().Add(time.Hour).Unix()), 32); err != nil {
		t.Fatalf("store the expiry: %v", err)
	}
	if err := body.StoreUInt(0, 32); err != nil {
		t.Fatalf("store the sequence number: %v", err)
	}
	if err := body.StoreUInt(3, 8); err != nil {
		t.Fatalf("store the mode: %v", err)
	}
	if err := body.StoreRef(msgCell); err != nil {
		t.Fatalf("store the message ref: %v", err)
	}
	bodyCell := body.EndCell()

	if !sign {
		// The unsigned body is the body itself.
		ext := tlb.ExternalMessage{DstAddr: dst, Body: bodyCell}
		root, err := tlb.ToCell(ext)
		if err != nil {
			t.Fatalf("build the external message: %v", err)
		}
		return root.ToBOC()
	}

	// The signed body is the signature followed by the unsigned body.
	signed := cell.BeginCell()
	if err := signed.StoreSlice(ed25519.Sign(key, bodyCell.Hash()), 512); err != nil {
		t.Fatalf("store the signature: %v", err)
	}
	if err := signed.StoreRef(bodyCell); err != nil {
		t.Fatalf("store the unsigned ref: %v", err)
	}
	ext := tlb.ExternalMessage{DstAddr: dst, Body: signed.EndCell()}
	root, err := tlb.ToCell(ext)
	if err != nil {
		t.Fatalf("build the external message: %v", err)
	}
	return root.ToBOC()
}

func TestParseTransferBodyRequiresOneUsePasswordEnvelope(t *testing.T) {
	secret := bytes.Repeat([]byte{7}, 32)
	wire := map[string]any{"version": 1, "recipient_user_id": 42, "random_id": 99, "amount_nanoton": int64(12500000000), "comment": "  hello\nworld  ", "tmp_password": base64.StdEncoding.EncodeToString(secret)}
	body, _ := json.Marshal(wire)
	got, err := ParseTransferBody(body)
	if err != nil || got.AmountNanoton != 12500000000 || got.RecipientUserID != 42 || got.RandomID != 99 || !bytes.Equal(got.TmpPassword, secret) || got.Comment != "helloworld" {
		t.Fatalf("parse: %+v %v", got, err)
	}
	for _, field := range []string{"version", "recipient_user_id", "random_id", "amount_nanoton", "tmp_password"} {
		t.Run(field, func(t *testing.T) {
			copy := map[string]any{}
			for k, v := range wire {
				copy[k] = v
			}
			delete(copy, field)
			b, _ := json.Marshal(copy)
			if _, err := ParseTransferBody(b); err == nil {
				t.Fatal("incomplete authorization accepted")
			}
		})
	}
	wire["unexpected"] = true
	body, _ = json.Marshal(wire)
	if _, err := ParseTransferBody(body); err == nil {
		t.Fatal("unknown field accepted")
	}
}
func TestParseTransferBodyRejectsLegacyAndTON(t *testing.T) {
	_, key, _ := ed25519.GenerateKey(nil)
	bodies := [][]byte{nil, []byte(`{"amount_nanoton":12500000000}`), []byte(`{}`), []byte(strings.Repeat("x", 4097)), buildTransferBody(t, testAddress(t, 7), 100, "", false, nil), buildTransferBody(t, testAddress(t, 7), 100, "", true, key)}
	for i, body := range bodies {
		if _, err := ParseTransferBody(body); err == nil {
			t.Fatalf("legacy body %d accepted", i)
		}
	}
}
func TestTransferCommentIsSanitized(t *testing.T) {
	if got := sanitizeTransferComment("  " + strings.Repeat("я", 200) + "\n"); len([]rune(got)) != 140 {
		t.Fatal("comment bound lost")
	}
}