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" ) // GAS represents GAS native contract. type GAS struct { nep17TokenNative NEO INEO 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 ) // GASFactor is a divisor for finding GAS integral value. const GASFactor = NEOTotalSupply // newGAS returns GAS native contract. func newGAS(init int64) *GAS { g := &GAS{ initialSupply: init, } defer g.BuildHFSpecificMD(g.ActiveIn()) nep17 := newNEP17Native(nativenames.Gas, nativeids.GasToken, nil) nep17.symbol = "GAS" nep17.decimals = 8 nep17.factor = GASFactor nep17.incBalance = g.increaseBalance nep17.balFromBytes = g.balanceFromBytes g.nep17TokenNative = *nep17 return g } func (g *GAS) 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 *GAS) 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 GAS contract. func (g *GAS) 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 *GAS) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error { return nil } // OnPersist implements the Contract interface. func (g *GAS) 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.NEO.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 *GAS) PostPersist(ic *interop.Context) error { return nil } // getVitaExemptType determines if sender is exempt from fees and what type. func (g *GAS) 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 GAS from Treasury for local Vita holders (100% local). func (g *GAS) burnFromTreasury(ic *interop.Context, amount *big.Int) { g.burnFromTreasuryInternal(ic, amount, 100, 0) } // burnFromTreasuryWithSplit burns GAS with visiting fee split between local Treasury and inter-chain debt. func (g *GAS) 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 *GAS) 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 *GAS) ActiveIn() *config.Hardfork { return nil } // BalanceOf returns native GAS token balance for the acc. func (g *GAS) 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 }