276 lines
8.5 KiB
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
|
|
}
|