internal/app/wallet/transfer_test.go
Rama main · réplica de lectura del repositorio original.
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")
}
}