Add asylum registry and naturalization for humanitarian protection
Extend Federation contract to protect citizens when their home government
becomes hostile:
- Add Asylum Registry for refugee protection
- grantAsylum/revokeAsylum methods (committee only)
- hasAsylum/getAsylumInfo query methods
- AsylumGranted/AsylumRevoked events
- Asylum seekers get 100% local Treasury funding with NO inter-chain
debt to avoid funding hostile governments
- Add Naturalization (Vita Transfer) for permanent immigration
- naturalize method for permanent citizenship transfer
- isNaturalizedCitizen/getNaturalizationInfo query methods
- CitizenNaturalized event
- Naturalized citizens treated same as local Vita holders
- Add VitaExemptAsylum type for humanitarian override
- Fee exemption priority: Local Vita > Naturalized > Visitor > Asylum
- Asylum: 100% from local Treasury, zero inter-chain debt
- Protects refugees even if home chain revokes their Vita
- Update IsVitaFeeExempt() to check all exemption types
- Local Vita, naturalized citizens, visitors, and asylum seekers
The one-Vita-per-person rule remains inviolable - Vita is tied only to
a living person globally. This enables protection without duplication.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f03564d676
commit
5d410f9ba0
|
|
@ -2458,7 +2458,7 @@ func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVitaFeeExempt returns true if the account has an active Vita token
|
// IsVitaFeeExempt returns true if the account has an active Vita token
|
||||||
// (local or visiting) and is exempt from paying transaction fees.
|
// (local, naturalized, visiting, or asylum) and is exempt from paying transaction fees.
|
||||||
// This implements the Feer interface.
|
// This implements the Feer interface.
|
||||||
func (bc *Blockchain) IsVitaFeeExempt(acc util.Uint160) bool {
|
func (bc *Blockchain) IsVitaFeeExempt(acc util.Uint160) bool {
|
||||||
// Check local Vita first
|
// Check local Vita first
|
||||||
|
|
@ -2468,11 +2468,21 @@ func (bc *Blockchain) IsVitaFeeExempt(acc util.Uint160) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check visiting Vita registry
|
|
||||||
|
// Check federation-based exemptions
|
||||||
if bc.federation != nil {
|
if bc.federation != nil {
|
||||||
|
// Naturalized citizens are exempt (treated same as local)
|
||||||
|
if bc.federation.IsNaturalizedCitizen(bc.dao, acc) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Visitors are exempt
|
||||||
if bc.federation.IsVisitor(bc.dao, acc) {
|
if bc.federation.IsVisitor(bc.dao, acc) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
// Asylum seekers are exempt (humanitarian override)
|
||||||
|
if bc.federation.HasAsylum(bc.dao, acc) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,10 @@ type (
|
||||||
GetInterChainDebt(d *dao.Simple, chainID uint32) *big.Int
|
GetInterChainDebt(d *dao.Simple, chainID uint32) *big.Int
|
||||||
// Address returns the contract's script hash.
|
// Address returns the contract's script hash.
|
||||||
Address() util.Uint160
|
Address() util.Uint160
|
||||||
|
// HasAsylum checks if an address has asylum status (humanitarian override).
|
||||||
|
HasAsylum(d *dao.Simple, owner util.Uint160) bool
|
||||||
|
// IsNaturalizedCitizen checks if an address is a naturalized citizen (permanent immigration).
|
||||||
|
IsNaturalizedCitizen(d *dao.Simple, owner util.Uint160) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ITreasury is an interface required from native Treasury contract for
|
// ITreasury is an interface required from native Treasury contract for
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ const (
|
||||||
prefixVisitingFeePercent byte = 0x01 // -> uint8 (0-100, % host chain pays for visitors)
|
prefixVisitingFeePercent byte = 0x01 // -> uint8 (0-100, % host chain pays for visitors)
|
||||||
prefixVisitorRegistry byte = 0x02 // owner (Uint160) -> home chain ID (uint32)
|
prefixVisitorRegistry byte = 0x02 // owner (Uint160) -> home chain ID (uint32)
|
||||||
prefixInterChainDebt byte = 0x03 // chain ID (uint32) -> *big.Int (amount owed)
|
prefixInterChainDebt byte = 0x03 // chain ID (uint32) -> *big.Int (amount owed)
|
||||||
|
prefixAsylumRegistry byte = 0x04 // owner (Uint160) -> AsylumRecord
|
||||||
|
prefixNaturalizedCitizen byte = 0x05 // owner (Uint160) -> NaturalizationRecord
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default values.
|
// Default values.
|
||||||
|
|
@ -45,6 +47,9 @@ const (
|
||||||
VisitorUnregisteredEvent = "VisitorUnregistered"
|
VisitorUnregisteredEvent = "VisitorUnregistered"
|
||||||
FeePercentChangedEvent = "FeePercentChanged"
|
FeePercentChangedEvent = "FeePercentChanged"
|
||||||
DebtSettledEvent = "DebtSettled"
|
DebtSettledEvent = "DebtSettled"
|
||||||
|
AsylumGrantedEvent = "AsylumGranted"
|
||||||
|
AsylumRevokedEvent = "AsylumRevoked"
|
||||||
|
CitizenNaturalizedEvent = "CitizenNaturalized"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Various errors.
|
// Various errors.
|
||||||
|
|
@ -54,6 +59,10 @@ var (
|
||||||
ErrVisitorNotFound = errors.New("visitor not found")
|
ErrVisitorNotFound = errors.New("visitor not found")
|
||||||
ErrInvalidChainID = errors.New("invalid chain ID")
|
ErrInvalidChainID = errors.New("invalid chain ID")
|
||||||
ErrInsufficientDebt = errors.New("insufficient debt to settle")
|
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)
|
var _ interop.Contract = (*Federation)(nil)
|
||||||
|
|
@ -114,6 +123,51 @@ func newFederation() *Federation {
|
||||||
md = NewMethodAndPrice(f.settleDebt, 1<<16, callflag.States|callflag.AllowNotify)
|
md = NewMethodAndPrice(f.settleDebt, 1<<16, callflag.States|callflag.AllowNotify)
|
||||||
f.AddMethod(md, desc)
|
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
|
// Events
|
||||||
eDesc := NewEventDescriptor(VisitorRegisteredEvent,
|
eDesc := NewEventDescriptor(VisitorRegisteredEvent,
|
||||||
manifest.NewParameter("owner", smartcontract.Hash160Type),
|
manifest.NewParameter("owner", smartcontract.Hash160Type),
|
||||||
|
|
@ -135,6 +189,21 @@ func newFederation() *Federation {
|
||||||
manifest.NewParameter("remaining", smartcontract.IntegerType))
|
manifest.NewParameter("remaining", smartcontract.IntegerType))
|
||||||
f.AddEvent(NewEvent(eDesc))
|
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
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,6 +265,20 @@ func makeDebtKey(chainID uint32) []byte {
|
||||||
return key
|
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
|
// Internal storage methods
|
||||||
|
|
||||||
func (f *Federation) getFeePercentInternal(d *dao.Simple) uint8 {
|
func (f *Federation) getFeePercentInternal(d *dao.Simple) uint8 {
|
||||||
|
|
@ -250,6 +333,60 @@ func (f *Federation) addDebtInternal(d *dao.Simple, chainID uint32, amount *big.
|
||||||
f.setDebtInternal(d, chainID, newDebt)
|
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
|
// Contract methods
|
||||||
|
|
||||||
func (f *Federation) getFeePercent(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (f *Federation) getFeePercent(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
|
@ -409,6 +546,137 @@ func (f *Federation) settleDebt(ic *interop.Context, args []stackitem.Item) stac
|
||||||
return stackitem.NewBool(true)
|
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.NEO.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.NEO.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.NEO.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
|
// Public methods for cross-native access
|
||||||
|
|
||||||
// GetVisitingFeePercent returns the visiting fee percent (for cross-native access).
|
// GetVisitingFeePercent returns the visiting fee percent (for cross-native access).
|
||||||
|
|
@ -442,3 +710,13 @@ func (f *Federation) GetInterChainDebt(d *dao.Simple, chainID uint32) *big.Int {
|
||||||
func (f *Federation) Address() util.Uint160 {
|
func (f *Federation) Address() util.Uint160 {
|
||||||
return f.Hash
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@ const (
|
||||||
VitaExemptLocal
|
VitaExemptLocal
|
||||||
// VitaExemptVisitor indicates a visiting Vita holder (split between local and home chain).
|
// VitaExemptVisitor indicates a visiting Vita holder (split between local and home chain).
|
||||||
VitaExemptVisitor
|
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.
|
// GASFactor is a divisor for finding GAS integral value.
|
||||||
|
|
@ -143,6 +146,10 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
|
||||||
homeChain = g.Federation.GetHomeChain(ic.DAO, sender)
|
homeChain = g.Federation.GetHomeChain(ic.DAO, sender)
|
||||||
}
|
}
|
||||||
g.burnFromTreasuryWithSplit(ic, absAmount, homeChain)
|
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:
|
default:
|
||||||
// Non-citizen: burn from sender
|
// Non-citizen: burn from sender
|
||||||
g.Burn(ic, sender, absAmount)
|
g.Burn(ic, sender, absAmount)
|
||||||
|
|
@ -179,10 +186,23 @@ func (g *GAS) getVitaExemptType(d *dao.Simple, sender util.Uint160) VitaExemptTy
|
||||||
return VitaExemptLocal
|
return VitaExemptLocal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check naturalized citizen (treated same as local)
|
||||||
|
if g.Federation != nil && g.Federation.IsNaturalizedCitizen(d, sender) {
|
||||||
|
return VitaExemptLocal
|
||||||
|
}
|
||||||
|
|
||||||
// Check visitor registry
|
// Check visitor registry
|
||||||
if g.Federation != nil && g.Federation.IsVisitor(d, sender) {
|
if g.Federation != nil && g.Federation.IsVisitor(d, sender) {
|
||||||
return VitaExemptVisitor
|
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
|
return VitaExemptNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue