package native import ( "encoding/binary" "errors" "math/big" "git.marketally.com/tutus-one/tutus-chain/pkg/config" "git.marketally.com/tutus-one/tutus-chain/pkg/core/dao" "git.marketally.com/tutus-one/tutus-chain/pkg/core/interop" "git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativeids" "git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativenames" "git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract" "git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/callflag" "git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/manifest" "git.marketally.com/tutus-one/tutus-chain/pkg/util" "git.marketally.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) }