723 lines
24 KiB
Go
723 lines
24 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
|
|
Tutus ITutus
|
|
}
|
|
|
|
// 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)
|
|
prefixAsylumRegistry byte = 0x04 // owner (Uint160) -> AsylumRecord
|
|
prefixNaturalizedCitizen byte = 0x05 // owner (Uint160) -> NaturalizationRecord
|
|
)
|
|
|
|
// 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"
|
|
AsylumGrantedEvent = "AsylumGranted"
|
|
AsylumRevokedEvent = "AsylumRevoked"
|
|
CitizenNaturalizedEvent = "CitizenNaturalized"
|
|
)
|
|
|
|
// 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")
|
|
ErrAsylumAlreadyGranted = errors.New("asylum already granted")
|
|
ErrAsylumNotFound = errors.New("asylum not found")
|
|
ErrAlreadyNaturalized = errors.New("already naturalized")
|
|
ErrNotNaturalized = errors.New("not naturalized")
|
|
)
|
|
|
|
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)
|
|
|
|
// grantAsylum method (committee only)
|
|
desc = NewDescriptor("grantAsylum", smartcontract.BoolType,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type),
|
|
manifest.NewParameter("homeChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("reason", smartcontract.StringType))
|
|
md = NewMethodAndPrice(f.grantAsylum, 1<<16, callflag.States|callflag.AllowNotify)
|
|
f.AddMethod(md, desc)
|
|
|
|
// revokeAsylum method (committee only)
|
|
desc = NewDescriptor("revokeAsylum", smartcontract.BoolType,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(f.revokeAsylum, 1<<16, callflag.States|callflag.AllowNotify)
|
|
f.AddMethod(md, desc)
|
|
|
|
// hasAsylum method
|
|
desc = NewDescriptor("hasAsylum", smartcontract.BoolType,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(f.hasAsylum, 1<<15, callflag.ReadStates)
|
|
f.AddMethod(md, desc)
|
|
|
|
// getAsylumInfo method
|
|
desc = NewDescriptor("getAsylumInfo", smartcontract.ArrayType,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(f.getAsylumInfo, 1<<15, callflag.ReadStates)
|
|
f.AddMethod(md, desc)
|
|
|
|
// naturalize method (committee only)
|
|
desc = NewDescriptor("naturalize", smartcontract.BoolType,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type),
|
|
manifest.NewParameter("originalHomeChain", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(f.naturalize, 1<<16, callflag.States|callflag.AllowNotify)
|
|
f.AddMethod(md, desc)
|
|
|
|
// isNaturalizedCitizen method
|
|
desc = NewDescriptor("isNaturalizedCitizen", smartcontract.BoolType,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(f.isNaturalizedCitizen, 1<<15, callflag.ReadStates)
|
|
f.AddMethod(md, desc)
|
|
|
|
// getNaturalizationInfo method
|
|
desc = NewDescriptor("getNaturalizationInfo", smartcontract.ArrayType,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(f.getNaturalizationInfo, 1<<15, callflag.ReadStates)
|
|
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))
|
|
|
|
eDesc = NewEventDescriptor(AsylumGrantedEvent,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type),
|
|
manifest.NewParameter("homeChainID", smartcontract.IntegerType),
|
|
manifest.NewParameter("reason", smartcontract.StringType))
|
|
f.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(AsylumRevokedEvent,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type))
|
|
f.AddEvent(NewEvent(eDesc))
|
|
|
|
eDesc = NewEventDescriptor(CitizenNaturalizedEvent,
|
|
manifest.NewParameter("owner", smartcontract.Hash160Type),
|
|
manifest.NewParameter("originalHomeChain", 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
|
|
}
|
|
|
|
func makeAsylumKey(owner util.Uint160) []byte {
|
|
key := make([]byte, 1+util.Uint160Size)
|
|
key[0] = prefixAsylumRegistry
|
|
copy(key[1:], owner.BytesBE())
|
|
return key
|
|
}
|
|
|
|
func makeNaturalizedKey(owner util.Uint160) []byte {
|
|
key := make([]byte, 1+util.Uint160Size)
|
|
key[0] = prefixNaturalizedCitizen
|
|
copy(key[1:], owner.BytesBE())
|
|
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)
|
|
}
|
|
|
|
// Asylum record storage format: homeChainID (4 bytes) + grantedAt (4 bytes) + reason (variable)
|
|
func (f *Federation) getAsylumInternal(d *dao.Simple, owner util.Uint160) (homeChainID uint32, grantedAt uint32, reason string, exists bool) {
|
|
si := d.GetStorageItem(f.ID, makeAsylumKey(owner))
|
|
if si == nil || len(si) < 8 {
|
|
return 0, 0, "", false
|
|
}
|
|
homeChainID = binary.BigEndian.Uint32(si[0:4])
|
|
grantedAt = binary.BigEndian.Uint32(si[4:8])
|
|
if len(si) > 8 {
|
|
reason = string(si[8:])
|
|
}
|
|
return homeChainID, grantedAt, reason, true
|
|
}
|
|
|
|
func (f *Federation) setAsylumInternal(d *dao.Simple, owner util.Uint160, homeChainID uint32, grantedAt uint32, reason string) {
|
|
buf := make([]byte, 8+len(reason))
|
|
binary.BigEndian.PutUint32(buf[0:4], homeChainID)
|
|
binary.BigEndian.PutUint32(buf[4:8], grantedAt)
|
|
copy(buf[8:], reason)
|
|
d.PutStorageItem(f.ID, makeAsylumKey(owner), buf)
|
|
}
|
|
|
|
func (f *Federation) deleteAsylumInternal(d *dao.Simple, owner util.Uint160) {
|
|
d.DeleteStorageItem(f.ID, makeAsylumKey(owner))
|
|
}
|
|
|
|
func (f *Federation) hasAsylumInternal(d *dao.Simple, owner util.Uint160) bool {
|
|
si := d.GetStorageItem(f.ID, makeAsylumKey(owner))
|
|
return si != nil
|
|
}
|
|
|
|
// Naturalization record storage format: originalHomeChain (4 bytes) + naturalizedAt (4 bytes)
|
|
func (f *Federation) getNaturalizedInternal(d *dao.Simple, owner util.Uint160) (originalHomeChain uint32, naturalizedAt uint32, exists bool) {
|
|
si := d.GetStorageItem(f.ID, makeNaturalizedKey(owner))
|
|
if si == nil || len(si) < 8 {
|
|
return 0, 0, false
|
|
}
|
|
originalHomeChain = binary.BigEndian.Uint32(si[0:4])
|
|
naturalizedAt = binary.BigEndian.Uint32(si[4:8])
|
|
return originalHomeChain, naturalizedAt, true
|
|
}
|
|
|
|
func (f *Federation) setNaturalizedInternal(d *dao.Simple, owner util.Uint160, originalHomeChain uint32, naturalizedAt uint32) {
|
|
buf := make([]byte, 8)
|
|
binary.BigEndian.PutUint32(buf[0:4], originalHomeChain)
|
|
binary.BigEndian.PutUint32(buf[4:8], naturalizedAt)
|
|
d.PutStorageItem(f.ID, makeNaturalizedKey(owner), buf)
|
|
}
|
|
|
|
func (f *Federation) isNaturalizedInternal(d *dao.Simple, owner util.Uint160) bool {
|
|
si := d.GetStorageItem(f.ID, makeNaturalizedKey(owner))
|
|
return si != nil
|
|
}
|
|
|
|
// 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.Tutus.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.Tutus.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.Tutus.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.Tutus.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)
|
|
}
|
|
|
|
// Asylum methods
|
|
|
|
func (f *Federation) grantAsylum(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
owner := toUint160(args[0])
|
|
homeChainID := uint32(toBigInt(args[1]).Int64())
|
|
reason := toString(args[2])
|
|
|
|
// Validate chain ID
|
|
if homeChainID == 0 {
|
|
panic(ErrInvalidChainID)
|
|
}
|
|
|
|
// Check committee
|
|
if !f.Tutus.CheckCommittee(ic) {
|
|
panic(ErrNotCommittee)
|
|
}
|
|
|
|
// Check if already has asylum
|
|
if f.hasAsylumInternal(ic.DAO, owner) {
|
|
panic(ErrAsylumAlreadyGranted)
|
|
}
|
|
|
|
// Grant asylum
|
|
f.setAsylumInternal(ic.DAO, owner, homeChainID, ic.BlockHeight(), reason)
|
|
|
|
// Emit event
|
|
err := ic.AddNotification(f.Hash, AsylumGrantedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(owner.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(homeChainID))),
|
|
stackitem.NewByteArray([]byte(reason)),
|
|
}))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (f *Federation) revokeAsylum(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
owner := toUint160(args[0])
|
|
|
|
// Check committee
|
|
if !f.Tutus.CheckCommittee(ic) {
|
|
panic(ErrNotCommittee)
|
|
}
|
|
|
|
// Check if has asylum
|
|
if !f.hasAsylumInternal(ic.DAO, owner) {
|
|
panic(ErrAsylumNotFound)
|
|
}
|
|
|
|
// Revoke asylum
|
|
f.deleteAsylumInternal(ic.DAO, owner)
|
|
|
|
// Emit event
|
|
err := ic.AddNotification(f.Hash, AsylumRevokedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(owner.BytesBE()),
|
|
}))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (f *Federation) hasAsylum(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
owner := toUint160(args[0])
|
|
return stackitem.NewBool(f.hasAsylumInternal(ic.DAO, owner))
|
|
}
|
|
|
|
func (f *Federation) getAsylumInfo(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
owner := toUint160(args[0])
|
|
homeChainID, grantedAt, reason, exists := f.getAsylumInternal(ic.DAO, owner)
|
|
if !exists {
|
|
return stackitem.Null{}
|
|
}
|
|
return stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(homeChainID))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(grantedAt))),
|
|
stackitem.NewByteArray([]byte(reason)),
|
|
})
|
|
}
|
|
|
|
// Naturalization methods
|
|
|
|
func (f *Federation) naturalize(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
owner := toUint160(args[0])
|
|
originalHomeChain := uint32(toBigInt(args[1]).Int64())
|
|
|
|
// Check committee
|
|
if !f.Tutus.CheckCommittee(ic) {
|
|
panic(ErrNotCommittee)
|
|
}
|
|
|
|
// Check if already naturalized
|
|
if f.isNaturalizedInternal(ic.DAO, owner) {
|
|
panic(ErrAlreadyNaturalized)
|
|
}
|
|
|
|
// Naturalize the citizen
|
|
f.setNaturalizedInternal(ic.DAO, owner, originalHomeChain, ic.BlockHeight())
|
|
|
|
// Emit event
|
|
err := ic.AddNotification(f.Hash, CitizenNaturalizedEvent, stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(owner.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(originalHomeChain))),
|
|
}))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (f *Federation) isNaturalizedCitizen(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
owner := toUint160(args[0])
|
|
return stackitem.NewBool(f.isNaturalizedInternal(ic.DAO, owner))
|
|
}
|
|
|
|
func (f *Federation) getNaturalizationInfo(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
owner := toUint160(args[0])
|
|
originalHomeChain, naturalizedAt, exists := f.getNaturalizedInternal(ic.DAO, owner)
|
|
if !exists {
|
|
return stackitem.Null{}
|
|
}
|
|
return stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(originalHomeChain))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(naturalizedAt))),
|
|
})
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// HasAsylum checks if an address has asylum status (for cross-native access).
|
|
func (f *Federation) HasAsylum(d *dao.Simple, owner util.Uint160) bool {
|
|
return f.hasAsylumInternal(d, owner)
|
|
}
|
|
|
|
// IsNaturalizedCitizen checks if an address is a naturalized citizen (for cross-native access).
|
|
func (f *Federation) IsNaturalizedCitizen(d *dao.Simple, owner util.Uint160) bool {
|
|
return f.isNaturalizedInternal(d, owner)
|
|
}
|