mirror of
https://github.com/luxfi/chains.git
synced 2026-07-27 03:39:41 +00:00
141 lines
4.0 KiB
Go
141 lines
4.0 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package fee
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
)
|
|
|
|
// KV is the minimal key/value surface Ledger needs. It is the read/write subset
|
|
// of luxfi/database.Database (satisfied by versiondb, memdb, and any backing
|
|
// store), declared locally so the settlement primitive does not pin a database
|
|
// module version — keeping it buildable beside any VM under GOWORK=off.
|
|
type KV interface {
|
|
Has(key []byte) (bool, error)
|
|
Get(key []byte) ([]byte, error)
|
|
Put(key []byte, value []byte) error
|
|
}
|
|
|
|
var (
|
|
// balPrefix namespaces per-account balance entries: balPrefix||acct -> u64.
|
|
balPrefix = []byte("fee/bal/")
|
|
// burnedKey holds the cumulative burned-supply counter: -> u64.
|
|
burnedKey = []byte("fee/burned")
|
|
)
|
|
|
|
// Ledger is the canonical KV-backed Balances implementation. It writes to the
|
|
// VM's state KV (a versiondb in production), so every Credit/Burn participates
|
|
// in the block's atomic commit: a fee burn and the operation it pays for land
|
|
// together or not at all. Balances are nLUX (1e-6 LUX), matching the
|
|
// node/vms/types/fee floor units.
|
|
type Ledger struct {
|
|
kv KV
|
|
}
|
|
|
|
// NewLedger returns a Ledger over kv. kv is the VM's state database; in a block
|
|
// Accept it is the versiondb whose Commit the block performs.
|
|
func NewLedger(kv KV) *Ledger {
|
|
return &Ledger{kv: kv}
|
|
}
|
|
|
|
func balKey(acct Account) []byte {
|
|
k := make([]byte, 0, len(balPrefix)+len(acct))
|
|
k = append(k, balPrefix...)
|
|
k = append(k, acct[:]...)
|
|
return k
|
|
}
|
|
|
|
// readU64 returns the uint64 stored at key, or 0 if the key is absent.
|
|
func (l *Ledger) readU64(key []byte) (uint64, error) {
|
|
ok, err := l.kv.Has(key)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("fee ledger: has %x: %w", key, err)
|
|
}
|
|
if !ok {
|
|
return 0, nil
|
|
}
|
|
b, err := l.kv.Get(key)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("fee ledger: get %x: %w", key, err)
|
|
}
|
|
if len(b) != 8 {
|
|
return 0, fmt.Errorf("fee ledger: corrupt u64 at %x: len %d", key, len(b))
|
|
}
|
|
return binary.BigEndian.Uint64(b), nil
|
|
}
|
|
|
|
func (l *Ledger) writeU64(key []byte, v uint64) error {
|
|
var b [8]byte
|
|
binary.BigEndian.PutUint64(b[:], v)
|
|
if err := l.kv.Put(key, b[:]); err != nil {
|
|
return fmt.Errorf("fee ledger: put %x: %w", key, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Balance returns acct's spendable nLUX (0 if never funded).
|
|
func (l *Ledger) Balance(acct Account) (uint64, error) {
|
|
return l.readU64(balKey(acct))
|
|
}
|
|
|
|
// Credit adds amount nLUX to acct. Overflow is refused (fail-secure: minting
|
|
// must never silently wrap to a smaller balance).
|
|
func (l *Ledger) Credit(acct Account, amount uint64) error {
|
|
if amount == 0 {
|
|
return nil
|
|
}
|
|
key := balKey(acct)
|
|
cur, err := l.readU64(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
next, over := addOverflow(cur, amount)
|
|
if over {
|
|
return ErrBalanceOverflow
|
|
}
|
|
return l.writeU64(key, next)
|
|
}
|
|
|
|
// Burn debits amount nLUX from acct and reduces circulating supply by the same
|
|
// amount (the burned counter rises). It is the fee settlement op: it returns
|
|
// ErrInsufficientFunds if acct cannot cover amount, leaving state untouched.
|
|
func (l *Ledger) Burn(acct Account, amount uint64) error {
|
|
if amount == 0 {
|
|
return nil
|
|
}
|
|
key := balKey(acct)
|
|
cur, err := l.readU64(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cur < amount {
|
|
return ErrInsufficientFunds
|
|
}
|
|
burned, err := l.readU64(burnedKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nextBurned, over := addOverflow(burned, amount)
|
|
if over {
|
|
// Burned-supply accounting must never wrap; refuse rather than corrupt
|
|
// the audit total. Unreachable in practice (burned <= genesis supply).
|
|
return ErrBalanceOverflow
|
|
}
|
|
// Debit first, then record the burn. Both writes hit the same versiondb and
|
|
// commit atomically with the block; a mid-sequence failure aborts the block.
|
|
if err := l.writeU64(key, cur-amount); err != nil {
|
|
return err
|
|
}
|
|
return l.writeU64(burnedKey, nextBurned)
|
|
}
|
|
|
|
// Burned returns cumulative burned supply in nLUX.
|
|
func (l *Ledger) Burned() (uint64, error) {
|
|
return l.readU64(burnedKey)
|
|
}
|
|
|
|
// Ledger is a Balances.
|
|
var _ Balances = (*Ledger)(nil)
|