tutus-chain/pkg/core/native/native_lub.go

276 lines
8.5 KiB
Go

package native
import (
"errors"
"math/big"
"github.com/tutus-one/tutus-chain/pkg/config"
"github.com/tutus-one/tutus-chain/pkg/core/dao"
"github.com/tutus-one/tutus-chain/pkg/core/interop"
"github.com/tutus-one/tutus-chain/pkg/core/native/nativeids"
"github.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
"github.com/tutus-one/tutus-chain/pkg/core/state"
"github.com/tutus-one/tutus-chain/pkg/core/transaction"
"github.com/tutus-one/tutus-chain/pkg/crypto/hash"
"github.com/tutus-one/tutus-chain/pkg/crypto/keys"
"github.com/tutus-one/tutus-chain/pkg/smartcontract"
"github.com/tutus-one/tutus-chain/pkg/util"
)
// Lub represents Lub native contract (utility/fee token - lubrication).
type Lub struct {
nep17TokenNative
Tutus ITutus
Policy IPolicy
Vita IVita // For checking local Vita status
Federation IFederation // For visiting Vita and fee split
Treasury ITreasury // For subsidizing fees
initialSupply int64
}
// VitaExemptType indicates the type of Vita exemption for fee purposes.
type VitaExemptType int
const (
// VitaExemptNone indicates no Vita exemption.
VitaExemptNone VitaExemptType = iota
// VitaExemptLocal indicates a local Vita holder (100% paid by local Treasury).
VitaExemptLocal
// VitaExemptVisitor indicates a visiting Vita holder (split between local and home chain).
VitaExemptVisitor
// VitaExemptAsylum indicates an asylum seeker (100% local Treasury, no inter-chain debt).
// Used for humanitarian override when home chain becomes hostile.
VitaExemptAsylum
)
// LubFactor is a divisor for finding Lub integral value.
const LubFactor = TutusTotalSupply
// newLub returns Lub native contract.
func newLub(init int64) *Lub {
g := &Lub{
initialSupply: init,
}
defer g.BuildHFSpecificMD(g.ActiveIn())
nep17 := newNEP17Native(nativenames.Lub, nativeids.Lub, nil)
nep17.symbol = "LUB"
nep17.decimals = 8
nep17.factor = LubFactor
nep17.incBalance = g.increaseBalance
nep17.balFromBytes = g.balanceFromBytes
g.nep17TokenNative = *nep17
return g
}
func (g *Lub) increaseBalance(_ *interop.Context, _ util.Uint160, si *state.StorageItem, amount *big.Int, checkBal *big.Int) (func(), error) {
acc, err := state.NEP17BalanceFromBytes(*si)
if err != nil {
return nil, err
}
if sign := amount.Sign(); sign == 0 {
// Requested self-transfer amount can be higher than actual balance.
if checkBal != nil && acc.Balance.Cmp(checkBal) < 0 {
err = errors.New("insufficient funds")
}
return nil, err
} else if sign == -1 && acc.Balance.CmpAbs(amount) == -1 {
return nil, errors.New("insufficient funds")
}
acc.Balance.Add(&acc.Balance, amount)
if acc.Balance.Sign() != 0 {
*si = acc.Bytes(nil)
} else {
*si = nil
}
return nil, nil
}
func (g *Lub) balanceFromBytes(si *state.StorageItem) (*big.Int, error) {
acc, err := state.NEP17BalanceFromBytes(*si)
if err != nil {
return nil, err
}
return &acc.Balance, err
}
// Initialize initializes a Lub contract.
func (g *Lub) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != g.ActiveIn() {
return nil
}
if err := g.nep17TokenNative.Initialize(ic); err != nil {
return err
}
_, totalSupply := g.getTotalSupply(ic.DAO)
if totalSupply.Sign() != 0 {
return errors.New("already initialized")
}
h, err := getStandbyValidatorsHash(ic)
if err != nil {
return err
}
g.Mint(ic, h, big.NewInt(g.initialSupply), false)
return nil
}
// InitializeCache implements the Contract interface.
func (g *Lub) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
return nil
}
// OnPersist implements the Contract interface.
func (g *Lub) OnPersist(ic *interop.Context) error {
if len(ic.Block.Transactions) == 0 {
return nil
}
for _, tx := range ic.Block.Transactions {
absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee)
sender := tx.Sender()
// Check fee exemption type
exemptType := g.getVitaExemptType(ic.DAO, sender)
switch exemptType {
case VitaExemptLocal:
// Local citizen: 100% from Treasury
g.burnFromTreasury(ic, absAmount)
case VitaExemptVisitor:
// Visitor: split between local Treasury and inter-chain debt
var homeChain uint32
if g.Federation != nil {
homeChain = g.Federation.GetHomeChain(ic.DAO, sender)
}
g.burnFromTreasuryWithSplit(ic, absAmount, homeChain)
case VitaExemptAsylum:
// Asylum seeker: 100% from local Treasury, no inter-chain debt
// Humanitarian override - don't fund hostile home chain
g.burnFromTreasury(ic, absAmount)
default:
// Non-citizen: burn from sender
g.Burn(ic, sender, absAmount)
}
}
validators := g.Tutus.GetNextBlockValidatorsInternal(ic.DAO)
primary := validators[ic.Block.PrimaryIndex].GetScriptHash()
var netFee int64
for _, tx := range ic.Block.Transactions {
netFee += tx.NetworkFee
// Reward for NotaryAssisted attribute will be minted to designated notary nodes
// by Notary contract.
attrs := tx.GetAttributes(transaction.NotaryAssistedT)
if len(attrs) != 0 {
na := attrs[0].Value.(*transaction.NotaryAssisted)
netFee -= (int64(na.NKeys) + 1) * g.Policy.GetAttributeFeeInternal(ic.DAO, transaction.NotaryAssistedT)
}
}
g.Mint(ic, primary, big.NewInt(int64(netFee)), false)
return nil
}
// PostPersist implements the Contract interface.
func (g *Lub) PostPersist(ic *interop.Context) error {
return nil
}
// getVitaExemptType determines if sender is exempt from fees and what type.
func (g *Lub) getVitaExemptType(d *dao.Simple, sender util.Uint160) VitaExemptType {
// Check local Vita first
if g.Vita != nil {
token, err := g.Vita.GetTokenByOwner(d, sender)
if err == nil && token != nil && token.Status == state.TokenStatusActive {
return VitaExemptLocal
}
}
// Check naturalized citizen (treated same as local)
if g.Federation != nil && g.Federation.IsNaturalizedCitizen(d, sender) {
return VitaExemptLocal
}
// Check visitor registry
if g.Federation != nil && g.Federation.IsVisitor(d, sender) {
return VitaExemptVisitor
}
// Check asylum registry (humanitarian override)
// Asylum seekers get fee exemption even if their home chain revoked their Vita
if g.Federation != nil && g.Federation.HasAsylum(d, sender) {
return VitaExemptAsylum
}
return VitaExemptNone
}
// burnFromTreasury burns Lub from Treasury for local Vita holders (100% local).
func (g *Lub) burnFromTreasury(ic *interop.Context, amount *big.Int) {
g.burnFromTreasuryInternal(ic, amount, 100, 0)
}
// burnFromTreasuryWithSplit burns Lub with visiting fee split between local Treasury and inter-chain debt.
func (g *Lub) burnFromTreasuryWithSplit(ic *interop.Context, amount *big.Int, homeChain uint32) {
localPercent := uint8(100)
if g.Federation != nil {
localPercent = g.Federation.GetVisitingFeePercent(ic.DAO)
}
g.burnFromTreasuryInternal(ic, amount, localPercent, homeChain)
}
// burnFromTreasuryInternal handles the actual burn with optional split between local and inter-chain.
func (g *Lub) burnFromTreasuryInternal(ic *interop.Context, amount *big.Int, localPercent uint8, homeChain uint32) {
if g.Treasury == nil {
return
}
treasuryAddr := g.Treasury.Address()
treasuryBalance := g.BalanceOf(ic.DAO, treasuryAddr)
// Calculate local vs inter-chain portions
localAmount := new(big.Int).Mul(amount, big.NewInt(int64(localPercent)))
localAmount.Div(localAmount, big.NewInt(100))
interChainAmount := new(big.Int).Sub(amount, localAmount)
// Burn from Treasury what we can
if treasuryBalance.Cmp(localAmount) >= 0 {
g.Burn(ic, treasuryAddr, localAmount)
} else if treasuryBalance.Sign() > 0 {
deficit := new(big.Int).Sub(localAmount, treasuryBalance)
g.Burn(ic, treasuryAddr, treasuryBalance)
g.Treasury.AddDeficit(ic.DAO, deficit)
} else {
g.Treasury.AddDeficit(ic.DAO, localAmount)
}
// Record inter-chain debt (if visitor)
if interChainAmount.Sign() > 0 && homeChain != 0 && g.Federation != nil {
g.Federation.AddInterChainDebt(ic.DAO, homeChain, interChainAmount)
}
}
// ActiveIn implements the Contract interface.
func (g *Lub) ActiveIn() *config.Hardfork {
return nil
}
// BalanceOf returns native Lub token balance for the acc.
func (g *Lub) BalanceOf(d *dao.Simple, acc util.Uint160) *big.Int {
return g.balanceOfInternal(d, acc)
}
func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) {
cfg := ic.Chain.GetConfig()
committee, err := keys.NewPublicKeysFromStrings(cfg.StandbyCommittee)
if err != nil {
return util.Uint160{}, err
}
s, err := smartcontract.CreateDefaultMultiSigRedeemScript(committee[:cfg.GetNumOfCNs(0)])
if err != nil {
return util.Uint160{}, err
}
return hash.Hash160(s), nil
}