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

445 lines
14 KiB
Go

package native
import (
"encoding/binary"
"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/smartcontract"
"github.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
"github.com/tutus-one/tutus-chain/pkg/smartcontract/manifest"
"github.com/tutus-one/tutus-chain/pkg/util"
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)
// Federation represents the Federation native contract for cross-chain Vita coordination.
// It manages:
// - Visiting fee percentage (what % of fees the host chain pays for visitors)
// - Visitor registry (tracking Vita holders from other chains)
// - Inter-chain debt (tracking fees owed to other chains)
type Federation struct {
interop.ContractMD
NEO INEO
}
// Storage key prefixes for Federation.
const (
prefixVisitingFeePercent byte = 0x01 // -> uint8 (0-100, % host chain pays for visitors)
prefixVisitorRegistry byte = 0x02 // owner (Uint160) -> home chain ID (uint32)
prefixInterChainDebt byte = 0x03 // chain ID (uint32) -> *big.Int (amount owed)
)
// Default values.
const (
defaultVisitingFeePercent uint8 = 50 // 50% local, 50% inter-chain debt
)
// Event names for Federation.
const (
VisitorRegisteredEvent = "VisitorRegistered"
VisitorUnregisteredEvent = "VisitorUnregistered"
FeePercentChangedEvent = "FeePercentChanged"
DebtSettledEvent = "DebtSettled"
)
// Various errors.
var (
ErrInvalidFeePercent = errors.New("fee percent must be 0-100")
ErrVisitorAlreadyExists = errors.New("visitor already registered")
ErrVisitorNotFound = errors.New("visitor not found")
ErrInvalidChainID = errors.New("invalid chain ID")
ErrInsufficientDebt = errors.New("insufficient debt to settle")
)
var _ interop.Contract = (*Federation)(nil)
// newFederation creates a new Federation native contract.
func newFederation() *Federation {
f := &Federation{
ContractMD: *interop.NewContractMD(nativenames.Federation, nativeids.Federation),
}
defer f.BuildHFSpecificMD(f.ActiveIn())
// getFeePercent method
desc := NewDescriptor("getFeePercent", smartcontract.IntegerType)
md := NewMethodAndPrice(f.getFeePercent, 1<<15, callflag.ReadStates)
f.AddMethod(md, desc)
// setFeePercent method (committee only)
desc = NewDescriptor("setFeePercent", smartcontract.BoolType,
manifest.NewParameter("percent", smartcontract.IntegerType))
md = NewMethodAndPrice(f.setFeePercent, 1<<16, callflag.States|callflag.AllowNotify)
f.AddMethod(md, desc)
// registerVisitor method (committee only)
desc = NewDescriptor("registerVisitor", smartcontract.BoolType,
manifest.NewParameter("owner", smartcontract.Hash160Type),
manifest.NewParameter("homeChainID", smartcontract.IntegerType))
md = NewMethodAndPrice(f.registerVisitor, 1<<16, callflag.States|callflag.AllowNotify)
f.AddMethod(md, desc)
// unregisterVisitor method (committee only)
desc = NewDescriptor("unregisterVisitor", smartcontract.BoolType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(f.unregisterVisitor, 1<<16, callflag.States|callflag.AllowNotify)
f.AddMethod(md, desc)
// isVisitor method
desc = NewDescriptor("isVisitor", smartcontract.BoolType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(f.isVisitor, 1<<15, callflag.ReadStates)
f.AddMethod(md, desc)
// getHomeChain method
desc = NewDescriptor("getHomeChain", smartcontract.IntegerType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(f.getHomeChain, 1<<15, callflag.ReadStates)
f.AddMethod(md, desc)
// getInterChainDebt method
desc = NewDescriptor("getInterChainDebt", smartcontract.IntegerType,
manifest.NewParameter("chainID", smartcontract.IntegerType))
md = NewMethodAndPrice(f.getInterChainDebt, 1<<15, callflag.ReadStates)
f.AddMethod(md, desc)
// settleDebt method (committee only)
desc = NewDescriptor("settleDebt", smartcontract.BoolType,
manifest.NewParameter("chainID", smartcontract.IntegerType),
manifest.NewParameter("amount", smartcontract.IntegerType))
md = NewMethodAndPrice(f.settleDebt, 1<<16, callflag.States|callflag.AllowNotify)
f.AddMethod(md, desc)
// Events
eDesc := NewEventDescriptor(VisitorRegisteredEvent,
manifest.NewParameter("owner", smartcontract.Hash160Type),
manifest.NewParameter("homeChainID", smartcontract.IntegerType))
f.AddEvent(NewEvent(eDesc))
eDesc = NewEventDescriptor(VisitorUnregisteredEvent,
manifest.NewParameter("owner", smartcontract.Hash160Type))
f.AddEvent(NewEvent(eDesc))
eDesc = NewEventDescriptor(FeePercentChangedEvent,
manifest.NewParameter("oldPercent", smartcontract.IntegerType),
manifest.NewParameter("newPercent", smartcontract.IntegerType))
f.AddEvent(NewEvent(eDesc))
eDesc = NewEventDescriptor(DebtSettledEvent,
manifest.NewParameter("chainID", smartcontract.IntegerType),
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("remaining", smartcontract.IntegerType))
f.AddEvent(NewEvent(eDesc))
return f
}
// Metadata returns contract metadata.
func (f *Federation) Metadata() *interop.ContractMD {
return &f.ContractMD
}
// Initialize initializes Federation contract at the specified hardfork.
func (f *Federation) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != f.ActiveIn() {
return nil
}
// Initialize default fee percent
f.setFeePercentInternal(ic.DAO, defaultVisitingFeePercent)
return nil
}
// InitializeCache fills native Federation cache from DAO on node restart.
func (f *Federation) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
return nil
}
// OnPersist implements the Contract interface.
func (f *Federation) OnPersist(ic *interop.Context) error {
return nil
}
// PostPersist implements the Contract interface.
func (f *Federation) PostPersist(ic *interop.Context) error {
return nil
}
// ActiveIn returns the hardfork this contract activates in.
func (f *Federation) ActiveIn() *config.Hardfork {
var hf = config.HFFaun
return &hf
}
// Storage key helpers
func makeFeePercentKey() []byte {
return []byte{prefixVisitingFeePercent}
}
func makeVisitorKey(owner util.Uint160) []byte {
key := make([]byte, 1+util.Uint160Size)
key[0] = prefixVisitorRegistry
copy(key[1:], owner.BytesBE())
return key
}
func makeDebtKey(chainID uint32) []byte {
key := make([]byte, 5)
key[0] = prefixInterChainDebt
binary.BigEndian.PutUint32(key[1:], chainID)
return key
}
// Internal storage methods
func (f *Federation) getFeePercentInternal(d *dao.Simple) uint8 {
si := d.GetStorageItem(f.ID, makeFeePercentKey())
if si == nil {
return defaultVisitingFeePercent
}
return si[0]
}
func (f *Federation) setFeePercentInternal(d *dao.Simple, percent uint8) {
d.PutStorageItem(f.ID, makeFeePercentKey(), []byte{percent})
}
func (f *Federation) getVisitorChainInternal(d *dao.Simple, owner util.Uint160) (uint32, bool) {
si := d.GetStorageItem(f.ID, makeVisitorKey(owner))
if si == nil {
return 0, false
}
return binary.BigEndian.Uint32(si), true
}
func (f *Federation) setVisitorChainInternal(d *dao.Simple, owner util.Uint160, chainID uint32) {
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, chainID)
d.PutStorageItem(f.ID, makeVisitorKey(owner), buf)
}
func (f *Federation) deleteVisitorInternal(d *dao.Simple, owner util.Uint160) {
d.DeleteStorageItem(f.ID, makeVisitorKey(owner))
}
func (f *Federation) getDebtInternal(d *dao.Simple, chainID uint32) *big.Int {
si := d.GetStorageItem(f.ID, makeDebtKey(chainID))
if si == nil {
return big.NewInt(0)
}
return new(big.Int).SetBytes(si)
}
func (f *Federation) setDebtInternal(d *dao.Simple, chainID uint32, amount *big.Int) {
if amount.Sign() <= 0 {
d.DeleteStorageItem(f.ID, makeDebtKey(chainID))
return
}
d.PutStorageItem(f.ID, makeDebtKey(chainID), amount.Bytes())
}
func (f *Federation) addDebtInternal(d *dao.Simple, chainID uint32, amount *big.Int) {
current := f.getDebtInternal(d, chainID)
newDebt := new(big.Int).Add(current, amount)
f.setDebtInternal(d, chainID, newDebt)
}
// Contract methods
func (f *Federation) getFeePercent(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
percent := f.getFeePercentInternal(ic.DAO)
return stackitem.NewBigInteger(big.NewInt(int64(percent)))
}
func (f *Federation) setFeePercent(ic *interop.Context, args []stackitem.Item) stackitem.Item {
percent := toBigInt(args[0]).Int64()
// Validate percent
if percent < 0 || percent > 100 {
panic(ErrInvalidFeePercent)
}
// Check committee
if !f.NEO.CheckCommittee(ic) {
panic(ErrNotCommittee)
}
// Get old value for event
oldPercent := f.getFeePercentInternal(ic.DAO)
// Set new value
f.setFeePercentInternal(ic.DAO, uint8(percent))
// Emit event
err := ic.AddNotification(f.Hash, FeePercentChangedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(oldPercent))),
stackitem.NewBigInteger(big.NewInt(percent)),
}))
if err != nil {
panic(err)
}
return stackitem.NewBool(true)
}
func (f *Federation) registerVisitor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
chainID := uint32(toBigInt(args[1]).Int64())
// Validate chain ID
if chainID == 0 {
panic(ErrInvalidChainID)
}
// Check committee
if !f.NEO.CheckCommittee(ic) {
panic(ErrNotCommittee)
}
// Check if already registered
if _, exists := f.getVisitorChainInternal(ic.DAO, owner); exists {
panic(ErrVisitorAlreadyExists)
}
// Register visitor
f.setVisitorChainInternal(ic.DAO, owner, chainID)
// Emit event
err := ic.AddNotification(f.Hash, VisitorRegisteredEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(owner.BytesBE()),
stackitem.NewBigInteger(big.NewInt(int64(chainID))),
}))
if err != nil {
panic(err)
}
return stackitem.NewBool(true)
}
func (f *Federation) unregisterVisitor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
// Check committee
if !f.NEO.CheckCommittee(ic) {
panic(ErrNotCommittee)
}
// Check if registered
if _, exists := f.getVisitorChainInternal(ic.DAO, owner); !exists {
panic(ErrVisitorNotFound)
}
// Unregister visitor
f.deleteVisitorInternal(ic.DAO, owner)
// Emit event
err := ic.AddNotification(f.Hash, VisitorUnregisteredEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(owner.BytesBE()),
}))
if err != nil {
panic(err)
}
return stackitem.NewBool(true)
}
func (f *Federation) isVisitor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
_, exists := f.getVisitorChainInternal(ic.DAO, owner)
return stackitem.NewBool(exists)
}
func (f *Federation) getHomeChain(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
chainID, exists := f.getVisitorChainInternal(ic.DAO, owner)
if !exists {
return stackitem.NewBigInteger(big.NewInt(0))
}
return stackitem.NewBigInteger(big.NewInt(int64(chainID)))
}
func (f *Federation) getInterChainDebt(ic *interop.Context, args []stackitem.Item) stackitem.Item {
chainID := uint32(toBigInt(args[0]).Int64())
debt := f.getDebtInternal(ic.DAO, chainID)
return stackitem.NewBigInteger(debt)
}
func (f *Federation) settleDebt(ic *interop.Context, args []stackitem.Item) stackitem.Item {
chainID := uint32(toBigInt(args[0]).Int64())
amount := toBigInt(args[1])
// Validate chain ID
if chainID == 0 {
panic(ErrInvalidChainID)
}
// Check committee
if !f.NEO.CheckCommittee(ic) {
panic(ErrNotCommittee)
}
// Get current debt
currentDebt := f.getDebtInternal(ic.DAO, chainID)
// Check sufficient debt
if currentDebt.Cmp(amount) < 0 {
panic(ErrInsufficientDebt)
}
// Subtract settled amount
remaining := new(big.Int).Sub(currentDebt, amount)
f.setDebtInternal(ic.DAO, chainID, remaining)
// Emit event
err := ic.AddNotification(f.Hash, DebtSettledEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(chainID))),
stackitem.NewBigInteger(amount),
stackitem.NewBigInteger(remaining),
}))
if err != nil {
panic(err)
}
return stackitem.NewBool(true)
}
// Public methods for cross-native access
// GetVisitingFeePercent returns the visiting fee percent (for cross-native access).
func (f *Federation) GetVisitingFeePercent(d *dao.Simple) uint8 {
return f.getFeePercentInternal(d)
}
// IsVisitor checks if an address is a registered visitor (for cross-native access).
func (f *Federation) IsVisitor(d *dao.Simple, owner util.Uint160) bool {
_, exists := f.getVisitorChainInternal(d, owner)
return exists
}
// GetHomeChain returns the home chain ID for a visitor (for cross-native access).
func (f *Federation) GetHomeChain(d *dao.Simple, owner util.Uint160) uint32 {
chainID, _ := f.getVisitorChainInternal(d, owner)
return chainID
}
// AddInterChainDebt adds to the inter-chain debt for a specific chain (for cross-native access).
func (f *Federation) AddInterChainDebt(d *dao.Simple, chainID uint32, amount *big.Int) {
f.addDebtInternal(d, chainID, amount)
}
// GetInterChainDebt returns the inter-chain debt for a specific chain (for cross-native access).
func (f *Federation) GetInterChainDebt(d *dao.Simple, chainID uint32) *big.Int {
return f.getDebtInternal(d, chainID)
}
// Address returns the contract's script hash.
func (f *Federation) Address() util.Uint160 {
return f.Hash
}