package state import ( "errors" "fmt" "math/big" "github.com/tutus-one/tutus-chain/pkg/util" "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" ) // Spending category constants (bitmask). const ( CategoryUnrestricted uint8 = 0 // No restrictions (standard money) CategoryFood uint8 = 1 << 0 // Bit 0: Food purchases CategoryShelter uint8 = 1 << 1 // Bit 1: Rent/utilities/housing CategoryMedical uint8 = 1 << 2 // Bit 2: Healthcare expenses CategoryEducation uint8 = 1 << 3 // Bit 3: Tuition/books/supplies CategoryTransport uint8 = 1 << 4 // Bit 4: Transportation CategoryAll uint8 = 0xFF // All categories ) // TxType represents the type of VTS transaction. type TxType uint8 const ( TxTypeTransfer TxType = 0 // P2P transfer (neutral) TxTypeIncome TxType = 1 // Wages, sales, dividends (taxable) TxTypeExpense TxType = 2 // Purchases, bills (potentially deductible) TxTypeBenefit TxType = 3 // Government benefits (tax-exempt income) TxTypeGift TxType = 4 // Gift (may have gift tax implications) TxTypeTax TxType = 5 // Tax payment/withholding ) // VTSBalance represents the balance state of a VTS token holder. // It tracks both unrestricted and category-restricted balances. type VTSBalance struct { Unrestricted big.Int // Unrestricted balance (can be spent anywhere) Restricted map[uint8]*big.Int // Category -> restricted balance } // NewVTSBalance creates a new empty VTSBalance. func NewVTSBalance() *VTSBalance { return &VTSBalance{ Restricted: make(map[uint8]*big.Int), } } // Total returns the total balance (unrestricted + all restricted). func (b *VTSBalance) Total() *big.Int { total := new(big.Int).Set(&b.Unrestricted) for _, amt := range b.Restricted { total.Add(total, amt) } return total } // ToStackItem converts VTSBalance to stackitem. func (b *VTSBalance) ToStackItem() (stackitem.Item, error) { // Format: [unrestricted, [[category, amount], [category, amount], ...]] restrictedItems := make([]stackitem.Item, 0, len(b.Restricted)) for cat, amt := range b.Restricted { if amt.Sign() > 0 { restrictedItems = append(restrictedItems, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(cat))), stackitem.NewBigInteger(amt), })) } } return stackitem.NewStruct([]stackitem.Item{ stackitem.NewBigInteger(&b.Unrestricted), stackitem.NewArray(restrictedItems), }), nil } // FromStackItem converts stackitem to VTSBalance. func (b *VTSBalance) FromStackItem(item stackitem.Item) error { structItems, ok := item.Value().([]stackitem.Item) if !ok || len(structItems) < 2 { return errors.New("invalid VTSBalance structure") } unrestricted, err := structItems[0].TryInteger() if err != nil { return fmt.Errorf("invalid unrestricted balance: %w", err) } b.Unrestricted = *unrestricted restrictedArr, ok := structItems[1].Value().([]stackitem.Item) if !ok { return errors.New("invalid restricted balances array") } b.Restricted = make(map[uint8]*big.Int) for _, ri := range restrictedArr { pair, ok := ri.Value().([]stackitem.Item) if !ok || len(pair) != 2 { return errors.New("invalid restricted balance pair") } catBI, err := pair[0].TryInteger() if err != nil { return fmt.Errorf("invalid category: %w", err) } amt, err := pair[1].TryInteger() if err != nil { return fmt.Errorf("invalid amount: %w", err) } b.Restricted[uint8(catBI.Int64())] = amt } return nil } // Vendor represents a registered vendor/merchant that can accept VTS payments. type Vendor struct { Address util.Uint160 // Vendor's script hash Name string // Display name (max 64 chars) Categories uint8 // Bitmask of accepted spending categories RegisteredAt uint32 // Block height when registered RegisteredBy util.Uint160 // Who registered this vendor Active bool // Whether vendor is currently active } // ToStackItem converts Vendor to stackitem. func (v *Vendor) ToStackItem() (stackitem.Item, error) { return stackitem.NewStruct([]stackitem.Item{ stackitem.NewByteArray(v.Address.BytesBE()), stackitem.NewByteArray([]byte(v.Name)), stackitem.NewBigInteger(big.NewInt(int64(v.Categories))), stackitem.NewBigInteger(big.NewInt(int64(v.RegisteredAt))), stackitem.NewByteArray(v.RegisteredBy.BytesBE()), stackitem.NewBool(v.Active), }), nil } // FromStackItem converts stackitem to Vendor. func (v *Vendor) FromStackItem(item stackitem.Item) error { structItems, ok := item.Value().([]stackitem.Item) if !ok || len(structItems) < 6 { return errors.New("invalid Vendor structure") } addrBytes, err := structItems[0].TryBytes() if err != nil { return fmt.Errorf("invalid address: %w", err) } v.Address, err = util.Uint160DecodeBytesBE(addrBytes) if err != nil { return fmt.Errorf("invalid address bytes: %w", err) } nameBytes, err := structItems[1].TryBytes() if err != nil { return fmt.Errorf("invalid name: %w", err) } v.Name = string(nameBytes) catBI, err := structItems[2].TryInteger() if err != nil { return fmt.Errorf("invalid categories: %w", err) } v.Categories = uint8(catBI.Int64()) regAtBI, err := structItems[3].TryInteger() if err != nil { return fmt.Errorf("invalid registeredAt: %w", err) } v.RegisteredAt = uint32(regAtBI.Int64()) regByBytes, err := structItems[4].TryBytes() if err != nil { return fmt.Errorf("invalid registeredBy: %w", err) } v.RegisteredBy, err = util.Uint160DecodeBytesBE(regByBytes) if err != nil { return fmt.Errorf("invalid registeredBy bytes: %w", err) } active, err := structItems[5].TryBool() if err != nil { return fmt.Errorf("invalid active: %w", err) } v.Active = active return nil } // TransactionRecord represents a recorded VTS transaction for tax accounting. type TransactionRecord struct { TxHash util.Uint256 // Transaction hash BlockHeight uint32 // When it occurred From util.Uint160 // Sender To util.Uint160 // Recipient Amount big.Int // Gross amount TxType TxType // Type of transaction Category uint8 // Spending category (if applicable) TaxWithheld big.Int // Tax withheld at source TaxRate uint16 // Rate applied (basis points, e.g., 2500 = 25%) Memo string // Optional description (max 256 chars) } // ToStackItem converts TransactionRecord to stackitem. func (t *TransactionRecord) ToStackItem() (stackitem.Item, error) { return stackitem.NewStruct([]stackitem.Item{ stackitem.NewByteArray(t.TxHash.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(t.BlockHeight))), stackitem.NewByteArray(t.From.BytesBE()), stackitem.NewByteArray(t.To.BytesBE()), stackitem.NewBigInteger(&t.Amount), stackitem.NewBigInteger(big.NewInt(int64(t.TxType))), stackitem.NewBigInteger(big.NewInt(int64(t.Category))), stackitem.NewBigInteger(&t.TaxWithheld), stackitem.NewBigInteger(big.NewInt(int64(t.TaxRate))), stackitem.NewByteArray([]byte(t.Memo)), }), nil } // FromStackItem converts stackitem to TransactionRecord. func (t *TransactionRecord) FromStackItem(item stackitem.Item) error { structItems, ok := item.Value().([]stackitem.Item) if !ok || len(structItems) < 10 { return errors.New("invalid TransactionRecord structure") } txHashBytes, err := structItems[0].TryBytes() if err != nil { return fmt.Errorf("invalid txHash: %w", err) } t.TxHash, err = util.Uint256DecodeBytesBE(txHashBytes) if err != nil { return fmt.Errorf("invalid txHash bytes: %w", err) } blockHeightBI, err := structItems[1].TryInteger() if err != nil { return fmt.Errorf("invalid blockHeight: %w", err) } t.BlockHeight = uint32(blockHeightBI.Int64()) fromBytes, err := structItems[2].TryBytes() if err != nil { return fmt.Errorf("invalid from: %w", err) } t.From, err = util.Uint160DecodeBytesBE(fromBytes) if err != nil { return fmt.Errorf("invalid from bytes: %w", err) } toBytes, err := structItems[3].TryBytes() if err != nil { return fmt.Errorf("invalid to: %w", err) } t.To, err = util.Uint160DecodeBytesBE(toBytes) if err != nil { return fmt.Errorf("invalid to bytes: %w", err) } amount, err := structItems[4].TryInteger() if err != nil { return fmt.Errorf("invalid amount: %w", err) } t.Amount = *amount txTypeBI, err := structItems[5].TryInteger() if err != nil { return fmt.Errorf("invalid txType: %w", err) } t.TxType = TxType(txTypeBI.Int64()) catBI, err := structItems[6].TryInteger() if err != nil { return fmt.Errorf("invalid category: %w", err) } t.Category = uint8(catBI.Int64()) taxWithheld, err := structItems[7].TryInteger() if err != nil { return fmt.Errorf("invalid taxWithheld: %w", err) } t.TaxWithheld = *taxWithheld taxRateBI, err := structItems[8].TryInteger() if err != nil { return fmt.Errorf("invalid taxRate: %w", err) } t.TaxRate = uint16(taxRateBI.Int64()) memoBytes, err := structItems[9].TryBytes() if err != nil { return fmt.Errorf("invalid memo: %w", err) } t.Memo = string(memoBytes) return nil } // TaxConfig represents the tax configuration for VTS. type TaxConfig struct { DefaultIncomeRate uint16 // Default income tax rate (basis points) DefaultSalesRate uint16 // Default sales tax rate (basis points) TreasuryAddress util.Uint160 // Where taxes are sent ExemptCategories uint8 // Categories exempt from sales tax (bitmask) } // ToStackItem converts TaxConfig to stackitem. func (c *TaxConfig) ToStackItem() (stackitem.Item, error) { return stackitem.NewStruct([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(c.DefaultIncomeRate))), stackitem.NewBigInteger(big.NewInt(int64(c.DefaultSalesRate))), stackitem.NewByteArray(c.TreasuryAddress.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(c.ExemptCategories))), }), nil } // FromStackItem converts stackitem to TaxConfig. func (c *TaxConfig) FromStackItem(item stackitem.Item) error { structItems, ok := item.Value().([]stackitem.Item) if !ok || len(structItems) < 4 { return errors.New("invalid TaxConfig structure") } incomeRateBI, err := structItems[0].TryInteger() if err != nil { return fmt.Errorf("invalid defaultIncomeRate: %w", err) } c.DefaultIncomeRate = uint16(incomeRateBI.Int64()) salesRateBI, err := structItems[1].TryInteger() if err != nil { return fmt.Errorf("invalid defaultSalesRate: %w", err) } c.DefaultSalesRate = uint16(salesRateBI.Int64()) treasuryBytes, err := structItems[2].TryBytes() if err != nil { return fmt.Errorf("invalid treasuryAddress: %w", err) } c.TreasuryAddress, err = util.Uint160DecodeBytesBE(treasuryBytes) if err != nil { return fmt.Errorf("invalid treasuryAddress bytes: %w", err) } exemptCatBI, err := structItems[3].TryInteger() if err != nil { return fmt.Errorf("invalid exemptCategories: %w", err) } c.ExemptCategories = uint8(exemptCatBI.Int64()) return nil } // TaxSummary represents a tax summary for a specific period. type TaxSummary struct { Account util.Uint160 // Account address StartBlock uint32 // Period start EndBlock uint32 // Period end TotalIncome big.Int // Total taxable income TotalBenefits big.Int // Total tax-exempt benefits TotalExpenses big.Int // Total expenses (for deductions) DeductibleExpenses big.Int // Expenses that are deductible TaxWithheld big.Int // Total tax withheld EstimatedOwed big.Int // Estimated tax owed (income * rate) Balance big.Int // TaxWithheld - EstimatedOwed } // ToStackItem converts TaxSummary to stackitem. func (s *TaxSummary) ToStackItem() (stackitem.Item, error) { return stackitem.NewStruct([]stackitem.Item{ stackitem.NewByteArray(s.Account.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(s.StartBlock))), stackitem.NewBigInteger(big.NewInt(int64(s.EndBlock))), stackitem.NewBigInteger(&s.TotalIncome), stackitem.NewBigInteger(&s.TotalBenefits), stackitem.NewBigInteger(&s.TotalExpenses), stackitem.NewBigInteger(&s.DeductibleExpenses), stackitem.NewBigInteger(&s.TaxWithheld), stackitem.NewBigInteger(&s.EstimatedOwed), stackitem.NewBigInteger(&s.Balance), }), nil } // FromStackItem converts stackitem to TaxSummary. func (s *TaxSummary) FromStackItem(item stackitem.Item) error { structItems, ok := item.Value().([]stackitem.Item) if !ok || len(structItems) < 10 { return errors.New("invalid TaxSummary structure") } accountBytes, err := structItems[0].TryBytes() if err != nil { return fmt.Errorf("invalid account: %w", err) } s.Account, err = util.Uint160DecodeBytesBE(accountBytes) if err != nil { return fmt.Errorf("invalid account bytes: %w", err) } startBI, err := structItems[1].TryInteger() if err != nil { return fmt.Errorf("invalid startBlock: %w", err) } s.StartBlock = uint32(startBI.Int64()) endBI, err := structItems[2].TryInteger() if err != nil { return fmt.Errorf("invalid endBlock: %w", err) } s.EndBlock = uint32(endBI.Int64()) totalIncome, err := structItems[3].TryInteger() if err != nil { return fmt.Errorf("invalid totalIncome: %w", err) } s.TotalIncome = *totalIncome totalBenefits, err := structItems[4].TryInteger() if err != nil { return fmt.Errorf("invalid totalBenefits: %w", err) } s.TotalBenefits = *totalBenefits totalExpenses, err := structItems[5].TryInteger() if err != nil { return fmt.Errorf("invalid totalExpenses: %w", err) } s.TotalExpenses = *totalExpenses deductible, err := structItems[6].TryInteger() if err != nil { return fmt.Errorf("invalid deductibleExpenses: %w", err) } s.DeductibleExpenses = *deductible taxWithheld, err := structItems[7].TryInteger() if err != nil { return fmt.Errorf("invalid taxWithheld: %w", err) } s.TaxWithheld = *taxWithheld estimatedOwed, err := structItems[8].TryInteger() if err != nil { return fmt.Errorf("invalid estimatedOwed: %w", err) } s.EstimatedOwed = *estimatedOwed balance, err := structItems[9].TryInteger() if err != nil { return fmt.Errorf("invalid balance: %w", err) } s.Balance = *balance return nil }