From f03564d67618af41fcdc97b245c12c9113e698ca Mon Sep 17 00:00:00 2001 From: Tutus Development Date: Sat, 20 Dec 2025 04:27:55 +0000 Subject: [PATCH] Add free GAS for Vita holders with cross-chain fee splitting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename PersonToken to Vita (soul-bound identity token) and implement fee exemption system for citizens: - Add Federation native contract for cross-chain Vita coordination - Visitor registry for Vita holders from other chains - Configurable visiting fee percentage (default 50%) - Inter-chain debt tracking for settlement between chains - Modify GAS contract to burn fees from Treasury for Vita holders - Local Vita: 100% paid by local Treasury - Visiting Vita: Split between local Treasury and inter-chain debt - Deficit tracking when Treasury is underfunded - Update mempool and blockchain to skip fee checks for Vita exempt users - Add IsVitaFeeExempt() to Feer interface - Modify verifyAndPoolTx() to allow zero-fee transactions - Rename PersonToken -> Vita across codebase - Update state, native contract, and tests - Maintain same functionality with clearer naming 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- pkg/core/blockchain.go | 38 +- pkg/core/mempool/feer.go | 3 + pkg/core/mempool/mem_pool.go | 7 + pkg/core/mempool/mem_pool_test.go | 4 + pkg/core/native/contract.go | 79 ++- pkg/core/native/federation.go | 444 +++++++++++++ pkg/core/native/native_gas.go | 101 ++- .../native/native_test/management_test.go | 4 +- .../{person_token_test.go => vita_test.go} | 88 +-- pkg/core/native/native_test/vts_test.go | 30 +- pkg/core/native/nativehashes/hashes.go | 6 +- pkg/core/native/nativeids/ids.go | 6 +- pkg/core/native/nativenames/names.go | 11 +- pkg/core/native/role_registry.go | 6 +- pkg/core/native/treasury.go | 41 ++ pkg/core/native/{person_token.go => vita.go} | 616 +++++++++--------- pkg/core/native/vts.go | 12 +- pkg/core/state/role_registry.go | 6 +- pkg/core/state/{person_token.go => vita.go} | 12 +- pkg/network/notary_feer.go | 6 + 20 files changed, 1108 insertions(+), 412 deletions(-) create mode 100644 pkg/core/native/federation.go rename pkg/core/native/native_test/{person_token_test.go => vita_test.go} (74%) rename pkg/core/native/{person_token.go => vita.go} (69%) rename pkg/core/state/{person_token.go => vita.go} (94%) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 415b925..9c30a97 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -218,6 +218,9 @@ type Blockchain struct { designate native.IDesignate oracle native.IOracle notary native.INotary + vita native.IVita + federation native.IFederation + treasury native.ITreasury extensible atomic.Value @@ -465,6 +468,18 @@ func NewBlockchain(s storage.Store, cfg config.Blockchain, log *zap.Logger, newN return nil, err } } + bc.vita = bc.contracts.Vita() + if err := validateNative(bc.vita, nativeids.Vita, nativenames.Vita, nativehashes.Vita); err != nil { + return nil, err + } + bc.federation = bc.contracts.Federation() + if err := validateNative(bc.federation, nativeids.Federation, nativenames.Federation, nativehashes.Federation); err != nil { + return nil, err + } + bc.treasury = bc.contracts.Treasury() + if err := validateNative(bc.treasury, nativeids.Treasury, nativenames.Treasury, nativehashes.Treasury); err != nil { + return nil, err + } bc.persistCond = sync.NewCond(&bc.lock) bc.gcBlockTimes, _ = lru.New[uint32, uint64](defaultBlockTimesCache) // Never errors for positive size @@ -2442,6 +2457,26 @@ func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) *big.Int { return bs } +// IsVitaFeeExempt returns true if the account has an active Vita token +// (local or visiting) and is exempt from paying transaction fees. +// This implements the Feer interface. +func (bc *Blockchain) IsVitaFeeExempt(acc util.Uint160) bool { + // Check local Vita first + if bc.vita != nil { + token, err := bc.vita.GetTokenByOwner(bc.dao, acc) + if err == nil && token != nil && token.Status == state.TokenStatusActive { + return true + } + } + // Check visiting Vita registry + if bc.federation != nil { + if bc.federation.IsVisitor(bc.dao, acc) { + return true + } + } + return false +} + // GetGoverningTokenBalance returns governing token (NEO) balance and the height // of the last balance change for the account. func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) { @@ -2946,7 +2981,8 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool. } needNetworkFee := int64(size)*bc.FeePerByte() + bc.CalculateAttributesFee(t) netFee := t.NetworkFee - needNetworkFee - if netFee < 0 { + // Skip fee check for Vita holders (local or visiting) - their fees are paid by Treasury + if netFee < 0 && !bc.IsVitaFeeExempt(t.Sender()) { return fmt.Errorf("%w: net fee is %v, need %v", ErrTxSmallNetworkFee, t.NetworkFee, needNetworkFee) } // check that current tx wasn't included in the conflicts attributes of some other transaction which is already in the chain diff --git a/pkg/core/mempool/feer.go b/pkg/core/mempool/feer.go index 900f3cc..f79788f 100644 --- a/pkg/core/mempool/feer.go +++ b/pkg/core/mempool/feer.go @@ -11,4 +11,7 @@ type Feer interface { FeePerByte() int64 GetUtilityTokenBalance(util.Uint160) *big.Int BlockHeight() uint32 + // IsVitaFeeExempt returns true if the account has an active Vita token + // (local or visiting) and is exempt from paying transaction fees. + IsVitaFeeExempt(util.Uint160) bool } diff --git a/pkg/core/mempool/mem_pool.go b/pkg/core/mempool/mem_pool.go index 37a84d9..58e7d8e 100644 --- a/pkg/core/mempool/mem_pool.go +++ b/pkg/core/mempool/mem_pool.go @@ -164,6 +164,13 @@ func (mp *Pool) HasConflicts(t *transaction.Transaction, fee Feer) bool { // and returns false if both balance check is required and the sender does not have enough GAS to pay. func (mp *Pool) tryAddSendersFee(tx *transaction.Transaction, feer Feer, needCheck bool) bool { payer := tx.Signers[mp.payerIndex].Account + + // Skip balance check for Vita fee exempt users (local or visiting Vita holders). + // Their fees are paid by the Treasury. + if feer.IsVitaFeeExempt(payer) { + return true + } + senderFee, ok := mp.fees[payer] if !ok { _ = senderFee.balance.SetFromBig(feer.GetUtilityTokenBalance(payer)) diff --git a/pkg/core/mempool/mem_pool_test.go b/pkg/core/mempool/mem_pool_test.go index 909391c..586787b 100644 --- a/pkg/core/mempool/mem_pool_test.go +++ b/pkg/core/mempool/mem_pool_test.go @@ -40,6 +40,10 @@ func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int { return big.NewInt(fs.balance) } +func (fs *FeerStub) IsVitaFeeExempt(util.Uint160) bool { + return false +} + func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) { mp := New(10, 0, false, nil) tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 14e0a93..9c185e0 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -111,12 +111,12 @@ type ( GetMaxNotValidBeforeDelta(dao *dao.Simple) uint32 } - // IPersonToken is an interface required from native PersonToken contract + // IVita is an interface required from native Vita contract // for interaction with Blockchain and other native contracts. - IPersonToken interface { + IVita interface { interop.Contract - GetTokenByOwner(d *dao.Simple, owner util.Uint160) (*state.PersonToken, error) - GetTokenByIDPublic(d *dao.Simple, tokenID uint64) (*state.PersonToken, error) + GetTokenByOwner(d *dao.Simple, owner util.Uint160) (*state.Vita, error) + GetTokenByIDPublic(d *dao.Simple, tokenID uint64) (*state.Vita, error) TokenExists(d *dao.Simple, owner util.Uint160) bool GetAttribute(d *dao.Simple, tokenID uint64, key string) (*state.Attribute, error) // IsAdultVerified checks if the owner has a verified "age_verified" attribute @@ -156,6 +156,37 @@ type ( // IsVendor returns true if address is a registered active vendor. IsVendor(d *dao.Simple, addr util.Uint160) bool } + + // IFederation is an interface required from native Federation contract for + // interaction with Blockchain and other native contracts. + // Federation manages cross-chain Vita coordination and visiting fee governance. + IFederation interface { + interop.Contract + // GetVisitingFeePercent returns the percentage of fees the host chain pays for visitors. + GetVisitingFeePercent(d *dao.Simple) uint8 + // IsVisitor checks if an address is a registered visitor from another chain. + IsVisitor(d *dao.Simple, owner util.Uint160) bool + // GetHomeChain returns the home chain ID for a visitor. + GetHomeChain(d *dao.Simple, owner util.Uint160) uint32 + // AddInterChainDebt adds to the inter-chain debt owed to a specific chain. + AddInterChainDebt(d *dao.Simple, chainID uint32, amount *big.Int) + // GetInterChainDebt returns the inter-chain debt owed to a specific chain. + GetInterChainDebt(d *dao.Simple, chainID uint32) *big.Int + // Address returns the contract's script hash. + Address() util.Uint160 + } + + // ITreasury is an interface required from native Treasury contract for + // interaction with Blockchain and other native contracts. + ITreasury interface { + interop.Contract + // Address returns the contract's script hash. + Address() util.Uint160 + // AddDeficit adds to the cumulative deficit tracking. + AddDeficit(d *dao.Simple, amount *big.Int) + // GetDeficit returns the current cumulative deficit. + GetDeficit(d *dao.Simple) *big.Int + } ) // Contracts is a convenient wrapper around an arbitrary set of native contracts @@ -271,10 +302,10 @@ func (cs *Contracts) Notary() INotary { return nil } -// PersonToken returns native IPersonToken contract implementation. It panics if +// Vita returns native IVita contract implementation. It panics if // there's no contract with proper name in cs. -func (cs *Contracts) PersonToken() IPersonToken { - return cs.ByName(nativenames.PersonToken).(IPersonToken) +func (cs *Contracts) Vita() IVita { + return cs.ByName(nativenames.Vita).(IVita) } // RoleRegistry returns native IRoleRegistry contract implementation. It panics if @@ -289,6 +320,18 @@ func (cs *Contracts) VTS() IVTS { return cs.ByName(nativenames.VTS).(IVTS) } +// Federation returns native IFederation contract implementation. It panics if +// there's no contract with proper name in cs. +func (cs *Contracts) Federation() IFederation { + return cs.ByName(nativenames.Federation).(IFederation) +} + +// Treasury returns native ITreasury contract implementation. It panics if +// there's no contract with proper name in cs. +func (cs *Contracts) Treasury() ITreasury { + return cs.ByName(nativenames.Treasury).(ITreasury) +} + // NewDefaultContracts returns a new set of default native contracts. func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { mgmt := NewManagement() @@ -325,8 +368,8 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { treasury := newTreasury() treasury.NEO = neo - personToken := newPersonToken() - personToken.NEO = neo + vita := newVita() + vita.NEO = neo // Parse TutusCommittee addresses from config var tutusCommittee []util.Uint160 @@ -344,14 +387,23 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { roleRegistry := newRoleRegistry(tutusCommittee) roleRegistry.NEO = neo - // Set RoleRegistry on PersonToken for cross-contract integration - personToken.RoleRegistry = roleRegistry + // Set RoleRegistry on Vita for cross-contract integration + vita.RoleRegistry = roleRegistry // Create VTS (Value Transfer System) contract vts := newVTS() vts.NEO = neo vts.RoleRegistry = roleRegistry - vts.PersonToken = personToken + vts.Vita = vita + + // Create Federation contract for cross-chain Vita coordination + federation := newFederation() + federation.NEO = neo + + // Wire GAS dependencies for Vita fee exemption + gas.Vita = vita + gas.Federation = federation + gas.Treasury = treasury return []interop.Contract{ mgmt, @@ -365,8 +417,9 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { oracle, notary, treasury, - personToken, + vita, roleRegistry, vts, + federation, } } diff --git a/pkg/core/native/federation.go b/pkg/core/native/federation.go new file mode 100644 index 0000000..cf92a8d --- /dev/null +++ b/pkg/core/native/federation.go @@ -0,0 +1,444 @@ +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 +} diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 666b4db..909054e 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -20,12 +20,27 @@ import ( // GAS represents GAS native contract. type GAS struct { nep17TokenNative - NEO INEO - Policy IPolicy + 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 +) + // GASFactor is a divisor for finding GAS integral value. const GASFactor = NEOTotalSupply @@ -112,7 +127,26 @@ func (g *GAS) OnPersist(ic *interop.Context) error { } for _, tx := range ic.Block.Transactions { absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee) - g.Burn(ic, tx.Sender(), absAmount) + 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) + default: + // Non-citizen: burn from sender + g.Burn(ic, sender, absAmount) + } } validators := g.NEO.GetNextBlockValidatorsInternal(ic.DAO) primary := validators[ic.Block.PrimaryIndex].GetScriptHash() @@ -136,6 +170,67 @@ 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 visitor registry + if g.Federation != nil && g.Federation.IsVisitor(d, sender) { + return VitaExemptVisitor + } + 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 diff --git a/pkg/core/native/native_test/management_test.go b/pkg/core/native/native_test/management_test.go index dad85e9..fb51d4e 100644 --- a/pkg/core/native/native_test/management_test.go +++ b/pkg/core/native/native_test/management_test.go @@ -51,7 +51,7 @@ var ( nativenames.Policy: `{"id":-7,"hash":"0xcc5e4edd9f5f8dba8bb65734541df7a1c081c67b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"PolicyContract","abi":{"methods":[{"name":"blockAccount","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"getAttributeFee","offset":7,"parameters":[{"name":"attributeType","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"getExecFeeFactor","offset":14,"parameters":[],"returntype":"Integer","safe":true},{"name":"getFeePerByte","offset":21,"parameters":[],"returntype":"Integer","safe":true},{"name":"getStoragePrice","offset":28,"parameters":[],"returntype":"Integer","safe":true},{"name":"isBlocked","offset":35,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"setAttributeFee","offset":42,"parameters":[{"name":"attributeType","type":"Integer"},{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setExecFeeFactor","offset":49,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setFeePerByte","offset":56,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setStoragePrice","offset":63,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"unblockAccount","offset":70,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.Designation: `{"id":-8,"hash":"0x49cf4e5378ffcd4dec034fd98a174c5491e395e2","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0A=","checksum":983638438},"manifest":{"name":"RoleManagement","abi":{"methods":[{"name":"designateAsRole","offset":0,"parameters":[{"name":"role","type":"Integer"},{"name":"nodes","type":"Array"}],"returntype":"Void","safe":false},{"name":"getDesignatedByRole","offset":7,"parameters":[{"name":"role","type":"Integer"},{"name":"index","type":"Integer"}],"returntype":"Array","safe":true}],"events":[{"name":"Designation","parameters":[{"name":"Role","type":"Integer"},{"name":"BlockIndex","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.Oracle: `{"id":-9,"hash":"0xfe924b7cfe89ddd271abaf7210a80a7e11178758","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2663858513},"manifest":{"name":"OracleContract","abi":{"methods":[{"name":"finish","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"getPrice","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"request","offset":14,"parameters":[{"name":"url","type":"String"},{"name":"filter","type":"String"},{"name":"callback","type":"String"},{"name":"userData","type":"Any"},{"name":"gasForResponse","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setPrice","offset":21,"parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","safe":false},{"name":"verify","offset":28,"parameters":[],"returntype":"Boolean","safe":true}],"events":[{"name":"OracleRequest","parameters":[{"name":"Id","type":"Integer"},{"name":"RequestContract","type":"Hash160"},{"name":"Url","type":"String"},{"name":"Filter","type":"String"}]},{"name":"OracleResponse","parameters":[{"name":"Id","type":"Integer"},{"name":"OriginalTx","type":"Hash256"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, - nativenames.PersonToken: `{"id":-12,"hash":"0x21a5728a3a261c77c7df30d47aa4dbdef134af04","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":2426471238},"manifest":{"name":"PersonToken","abi":{"methods":[{"name":"approveRecovery","offset":0,"parameters":[{"name":"requestId","type":"ByteArray"}],"returntype":"Boolean","safe":false},{"name":"cancelRecovery","offset":7,"parameters":[{"name":"requestId","type":"ByteArray"}],"returntype":"Boolean","safe":false},{"name":"createChallenge","offset":14,"parameters":[{"name":"owner","type":"Hash160"},{"name":"purpose","type":"String"}],"returntype":"Array","safe":false},{"name":"executeRecovery","offset":21,"parameters":[{"name":"requestId","type":"ByteArray"}],"returntype":"Boolean","safe":false},{"name":"exists","offset":28,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"fulfillChallenge","offset":35,"parameters":[{"name":"challengeId","type":"ByteArray"},{"name":"signature","type":"ByteArray"}],"returntype":"Boolean","safe":false},{"name":"getAttribute","offset":42,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"}],"returntype":"Array","safe":true},{"name":"getChallenge","offset":49,"parameters":[{"name":"challengeId","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"getRecoveryRequest","offset":56,"parameters":[{"name":"requestId","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"getToken","offset":63,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getTokenByID","offset":70,"parameters":[{"name":"tokenId","type":"Integer"}],"returntype":"Array","safe":true},{"name":"initiateRecovery","offset":77,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"newOwner","type":"Hash160"},{"name":"evidence","type":"ByteArray"}],"returntype":"ByteArray","safe":false},{"name":"register","offset":84,"parameters":[{"name":"owner","type":"Hash160"},{"name":"personHash","type":"ByteArray"},{"name":"isEntity","type":"Boolean"},{"name":"recoveryHash","type":"ByteArray"}],"returntype":"ByteArray","safe":false},{"name":"reinstate","offset":91,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"requireCoreRole","offset":98,"parameters":[{"name":"coreRole","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"requirePermission","offset":105,"parameters":[{"name":"resource","type":"String"},{"name":"action","type":"String"},{"name":"scope","type":"String"}],"returntype":"Integer","safe":true},{"name":"requireRole","offset":112,"parameters":[{"name":"roleId","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"revoke","offset":119,"parameters":[{"name":"owner","type":"Hash160"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"revokeAttribute","offset":126,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"setAttribute","offset":133,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"valueHash","type":"ByteArray"},{"name":"valueEnc","type":"ByteArray"},{"name":"expiresAt","type":"Integer"},{"name":"disclosureLevel","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"suspend","offset":140,"parameters":[{"name":"owner","type":"Hash160"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"totalSupply","offset":147,"parameters":[],"returntype":"Integer","safe":true},{"name":"validateCaller","offset":154,"parameters":[],"returntype":"Array","safe":true},{"name":"verifyAttribute","offset":161,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"expectedHash","type":"ByteArray"}],"returntype":"Boolean","safe":true},{"name":"verifyAuth","offset":168,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"purpose","type":"String"},{"name":"maxAge","type":"Integer"}],"returntype":"Boolean","safe":true}],"events":[{"name":"PersonTokenCreated","parameters":[{"name":"tokenId","type":"ByteArray"},{"name":"owner","type":"Hash160"},{"name":"createdAt","type":"Integer"}]},{"name":"PersonTokenSuspended","parameters":[{"name":"tokenId","type":"ByteArray"},{"name":"reason","type":"String"},{"name":"suspendedBy","type":"Hash160"}]},{"name":"PersonTokenReinstated","parameters":[{"name":"tokenId","type":"ByteArray"},{"name":"reinstatedBy","type":"Hash160"}]},{"name":"PersonTokenRevoked","parameters":[{"name":"tokenId","type":"ByteArray"},{"name":"reason","type":"String"},{"name":"revokedBy","type":"Hash160"}]},{"name":"AttributeSet","parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"attestor","type":"Hash160"},{"name":"expiresAt","type":"Integer"}]},{"name":"AttributeRevoked","parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"revokedBy","type":"Hash160"},{"name":"reason","type":"String"}]},{"name":"AuthChallengeCreated","parameters":[{"name":"challengeId","type":"ByteArray"},{"name":"tokenId","type":"Integer"},{"name":"purpose","type":"String"},{"name":"expiresAt","type":"Integer"}]},{"name":"AuthenticationSuccess","parameters":[{"name":"tokenId","type":"Integer"},{"name":"purpose","type":"String"},{"name":"timestamp","type":"Integer"}]},{"name":"RecoveryInitiated","parameters":[{"name":"tokenId","type":"Integer"},{"name":"requestId","type":"ByteArray"},{"name":"delayUntil","type":"Integer"}]},{"name":"RecoveryApproval","parameters":[{"name":"requestId","type":"ByteArray"},{"name":"approver","type":"Hash160"},{"name":"approvalCount","type":"Integer"},{"name":"required","type":"Integer"}]},{"name":"RecoveryExecuted","parameters":[{"name":"tokenId","type":"Integer"},{"name":"oldOwner","type":"Hash160"},{"name":"newOwner","type":"Hash160"}]},{"name":"RecoveryCancelled","parameters":[{"name":"requestId","type":"ByteArray"},{"name":"cancelledBy","type":"Hash160"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, + nativenames.Vita: `{"id":-12,"hash":"0xde437f043fdfb8f9c8241acb82d0791dd4b3217d","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":2426471238},"manifest":{"name":"Vita","abi":{"methods":[{"name":"approveRecovery","offset":0,"parameters":[{"name":"requestId","type":"ByteArray"}],"returntype":"Boolean","safe":false},{"name":"cancelRecovery","offset":7,"parameters":[{"name":"requestId","type":"ByteArray"}],"returntype":"Boolean","safe":false},{"name":"createChallenge","offset":14,"parameters":[{"name":"owner","type":"Hash160"},{"name":"purpose","type":"String"}],"returntype":"Array","safe":false},{"name":"executeRecovery","offset":21,"parameters":[{"name":"requestId","type":"ByteArray"}],"returntype":"Boolean","safe":false},{"name":"exists","offset":28,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"fulfillChallenge","offset":35,"parameters":[{"name":"challengeId","type":"ByteArray"},{"name":"signature","type":"ByteArray"}],"returntype":"Boolean","safe":false},{"name":"getAttribute","offset":42,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"}],"returntype":"Array","safe":true},{"name":"getChallenge","offset":49,"parameters":[{"name":"challengeId","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"getRecoveryRequest","offset":56,"parameters":[{"name":"requestId","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"getToken","offset":63,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getTokenByID","offset":70,"parameters":[{"name":"tokenId","type":"Integer"}],"returntype":"Array","safe":true},{"name":"initiateRecovery","offset":77,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"newOwner","type":"Hash160"},{"name":"evidence","type":"ByteArray"}],"returntype":"ByteArray","safe":false},{"name":"register","offset":84,"parameters":[{"name":"owner","type":"Hash160"},{"name":"personHash","type":"ByteArray"},{"name":"isEntity","type":"Boolean"},{"name":"recoveryHash","type":"ByteArray"}],"returntype":"ByteArray","safe":false},{"name":"reinstate","offset":91,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"requireCoreRole","offset":98,"parameters":[{"name":"coreRole","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"requirePermission","offset":105,"parameters":[{"name":"resource","type":"String"},{"name":"action","type":"String"},{"name":"scope","type":"String"}],"returntype":"Integer","safe":true},{"name":"requireRole","offset":112,"parameters":[{"name":"roleId","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"revoke","offset":119,"parameters":[{"name":"owner","type":"Hash160"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"revokeAttribute","offset":126,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"setAttribute","offset":133,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"valueHash","type":"ByteArray"},{"name":"valueEnc","type":"ByteArray"},{"name":"expiresAt","type":"Integer"},{"name":"disclosureLevel","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"suspend","offset":140,"parameters":[{"name":"owner","type":"Hash160"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"totalSupply","offset":147,"parameters":[],"returntype":"Integer","safe":true},{"name":"validateCaller","offset":154,"parameters":[],"returntype":"Array","safe":true},{"name":"verifyAttribute","offset":161,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"expectedHash","type":"ByteArray"}],"returntype":"Boolean","safe":true},{"name":"verifyAuth","offset":168,"parameters":[{"name":"tokenId","type":"Integer"},{"name":"purpose","type":"String"},{"name":"maxAge","type":"Integer"}],"returntype":"Boolean","safe":true}],"events":[{"name":"VitaCreated","parameters":[{"name":"tokenId","type":"ByteArray"},{"name":"owner","type":"Hash160"},{"name":"createdAt","type":"Integer"}]},{"name":"VitaSuspended","parameters":[{"name":"tokenId","type":"ByteArray"},{"name":"reason","type":"String"},{"name":"suspendedBy","type":"Hash160"}]},{"name":"VitaReinstated","parameters":[{"name":"tokenId","type":"ByteArray"},{"name":"reinstatedBy","type":"Hash160"}]},{"name":"VitaRevoked","parameters":[{"name":"tokenId","type":"ByteArray"},{"name":"reason","type":"String"},{"name":"revokedBy","type":"Hash160"}]},{"name":"AttributeSet","parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"attestor","type":"Hash160"},{"name":"expiresAt","type":"Integer"}]},{"name":"AttributeRevoked","parameters":[{"name":"tokenId","type":"Integer"},{"name":"key","type":"String"},{"name":"revokedBy","type":"Hash160"},{"name":"reason","type":"String"}]},{"name":"AuthChallengeCreated","parameters":[{"name":"challengeId","type":"ByteArray"},{"name":"tokenId","type":"Integer"},{"name":"purpose","type":"String"},{"name":"expiresAt","type":"Integer"}]},{"name":"AuthenticationSuccess","parameters":[{"name":"tokenId","type":"Integer"},{"name":"purpose","type":"String"},{"name":"timestamp","type":"Integer"}]},{"name":"RecoveryInitiated","parameters":[{"name":"tokenId","type":"Integer"},{"name":"requestId","type":"ByteArray"},{"name":"delayUntil","type":"Integer"}]},{"name":"RecoveryApproval","parameters":[{"name":"requestId","type":"ByteArray"},{"name":"approver","type":"Hash160"},{"name":"approvalCount","type":"Integer"},{"name":"required","type":"Integer"}]},{"name":"RecoveryExecuted","parameters":[{"name":"tokenId","type":"Integer"},{"name":"oldOwner","type":"Hash160"},{"name":"newOwner","type":"Hash160"}]},{"name":"RecoveryCancelled","parameters":[{"name":"requestId","type":"ByteArray"},{"name":"cancelledBy","type":"Hash160"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.RoleRegistry: `{"id":-13,"hash":"0x52200161c6f0b581b590d41af8ccc577dc7477a9","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":174904780},"manifest":{"name":"RoleRegistry","abi":{"methods":[{"name":"assignPermission","offset":0,"parameters":[{"name":"roleID","type":"Integer"},{"name":"resource","type":"String"},{"name":"action","type":"String"},{"name":"scope","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"createRole","offset":7,"parameters":[{"name":"name","type":"String"},{"name":"description","type":"String"},{"name":"parentID","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"deleteRole","offset":14,"parameters":[{"name":"roleID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"getPermissions","offset":21,"parameters":[{"name":"roleID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getRole","offset":28,"parameters":[{"name":"roleID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getRoleByName","offset":35,"parameters":[{"name":"name","type":"String"}],"returntype":"Array","safe":true},{"name":"getRolesForAddress","offset":42,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"grantRole","offset":49,"parameters":[{"name":"address","type":"Hash160"},{"name":"roleID","type":"Integer"},{"name":"expiresAt","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"hasPermission","offset":56,"parameters":[{"name":"address","type":"Hash160"},{"name":"resource","type":"String"},{"name":"action","type":"String"},{"name":"scope","type":"Integer"}],"returntype":"Boolean","safe":true},{"name":"hasRole","offset":63,"parameters":[{"name":"address","type":"Hash160"},{"name":"roleID","type":"Integer"}],"returntype":"Boolean","safe":true},{"name":"removePermission","offset":70,"parameters":[{"name":"roleID","type":"Integer"},{"name":"resource","type":"String"},{"name":"action","type":"String"}],"returntype":"Boolean","safe":false},{"name":"revokeRole","offset":77,"parameters":[{"name":"address","type":"Hash160"},{"name":"roleID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"totalRoles","offset":84,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"RoleCreated","parameters":[{"name":"roleID","type":"Integer"},{"name":"name","type":"String"},{"name":"parentID","type":"Integer"},{"name":"createdBy","type":"Hash160"}]},{"name":"RoleDeleted","parameters":[{"name":"roleID","type":"Integer"},{"name":"deletedBy","type":"Hash160"}]},{"name":"RoleGranted","parameters":[{"name":"address","type":"Hash160"},{"name":"roleID","type":"Integer"},{"name":"expiresAt","type":"Integer"},{"name":"grantedBy","type":"Hash160"}]},{"name":"RoleRevoked","parameters":[{"name":"address","type":"Hash160"},{"name":"roleID","type":"Integer"},{"name":"revokedBy","type":"Hash160"}]},{"name":"PermissionAssigned","parameters":[{"name":"roleID","type":"Integer"},{"name":"resource","type":"String"},{"name":"action","type":"String"},{"name":"scope","type":"Integer"}]},{"name":"PermissionRemoved","parameters":[{"name":"roleID","type":"Integer"},{"name":"resource","type":"String"},{"name":"action","type":"String"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.VTS: `{"id":-14,"hash":"0x893659b7f9d0a383d960234841ff8a6d825e3468","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":3508376793},"manifest":{"name":"VTS","abi":{"methods":[{"name":"balanceDetails","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"balanceOf","offset":7,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"burn","offset":14,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"canSpendAt","offset":21,"parameters":[{"name":"account","type":"Hash160"},{"name":"vendor","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean","safe":true},{"name":"convertToUnrestricted","offset":28,"parameters":[{"name":"account","type":"Hash160"},{"name":"category","type":"Integer"},{"name":"amount","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"deactivateVendor","offset":35,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"decimals","offset":42,"parameters":[],"returntype":"Integer","safe":true},{"name":"getDeductibleExpenses","offset":49,"parameters":[{"name":"account","type":"Hash160"},{"name":"startBlock","type":"Integer"},{"name":"endBlock","type":"Integer"},{"name":"category","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"getIncomeForPeriod","offset":56,"parameters":[{"name":"account","type":"Hash160"},{"name":"startBlock","type":"Integer"},{"name":"endBlock","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"getTaxConfig","offset":63,"parameters":[],"returntype":"Array","safe":true},{"name":"getTaxSummary","offset":70,"parameters":[{"name":"account","type":"Hash160"},{"name":"startBlock","type":"Integer"},{"name":"endBlock","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getTaxWithheld","offset":77,"parameters":[{"name":"account","type":"Hash160"},{"name":"startBlock","type":"Integer"},{"name":"endBlock","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"getTransactions","offset":84,"parameters":[{"name":"account","type":"Hash160"},{"name":"startBlock","type":"Integer"},{"name":"endBlock","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getVendor","offset":91,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getVendorCategories","offset":98,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"isVendor","offset":105,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"issueTaxRefund","offset":112,"parameters":[{"name":"account","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"mint","offset":119,"parameters":[{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"mintRestricted","offset":126,"parameters":[{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"category","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"payWage","offset":133,"parameters":[{"name":"employer","type":"Hash160"},{"name":"employee","type":"Hash160"},{"name":"grossAmount","type":"Integer"},{"name":"taxRate","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"registerVendor","offset":140,"parameters":[{"name":"address","type":"Hash160"},{"name":"name","type":"String"},{"name":"categories","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"restrictedBalanceOf","offset":147,"parameters":[{"name":"account","type":"Hash160"},{"name":"category","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"setTaxConfig","offset":154,"parameters":[{"name":"incomeRate","type":"Integer"},{"name":"salesRate","type":"Integer"},{"name":"treasuryAddress","type":"Hash160"},{"name":"exemptCategories","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"spend","offset":161,"parameters":[{"name":"from","type":"Hash160"},{"name":"vendor","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"symbol","offset":168,"parameters":[],"returntype":"String","safe":true},{"name":"totalSupply","offset":175,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":182,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"unrestrictedBalanceOf","offset":189,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"updateVendor","offset":196,"parameters":[{"name":"address","type":"Hash160"},{"name":"name","type":"String"},{"name":"categories","type":"Integer"}],"returntype":"Boolean","safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"Mint","parameters":[{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"category","type":"Integer"}]},{"name":"Burn","parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"Spend","parameters":[{"name":"from","type":"Hash160"},{"name":"vendor","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"categoriesUsed","type":"Integer"}]},{"name":"VendorRegistered","parameters":[{"name":"address","type":"Hash160"},{"name":"name","type":"String"},{"name":"categories","type":"Integer"}]},{"name":"VendorUpdated","parameters":[{"name":"address","type":"Hash160"},{"name":"name","type":"String"},{"name":"categories","type":"Integer"}]},{"name":"VendorDeactivated","parameters":[{"name":"address","type":"Hash160"}]},{"name":"TaxWithheld","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"grossAmount","type":"Integer"},{"name":"taxAmount","type":"Integer"},{"name":"taxRate","type":"Integer"}]},{"name":"TaxRefunded","parameters":[{"name":"account","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"ConvertedToUnrestricted","parameters":[{"name":"account","type":"Hash160"},{"name":"category","type":"Integer"},{"name":"amount","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-17"],"trusts":[],"extra":null},"updatecounter":0}`, } @@ -504,7 +504,7 @@ func TestBlockchain_GetNatives(t *testing.T) { // Check genesis-based native contract states. natives := bc.GetNatives() - require.Equal(t, len(nativenames.All)-2, len(natives)) // Notary is deployed starting from D hardfork, Treasury - starting from Faun. + require.Equal(t, len(nativenames.All)-3, len(natives)) // Notary is deployed starting from D hardfork, Treasury and Federation - starting from Faun. for _, cs := range natives { csFull := state.Contract{ ContractBase: cs.ContractBase, diff --git a/pkg/core/native/native_test/person_token_test.go b/pkg/core/native/native_test/vita_test.go similarity index 74% rename from pkg/core/native/native_test/person_token_test.go rename to pkg/core/native/native_test/vita_test.go index 7a338d8..b41cabc 100644 --- a/pkg/core/native/native_test/person_token_test.go +++ b/pkg/core/native/native_test/vita_test.go @@ -11,13 +11,13 @@ import ( "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" ) -func newPersonTokenClient(t *testing.T) *neotest.ContractInvoker { - return newNativeClient(t, nativenames.PersonToken) +func newVitaClient(t *testing.T) *neotest.ContractInvoker { + return newNativeClient(t, nativenames.Vita) } -// registerPersonToken is a helper to register a PersonToken for a signer. +// registerVita is a helper to register a Vita for a signer. // Returns the tokenID bytes. -func registerPersonToken(t *testing.T, c *neotest.ContractInvoker, signer neotest.Signer) []byte { +func registerVita(t *testing.T, c *neotest.ContractInvoker, signer neotest.Signer) []byte { owner := signer.ScriptHash() personHash := hash.Sha256(owner.BytesBE()).BytesBE() isEntity := false @@ -38,9 +38,9 @@ func registerPersonToken(t *testing.T, c *neotest.ContractInvoker, signer neotes return tokenIDBytes } -// TestPersonToken_Register tests basic registration functionality. -func TestPersonToken_Register(t *testing.T) { - c := newPersonTokenClient(t) +// TestVita_Register tests basic registration functionality. +func TestVita_Register(t *testing.T) { + c := newVitaClient(t) e := c.Executor acc := e.NewAccount(t) @@ -61,7 +61,7 @@ func TestPersonToken_Register(t *testing.T) { // Check event was emitted aer := e.GetTxExecResult(t, txHash) require.Equal(t, 1, len(aer.Events)) - require.Equal(t, "PersonTokenCreated", aer.Events[0].Name) + require.Equal(t, "VitaCreated", aer.Events[0].Name) // Check exists returns true invoker.Invoke(t, true, "exists", owner.BytesBE()) @@ -74,13 +74,13 @@ func TestPersonToken_Register(t *testing.T) { }, "getToken", owner.BytesBE()) } -// TestPersonToken_ValidateCaller tests the validateCaller method. +// TestVita_ValidateCaller tests the validateCaller method. // Note: validateCaller uses GetCallingScriptHash() which returns the calling contract's // script hash, not the transaction signer's address. When called directly from a transaction // (not from another contract), the caller has no token. These methods are designed for // cross-contract authorization. -func TestPersonToken_ValidateCaller(t *testing.T) { - c := newPersonTokenClient(t) +func TestVita_ValidateCaller(t *testing.T) { + c := newVitaClient(t) t.Run("no token - direct call", func(t *testing.T) { acc := c.Executor.NewAccount(t) @@ -88,35 +88,35 @@ func TestPersonToken_ValidateCaller(t *testing.T) { // validateCaller uses GetCallingScriptHash() which returns the transaction script hash // when called directly, not the signer's account. This will always fail for direct calls. - invoker.InvokeFail(t, "caller does not have a PersonToken", "validateCaller") + invoker.InvokeFail(t, "caller does not have a Vita", "validateCaller") }) // Note: Testing validateCaller with a token requires deploying a helper contract - // that has a PersonToken registered to its script hash, then calling validateCaller + // that has a Vita registered to its script hash, then calling validateCaller // from within that contract. This is the intended usage pattern for cross-contract auth. } -// TestPersonToken_RequireRole tests the requireRole method. +// TestVita_RequireRole tests the requireRole method. // Note: requireRole uses GetCallingScriptHash() - designed for cross-contract authorization. -func TestPersonToken_RequireRole(t *testing.T) { - c := newPersonTokenClient(t) +func TestVita_RequireRole(t *testing.T) { + c := newVitaClient(t) t.Run("no token - direct call", func(t *testing.T) { acc := c.Executor.NewAccount(t) invoker := c.WithSigners(acc) // Direct calls always fail because GetCallingScriptHash() returns transaction script hash - invoker.InvokeFail(t, "caller does not have a PersonToken", "requireRole", 0) + invoker.InvokeFail(t, "caller does not have a Vita", "requireRole", 0) }) // Note: Testing requireRole with actual role checks requires a deployed contract - // with a PersonToken registered to its script hash. + // with a Vita registered to its script hash. } -// TestPersonToken_RequireCoreRole tests the requireCoreRole method. +// TestVita_RequireCoreRole tests the requireCoreRole method. // Note: requireCoreRole uses GetCallingScriptHash() - designed for cross-contract authorization. -func TestPersonToken_RequireCoreRole(t *testing.T) { - c := newPersonTokenClient(t) +func TestVita_RequireCoreRole(t *testing.T) { + c := newVitaClient(t) // CoreRole constants const ( @@ -137,17 +137,17 @@ func TestPersonToken_RequireCoreRole(t *testing.T) { invoker := c.WithSigners(acc) // Direct calls always fail because GetCallingScriptHash() returns transaction script hash - invoker.InvokeFail(t, "caller does not have a PersonToken", "requireCoreRole", CoreRoleNone) + invoker.InvokeFail(t, "caller does not have a Vita", "requireCoreRole", CoreRoleNone) }) // Note: Testing requireCoreRole with actual role checks requires a deployed contract - // with a PersonToken registered to its script hash. + // with a Vita registered to its script hash. } -// TestPersonToken_RequirePermission tests the requirePermission method. +// TestVita_RequirePermission tests the requirePermission method. // Note: requirePermission uses GetCallingScriptHash() - designed for cross-contract authorization. -func TestPersonToken_RequirePermission(t *testing.T) { - c := newPersonTokenClient(t) +func TestVita_RequirePermission(t *testing.T) { + c := newVitaClient(t) t.Run("empty resource", func(t *testing.T) { acc := c.Executor.NewAccount(t) @@ -170,16 +170,16 @@ func TestPersonToken_RequirePermission(t *testing.T) { invoker := c.WithSigners(acc) // Direct calls always fail because GetCallingScriptHash() returns transaction script hash - invoker.InvokeFail(t, "caller does not have a PersonToken", "requirePermission", "documents", "read", "global") + invoker.InvokeFail(t, "caller does not have a Vita", "requirePermission", "documents", "read", "global") }) // Note: Testing requirePermission with actual permission checks requires a deployed contract - // with a PersonToken registered to its script hash. + // with a Vita registered to its script hash. } -// TestPersonToken_TotalSupply tests the totalSupply method. -func TestPersonToken_TotalSupply(t *testing.T) { - c := newPersonTokenClient(t) +// TestVita_TotalSupply tests the totalSupply method. +func TestVita_TotalSupply(t *testing.T) { + c := newVitaClient(t) e := c.Executor // Initially, totalSupply should be 0 @@ -187,27 +187,27 @@ func TestPersonToken_TotalSupply(t *testing.T) { // Register a token acc1 := e.NewAccount(t) - registerPersonToken(t, c, acc1) + registerVita(t, c, acc1) // Now totalSupply should be 1 c.Invoke(t, 1, "totalSupply") // Register another token acc2 := e.NewAccount(t) - registerPersonToken(t, c, acc2) + registerVita(t, c, acc2) // Now totalSupply should be 2 c.Invoke(t, 2, "totalSupply") } -// TestPersonToken_GetTokenByID tests the getTokenByID method. -func TestPersonToken_GetTokenByID(t *testing.T) { - c := newPersonTokenClient(t) +// TestVita_GetTokenByID tests the getTokenByID method. +func TestVita_GetTokenByID(t *testing.T) { + c := newVitaClient(t) e := c.Executor // Register a token - the first token gets ID 0 (counter starts at 0) acc := e.NewAccount(t) - registerPersonToken(t, c, acc) + registerVita(t, c, acc) // Token ID 0 should exist (first registered token) c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { @@ -215,7 +215,7 @@ func TestPersonToken_GetTokenByID(t *testing.T) { // Check that result is an array (not null) arr, ok := stack[0].Value().([]stackitem.Item) require.True(t, ok, "expected array result for existing token") - require.GreaterOrEqual(t, len(arr), 9) // PersonToken has 9 fields + require.GreaterOrEqual(t, len(arr), 9) // Vita has 9 fields // Check owner matches (owner is at index 1) owner, ok := arr[1].Value().([]byte) @@ -231,13 +231,13 @@ func TestPersonToken_GetTokenByID(t *testing.T) { }, "getTokenByID", 999999) } -// TestPersonToken_SuspendReinstate tests suspend and reinstate functionality. -func TestPersonToken_SuspendReinstate(t *testing.T) { - c := newPersonTokenClient(t) +// TestVita_SuspendReinstate tests suspend and reinstate functionality. +func TestVita_SuspendReinstate(t *testing.T) { + c := newVitaClient(t) e := c.Executor acc := e.NewAccount(t) - registerPersonToken(t, c, acc) + registerVita(t, c, acc) invoker := c.WithSigners(acc) committeeInvoker := c.WithSigners(c.Committee) @@ -285,6 +285,6 @@ func TestPersonToken_SuspendReinstate(t *testing.T) { // Note: Full cross-contract testing of validateCaller, requireRole, requireCoreRole, and // requirePermission would require deploying a helper contract that: -// 1. Has a PersonToken registered to its script hash -// 2. Calls the PersonToken cross-contract methods from within its own methods +// 1. Has a Vita registered to its script hash +// 2. Calls the Vita cross-contract methods from within its own methods // This is the intended usage pattern for these cross-contract authorization methods. diff --git a/pkg/core/native/native_test/vts_test.go b/pkg/core/native/native_test/vts_test.go index cae6a5d..509e91a 100644 --- a/pkg/core/native/native_test/vts_test.go +++ b/pkg/core/native/native_test/vts_test.go @@ -15,12 +15,12 @@ func newVTSClient(t *testing.T) *neotest.ContractInvoker { return newNativeClient(t, nativenames.VTS) } -// registerVita is a helper to register a PersonToken (Vita) for an account. +// registerVitaForVTS is a helper to register a Vita for an account. // This is required for accounts receiving restricted VTS. // Uses the existing executor to ensure we're on the same blockchain. -func registerVita(t *testing.T, e *neotest.Executor, acc neotest.Signer) { - personTokenHash := e.NativeHash(t, nativenames.PersonToken) - c := e.NewInvoker(personTokenHash, acc) +func registerVitaForVTS(t *testing.T, e *neotest.Executor, acc neotest.Signer) { + vitaHash := e.NativeHash(t, nativenames.Vita) + c := e.NewInvoker(vitaHash, acc) owner := acc.ScriptHash() personHash := hash.Sha256(owner.BytesBE()).BytesBE() isEntity := false @@ -37,8 +37,8 @@ func registerVita(t *testing.T, e *neotest.Executor, acc neotest.Signer) { // This is required for spending at age-restricted vendors. // Uses the existing executor to ensure we're on the same blockchain. func addAgeVerifiedAttribute(t *testing.T, e *neotest.Executor, committee neotest.Signer, acc neotest.Signer) { - personTokenHash := e.NativeHash(t, nativenames.PersonToken) - c := e.CommitteeInvoker(personTokenHash) + vitaHash := e.NativeHash(t, nativenames.Vita) + c := e.CommitteeInvoker(vitaHash) owner := acc.ScriptHash() // Get the token ID from getToken result @@ -126,7 +126,7 @@ func TestVTS_MintRestricted(t *testing.T) { committeeInvoker := c.WithSigners(c.Committee) // Restricted VTS requires recipient to have a Vita - registerVita(t, e, acc) + registerVitaForVTS(t, e, acc) t.Run("mint food-restricted VTS", func(t *testing.T) { committeeInvoker.Invoke(t, true, "mintRestricted", acc.ScriptHash(), 500_00000000, state.CategoryFood) @@ -334,7 +334,7 @@ func TestVTS_ConvertToUnrestricted(t *testing.T) { userInvoker := c.WithSigners(acc) // Restricted VTS requires Vita - registerVita(t, e, acc) + registerVitaForVTS(t, e, acc) // Mint restricted VTS committeeInvoker.Invoke(t, true, "mintRestricted", acc.ScriptHash(), 500_00000000, state.CategoryFood) @@ -460,7 +460,7 @@ func TestVTS_BalanceDetails(t *testing.T) { committeeInvoker := c.WithSigners(c.Committee) // Restricted VTS requires Vita - registerVita(t, e, acc) + registerVitaForVTS(t, e, acc) // Mint mixed VTS committeeInvoker.Invoke(t, true, "mint", acc.ScriptHash(), 100_00000000) @@ -542,7 +542,7 @@ func TestVTS_NegativeAmount(t *testing.T) { committeeInvoker := c.WithSigners(c.Committee) // Restricted VTS requires Vita - registerVita(t, e, acc) + registerVitaForVTS(t, e, acc) t.Run("mint negative fails", func(t *testing.T) { committeeInvoker.InvokeFail(t, "amount must be positive", "mint", acc.ScriptHash(), -1) @@ -564,7 +564,7 @@ func TestVTS_MultiCategoryVendor(t *testing.T) { customerInvoker := c.WithSigners(customer) // Restricted VTS requires Vita - registerVita(t, e, customer) + registerVitaForVTS(t, e, customer) // Register vendor accepting food AND shelter categories := state.CategoryFood | state.CategoryShelter @@ -605,8 +605,8 @@ func TestVTS_AgeRestrictedVendor(t *testing.T) { minorInvoker := c.WithSigners(minorCustomer) // Register Vita for both customers - registerVita(t, e, adultCustomer) - registerVita(t, e, minorCustomer) + registerVitaForVTS(t, e, adultCustomer) + registerVitaForVTS(t, e, minorCustomer) // Only adult has age_verified attribute addAgeVerifiedAttribute(t, e, c.Committee, adultCustomer) @@ -645,10 +645,10 @@ func TestVTS_MintRestrictedRequiresVita(t *testing.T) { committeeInvoker := c.WithSigners(c.Committee) // Only register Vita for one account - registerVita(t, e, accWithVita) + registerVitaForVTS(t, e, accWithVita) t.Run("cannot mint restricted VTS to account without Vita", func(t *testing.T) { - committeeInvoker.InvokeFail(t, "restricted VTS requires Vita (PersonToken)", "mintRestricted", accWithoutVita.ScriptHash(), 100_00000000, state.CategoryFood) + committeeInvoker.InvokeFail(t, "restricted VTS requires Vita", "mintRestricted", accWithoutVita.ScriptHash(), 100_00000000, state.CategoryFood) }) t.Run("can mint restricted VTS to account with Vita", func(t *testing.T) { diff --git a/pkg/core/native/nativehashes/hashes.go b/pkg/core/native/nativehashes/hashes.go index f981194..dad051b 100644 --- a/pkg/core/native/nativehashes/hashes.go +++ b/pkg/core/native/nativehashes/hashes.go @@ -31,10 +31,12 @@ var ( Notary = util.Uint160{0x3b, 0xec, 0x35, 0x31, 0x11, 0x9b, 0xba, 0xd7, 0x6d, 0xd0, 0x44, 0x92, 0xb, 0xd, 0xe6, 0xc3, 0x19, 0x4f, 0xe1, 0xc1} // Treasury is a hash of native Treasury contract. Treasury = util.Uint160{0xc1, 0x3a, 0x56, 0xc9, 0x83, 0x53, 0xa7, 0xea, 0x6a, 0x32, 0x4d, 0x9a, 0x83, 0x5d, 0x1b, 0x5b, 0xf2, 0x26, 0x63, 0x15} - // PersonToken is a hash of native PersonToken contract. - PersonToken = util.Uint160{0x4, 0xaf, 0x34, 0xf1, 0xde, 0xdb, 0xa4, 0x7a, 0xd4, 0x30, 0xdf, 0xc7, 0x77, 0x1c, 0x26, 0x3a, 0x8a, 0x72, 0xa5, 0x21} + // Vita is a hash of native Vita contract. + Vita = util.Uint160{0x7d, 0x21, 0xb3, 0xd4, 0x1d, 0x79, 0xd0, 0x82, 0xcb, 0x1a, 0x24, 0xc8, 0xf9, 0xb8, 0xdf, 0x3f, 0x4, 0x7f, 0x43, 0xde} // RoleRegistry is a hash of native RoleRegistry contract. RoleRegistry = util.Uint160{0xa9, 0x77, 0x74, 0xdc, 0x77, 0xc5, 0xcc, 0xf8, 0x1a, 0xd4, 0x90, 0xb5, 0x81, 0xb5, 0xf0, 0xc6, 0x61, 0x1, 0x20, 0x52} // VTS is a hash of native VTS contract. VTS = util.Uint160{0x68, 0x34, 0x5e, 0x82, 0x6d, 0x8a, 0xff, 0x41, 0x48, 0x23, 0x60, 0xd9, 0x83, 0xa3, 0xd0, 0xf9, 0xb7, 0x59, 0x36, 0x89} + // Federation is a hash of native Federation contract. + Federation = util.Uint160{0xfd, 0x78, 0x70, 0xb, 0xa9, 0x19, 0xed, 0xb0, 0x19, 0x40, 0xdd, 0xc7, 0x97, 0x48, 0xf4, 0x25, 0x1d, 0xb0, 0x5, 0x1f} ) diff --git a/pkg/core/native/nativeids/ids.go b/pkg/core/native/nativeids/ids.go index fc9a7b5..8aa8d73 100644 --- a/pkg/core/native/nativeids/ids.go +++ b/pkg/core/native/nativeids/ids.go @@ -29,10 +29,12 @@ const ( Notary int32 = -10 // Treasury is an ID of native Treasury contract. Treasury int32 = -11 - // PersonToken is an ID of native PersonToken contract. - PersonToken int32 = -12 + // Vita is an ID of native Vita contract. + Vita int32 = -12 // RoleRegistry is an ID of native RoleRegistry contract. RoleRegistry int32 = -13 // VTS is an ID of native VTS contract. VTS int32 = -14 + // Federation is an ID of native Federation contract. + Federation int32 = -15 ) diff --git a/pkg/core/native/nativenames/names.go b/pkg/core/native/nativenames/names.go index 31d2647..9829c87 100644 --- a/pkg/core/native/nativenames/names.go +++ b/pkg/core/native/nativenames/names.go @@ -13,9 +13,10 @@ const ( CryptoLib = "CryptoLib" StdLib = "StdLib" Treasury = "Treasury" - PersonToken = "PersonToken" + Vita = "Vita" RoleRegistry = "RoleRegistry" VTS = "VTS" + Federation = "Federation" ) // All contains the list of all native contract names ordered by the contract ID. @@ -31,9 +32,10 @@ var All = []string{ Oracle, Notary, Treasury, - PersonToken, + Vita, RoleRegistry, VTS, + Federation, } // IsValid checks if the name is a valid native contract's name. @@ -49,7 +51,8 @@ func IsValid(name string) bool { name == CryptoLib || name == StdLib || name == Treasury || - name == PersonToken || + name == Vita || name == RoleRegistry || - name == VTS + name == VTS || + name == Federation } diff --git a/pkg/core/native/role_registry.go b/pkg/core/native/role_registry.go index 3fdc2a5..3161ab3 100644 --- a/pkg/core/native/role_registry.go +++ b/pkg/core/native/role_registry.go @@ -22,7 +22,7 @@ import ( ) // RoleRegistry is a native contract for hierarchical role-based access control. -// It replaces NEO.CheckCommittee() with democratic governance based on Vita (PersonToken). +// It replaces NEO.CheckCommittee() with democratic governance based on Vita. type RoleRegistry struct { interop.ContractMD @@ -42,7 +42,7 @@ type RoleRegistryCache struct { const ( // RoleCommittee is the Tutus Committee role (designated officials). RoleCommittee uint64 = 1 - // RoleRegistrar can register new PersonTokens. + // RoleRegistrar can register new Vitas. RoleRegistrar uint64 = 2 // RoleAttestor can attest identity attributes. RoleAttestor uint64 = 3 @@ -269,7 +269,7 @@ func (r *RoleRegistry) Initialize(ic *interop.Context, hf *config.Hardfork, newM description string }{ {RoleCommittee, "COMMITTEE", "Tutus Committee member (designated officials)"}, - {RoleRegistrar, "REGISTRAR", "Can register new PersonTokens"}, + {RoleRegistrar, "REGISTRAR", "Can register new Vitas"}, {RoleAttestor, "ATTESTOR", "Can attest identity attributes"}, {RoleOperator, "OPERATOR", "System operator (node management)"}, } diff --git a/pkg/core/native/treasury.go b/pkg/core/native/treasury.go index f2e30ba..562b85f 100644 --- a/pkg/core/native/treasury.go +++ b/pkg/core/native/treasury.go @@ -1,6 +1,8 @@ package native import ( + "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" @@ -9,6 +11,7 @@ import ( "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" ) @@ -18,6 +21,11 @@ type Treasury struct { NEO INEO } +// Storage key prefixes for Treasury. +const ( + prefixTreasuryDeficit byte = 0x01 // -> *big.Int (cumulative deficit from fee subsidies) +) + var _ interop.Contract = (*Treasury)(nil) func newTreasury() *Treasury { @@ -105,3 +113,36 @@ func (t *Treasury) onNEP17Payment(ic *interop.Context, args []stackitem.Item) st var _, _, _ = from, amount, data return stackitem.Null{} } + +// Public methods for cross-native access + +// Address returns the contract's script hash. +func (t *Treasury) Address() util.Uint160 { + return t.Hash +} + +// Storage key helpers + +func makeDeficitKey() []byte { + return []byte{prefixTreasuryDeficit} +} + +// AddDeficit adds to the cumulative deficit (for cross-native access). +// This is called when Treasury cannot cover fee subsidies for Vita holders. +func (t *Treasury) AddDeficit(d *dao.Simple, amount *big.Int) { + if amount.Sign() <= 0 { + return + } + current := t.GetDeficit(d) + newDeficit := new(big.Int).Add(current, amount) + d.PutStorageItem(t.ID, makeDeficitKey(), newDeficit.Bytes()) +} + +// GetDeficit returns the current cumulative deficit (for cross-native access). +func (t *Treasury) GetDeficit(d *dao.Simple) *big.Int { + si := d.GetStorageItem(t.ID, makeDeficitKey()) + if si == nil { + return big.NewInt(0) + } + return new(big.Int).SetBytes(si) +} diff --git a/pkg/core/native/person_token.go b/pkg/core/native/vita.go similarity index 69% rename from pkg/core/native/person_token.go rename to pkg/core/native/vita.go index e285bb9..1b8b27e 100644 --- a/pkg/core/native/person_token.go +++ b/pkg/core/native/vita.go @@ -22,37 +22,37 @@ import ( "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" ) -// PersonToken represents a soul-bound identity native contract. -type PersonToken struct { +// Vita represents a soul-bound identity native contract. +type Vita struct { interop.ContractMD NEO INEO RoleRegistry IRoleRegistry } -// PersonTokenCache represents the cached state for PersonToken contract. -type PersonTokenCache struct { +// VitaCache represents the cached state for Vita contract. +type VitaCache struct { tokenCount uint64 } -// Storage key prefixes for PersonToken. +// Storage key prefixes for Vita. const ( prefixTokenByOwner = 0x01 // owner (Uint160) -> tokenID (uint64) - prefixTokenByID = 0x02 // tokenID (uint64) -> PersonToken + prefixTokenByID = 0x02 // tokenID (uint64) -> Vita token prefixPersonHash = 0x03 // personHash -> tokenID (uniqueness) prefixAttribute = 0x04 // tokenID + attrKey -> Attribute prefixChallenge = 0x05 // challengeID -> AuthChallenge prefixRecovery = 0x06 // requestID -> RecoveryRequest prefixActiveRecovery = 0x07 // tokenID + "recovery" -> requestID prefixTokenCounter = 0x10 // -> uint64 - prefixConfig = 0x11 // -> PersonTokenConfig + prefixConfig = 0x11 // -> VitaConfig ) -// Event names for PersonToken. +// Event names for Vita. const ( - PersonTokenCreatedEvent = "PersonTokenCreated" - PersonTokenSuspendedEvent = "PersonTokenSuspended" - PersonTokenReinstatedEvent = "PersonTokenReinstated" - PersonTokenRevokedEvent = "PersonTokenRevoked" + VitaCreatedEvent = "VitaCreated" + VitaSuspendedEvent = "VitaSuspended" + VitaReinstatedEvent = "VitaReinstated" + VitaRevokedEvent = "VitaRevoked" AttributeSetEvent = "AttributeSet" AttributeRevokedEvent = "AttributeRevoked" AuthenticationSuccessEvent = "AuthenticationSuccess" @@ -71,10 +71,10 @@ var ( ErrTokenSuspended = errors.New("token is suspended") ErrTokenRevoked = errors.New("token is revoked") ErrTokenNotSuspended = errors.New("token is not suspended") - ErrInvalidOwner = errors.New("invalid owner") - ErrInvalidPersonHash = errors.New("invalid person hash") - ErrNotCommittee = errors.New("invalid committee signature") - ErrPersonTokenInvalidWitness = errors.New("invalid witness") + ErrInvalidOwner = errors.New("invalid owner") + ErrInvalidPersonHash = errors.New("invalid person hash") + ErrNotCommittee = errors.New("invalid committee signature") + ErrVitaInvalidWitness = errors.New("invalid witness") ErrAttributeNotFound = errors.New("attribute not found") ErrAttributeRevoked = errors.New("attribute is already revoked") ErrAttributeExpired = errors.New("attribute has expired") @@ -97,18 +97,18 @@ var ( ErrInvalidNewOwner = errors.New("invalid new owner") ErrTokenInRecovery = errors.New("token is already in recovery") ErrNotRecoveryRequester = errors.New("not the recovery requester") - ErrCallerHasNoToken = errors.New("caller does not have a PersonToken") - ErrRoleNotAssigned = errors.New("required role not assigned") - ErrPermissionDenied = errors.New("permission denied") - ErrPersonTokenInvalidRole = errors.New("invalid core role") - ErrPersonTokenInvalidResource = errors.New("invalid resource") - ErrPersonTokenInvalidAction = errors.New("invalid action") + ErrCallerHasNoVita = errors.New("caller does not have a Vita") + ErrRoleNotAssigned = errors.New("required role not assigned") + ErrPermissionDenied = errors.New("permission denied") + ErrVitaInvalidRole = errors.New("invalid core role") + ErrVitaInvalidResource = errors.New("invalid resource") + ErrVitaInvalidAction = errors.New("invalid action") ) // CoreRole represents built-in roles for cross-contract access control. type CoreRole uint8 -// Core roles for PersonToken holders. +// Core roles for Vita holders. const ( CoreRoleNone CoreRole = 0 CoreRoleUser CoreRole = 1 // Basic authenticated user @@ -119,33 +119,33 @@ const ( ) var ( - _ interop.Contract = (*PersonToken)(nil) - _ dao.NativeContractCache = (*PersonTokenCache)(nil) + _ interop.Contract = (*Vita)(nil) + _ dao.NativeContractCache = (*VitaCache)(nil) ) // Copy implements NativeContractCache interface. -func (c *PersonTokenCache) Copy() dao.NativeContractCache { - return &PersonTokenCache{ +func (c *VitaCache) Copy() dao.NativeContractCache { + return &VitaCache{ tokenCount: c.tokenCount, } } // checkCommittee checks if the caller has committee authority. // Uses RoleRegistry if available, falls back to NEO.CheckCommittee(). -func (p *PersonToken) checkCommittee(ic *interop.Context) bool { - if p.RoleRegistry != nil { - return p.RoleRegistry.CheckCommittee(ic) +func (v *Vita) checkCommittee(ic *interop.Context) bool { + if v.RoleRegistry != nil { + return v.RoleRegistry.CheckCommittee(ic) } // Fallback to NEO for backwards compatibility - return p.NEO.CheckCommittee(ic) + return v.NEO.CheckCommittee(ic) } -// newPersonToken creates a new PersonToken native contract. -func newPersonToken() *PersonToken { - p := &PersonToken{ - ContractMD: *interop.NewContractMD(nativenames.PersonToken, nativeids.PersonToken), +// newVita creates a new Vita native contract. +func newVita() *Vita { + v := &Vita{ + ContractMD: *interop.NewContractMD(nativenames.Vita, nativeids.Vita), } - defer p.BuildHFSpecificMD(p.ActiveIn()) + defer v.BuildHFSpecificMD(v.ActiveIn()) // Register method desc := NewDescriptor("register", smartcontract.ByteArrayType, @@ -153,51 +153,51 @@ func newPersonToken() *PersonToken { manifest.NewParameter("personHash", smartcontract.ByteArrayType), manifest.NewParameter("isEntity", smartcontract.BoolType), manifest.NewParameter("recoveryHash", smartcontract.ByteArrayType)) - md := NewMethodAndPrice(p.register, 1<<17, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md := NewMethodAndPrice(v.register, 1<<17, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // GetToken method desc = NewDescriptor("getToken", smartcontract.ArrayType, manifest.NewParameter("owner", smartcontract.Hash160Type)) - md = NewMethodAndPrice(p.getToken, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.getToken, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // GetTokenByID method desc = NewDescriptor("getTokenByID", smartcontract.ArrayType, manifest.NewParameter("tokenId", smartcontract.IntegerType)) - md = NewMethodAndPrice(p.getTokenByID, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.getTokenByID, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // Exists method desc = NewDescriptor("exists", smartcontract.BoolType, manifest.NewParameter("owner", smartcontract.Hash160Type)) - md = NewMethodAndPrice(p.exists, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.exists, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // TotalSupply method desc = NewDescriptor("totalSupply", smartcontract.IntegerType) - md = NewMethodAndPrice(p.totalSupply, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.totalSupply, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // Suspend method desc = NewDescriptor("suspend", smartcontract.BoolType, manifest.NewParameter("owner", smartcontract.Hash160Type), manifest.NewParameter("reason", smartcontract.StringType)) - md = NewMethodAndPrice(p.suspend, 1<<16, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.suspend, 1<<16, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // Reinstate method desc = NewDescriptor("reinstate", smartcontract.BoolType, manifest.NewParameter("owner", smartcontract.Hash160Type)) - md = NewMethodAndPrice(p.reinstate, 1<<16, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.reinstate, 1<<16, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // Revoke method desc = NewDescriptor("revoke", smartcontract.BoolType, manifest.NewParameter("owner", smartcontract.Hash160Type), manifest.NewParameter("reason", smartcontract.StringType)) - md = NewMethodAndPrice(p.revoke, 1<<17, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.revoke, 1<<17, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // SetAttribute method desc = NewDescriptor("setAttribute", smartcontract.BoolType, @@ -207,242 +207,242 @@ func newPersonToken() *PersonToken { manifest.NewParameter("valueEnc", smartcontract.ByteArrayType), manifest.NewParameter("expiresAt", smartcontract.IntegerType), manifest.NewParameter("disclosureLevel", smartcontract.IntegerType)) - md = NewMethodAndPrice(p.setAttribute, 1<<17, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.setAttribute, 1<<17, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // GetAttribute method desc = NewDescriptor("getAttribute", smartcontract.ArrayType, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("key", smartcontract.StringType)) - md = NewMethodAndPrice(p.getAttribute, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.getAttribute, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // RevokeAttribute method desc = NewDescriptor("revokeAttribute", smartcontract.BoolType, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("key", smartcontract.StringType), manifest.NewParameter("reason", smartcontract.StringType)) - md = NewMethodAndPrice(p.revokeAttribute, 1<<16, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.revokeAttribute, 1<<16, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // VerifyAttribute method desc = NewDescriptor("verifyAttribute", smartcontract.BoolType, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("key", smartcontract.StringType), manifest.NewParameter("expectedHash", smartcontract.ByteArrayType)) - md = NewMethodAndPrice(p.verifyAttribute, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.verifyAttribute, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // CreateChallenge method desc = NewDescriptor("createChallenge", smartcontract.ArrayType, manifest.NewParameter("owner", smartcontract.Hash160Type), manifest.NewParameter("purpose", smartcontract.StringType)) - md = NewMethodAndPrice(p.createChallenge, 1<<16, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.createChallenge, 1<<16, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // GetChallenge method desc = NewDescriptor("getChallenge", smartcontract.ArrayType, manifest.NewParameter("challengeId", smartcontract.ByteArrayType)) - md = NewMethodAndPrice(p.getChallenge, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.getChallenge, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // FulfillChallenge method desc = NewDescriptor("fulfillChallenge", smartcontract.BoolType, manifest.NewParameter("challengeId", smartcontract.ByteArrayType), manifest.NewParameter("signature", smartcontract.ByteArrayType)) - md = NewMethodAndPrice(p.fulfillChallenge, 1<<17, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.fulfillChallenge, 1<<17, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // VerifyAuth method desc = NewDescriptor("verifyAuth", smartcontract.BoolType, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("purpose", smartcontract.StringType), manifest.NewParameter("maxAge", smartcontract.IntegerType)) - md = NewMethodAndPrice(p.verifyAuth, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.verifyAuth, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // InitiateRecovery method desc = NewDescriptor("initiateRecovery", smartcontract.ByteArrayType, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("newOwner", smartcontract.Hash160Type), manifest.NewParameter("evidence", smartcontract.ByteArrayType)) - md = NewMethodAndPrice(p.initiateRecovery, 1<<17, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.initiateRecovery, 1<<17, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // ApproveRecovery method desc = NewDescriptor("approveRecovery", smartcontract.BoolType, manifest.NewParameter("requestId", smartcontract.ByteArrayType)) - md = NewMethodAndPrice(p.approveRecovery, 1<<16, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.approveRecovery, 1<<16, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // ExecuteRecovery method desc = NewDescriptor("executeRecovery", smartcontract.BoolType, manifest.NewParameter("requestId", smartcontract.ByteArrayType)) - md = NewMethodAndPrice(p.executeRecovery, 1<<17, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.executeRecovery, 1<<17, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // CancelRecovery method desc = NewDescriptor("cancelRecovery", smartcontract.BoolType, manifest.NewParameter("requestId", smartcontract.ByteArrayType)) - md = NewMethodAndPrice(p.cancelRecovery, 1<<16, callflag.States|callflag.AllowNotify) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.cancelRecovery, 1<<16, callflag.States|callflag.AllowNotify) + v.AddMethod(md, desc) // GetRecoveryRequest method desc = NewDescriptor("getRecoveryRequest", smartcontract.ArrayType, manifest.NewParameter("requestId", smartcontract.ByteArrayType)) - md = NewMethodAndPrice(p.getRecoveryRequest, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.getRecoveryRequest, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // ValidateCaller method desc = NewDescriptor("validateCaller", smartcontract.ArrayType) - md = NewMethodAndPrice(p.validateCaller, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.validateCaller, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // RequireRole method desc = NewDescriptor("requireRole", smartcontract.IntegerType, manifest.NewParameter("roleId", smartcontract.IntegerType)) - md = NewMethodAndPrice(p.requireRole, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.requireRole, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // RequireCoreRole method desc = NewDescriptor("requireCoreRole", smartcontract.IntegerType, manifest.NewParameter("coreRole", smartcontract.IntegerType)) - md = NewMethodAndPrice(p.requireCoreRole, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.requireCoreRole, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // RequirePermission method desc = NewDescriptor("requirePermission", smartcontract.IntegerType, manifest.NewParameter("resource", smartcontract.StringType), manifest.NewParameter("action", smartcontract.StringType), manifest.NewParameter("scope", smartcontract.StringType)) - md = NewMethodAndPrice(p.requirePermission, 1<<15, callflag.ReadStates) - p.AddMethod(md, desc) + md = NewMethodAndPrice(v.requirePermission, 1<<15, callflag.ReadStates) + v.AddMethod(md, desc) // Events - eDesc := NewEventDescriptor(PersonTokenCreatedEvent, + eDesc := NewEventDescriptor(VitaCreatedEvent, manifest.NewParameter("tokenId", smartcontract.ByteArrayType), manifest.NewParameter("owner", smartcontract.Hash160Type), manifest.NewParameter("createdAt", smartcontract.IntegerType)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) - eDesc = NewEventDescriptor(PersonTokenSuspendedEvent, + eDesc = NewEventDescriptor(VitaSuspendedEvent, manifest.NewParameter("tokenId", smartcontract.ByteArrayType), manifest.NewParameter("reason", smartcontract.StringType), manifest.NewParameter("suspendedBy", smartcontract.Hash160Type)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) - eDesc = NewEventDescriptor(PersonTokenReinstatedEvent, + eDesc = NewEventDescriptor(VitaReinstatedEvent, manifest.NewParameter("tokenId", smartcontract.ByteArrayType), manifest.NewParameter("reinstatedBy", smartcontract.Hash160Type)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) - eDesc = NewEventDescriptor(PersonTokenRevokedEvent, + eDesc = NewEventDescriptor(VitaRevokedEvent, manifest.NewParameter("tokenId", smartcontract.ByteArrayType), manifest.NewParameter("reason", smartcontract.StringType), manifest.NewParameter("revokedBy", smartcontract.Hash160Type)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) eDesc = NewEventDescriptor(AttributeSetEvent, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("key", smartcontract.StringType), manifest.NewParameter("attestor", smartcontract.Hash160Type), manifest.NewParameter("expiresAt", smartcontract.IntegerType)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) eDesc = NewEventDescriptor(AttributeRevokedEvent, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("key", smartcontract.StringType), manifest.NewParameter("revokedBy", smartcontract.Hash160Type), manifest.NewParameter("reason", smartcontract.StringType)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) eDesc = NewEventDescriptor(AuthChallengeCreatedEvent, manifest.NewParameter("challengeId", smartcontract.ByteArrayType), manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("purpose", smartcontract.StringType), manifest.NewParameter("expiresAt", smartcontract.IntegerType)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) eDesc = NewEventDescriptor(AuthenticationSuccessEvent, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("purpose", smartcontract.StringType), manifest.NewParameter("timestamp", smartcontract.IntegerType)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) eDesc = NewEventDescriptor(RecoveryInitiatedEvent, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("requestId", smartcontract.ByteArrayType), manifest.NewParameter("delayUntil", smartcontract.IntegerType)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) eDesc = NewEventDescriptor(RecoveryApprovalEvent, manifest.NewParameter("requestId", smartcontract.ByteArrayType), manifest.NewParameter("approver", smartcontract.Hash160Type), manifest.NewParameter("approvalCount", smartcontract.IntegerType), manifest.NewParameter("required", smartcontract.IntegerType)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) eDesc = NewEventDescriptor(RecoveryExecutedEvent, manifest.NewParameter("tokenId", smartcontract.IntegerType), manifest.NewParameter("oldOwner", smartcontract.Hash160Type), manifest.NewParameter("newOwner", smartcontract.Hash160Type)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) eDesc = NewEventDescriptor(RecoveryCancelledEvent, manifest.NewParameter("requestId", smartcontract.ByteArrayType), manifest.NewParameter("cancelledBy", smartcontract.Hash160Type)) - p.AddEvent(NewEvent(eDesc)) + v.AddEvent(NewEvent(eDesc)) - return p + return v } // Metadata returns contract metadata. -func (p *PersonToken) Metadata() *interop.ContractMD { - return &p.ContractMD +func (v *Vita) Metadata() *interop.ContractMD { + return &v.ContractMD } -// Initialize initializes PersonToken contract at the specified hardfork. -func (p *PersonToken) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { - if hf != p.ActiveIn() { +// Initialize initializes Vita contract at the specified hardfork. +func (v *Vita) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { + if hf != v.ActiveIn() { return nil } - cache := &PersonTokenCache{ + cache := &VitaCache{ tokenCount: 0, } - ic.DAO.SetCache(p.ID, cache) + ic.DAO.SetCache(v.ID, cache) // Initialize token counter to 0 - p.setTokenCounter(ic.DAO, 0) + v.setTokenCounter(ic.DAO, 0) return nil } -// InitializeCache fills native PersonToken cache from DAO on node restart. -func (p *PersonToken) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error { - cache := &PersonTokenCache{} +// InitializeCache fills native Vita cache from DAO on node restart. +func (v *Vita) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error { + cache := &VitaCache{} // Load token counter from storage - counter := p.getTokenCounter(d) + counter := v.getTokenCounter(d) cache.tokenCount = counter - d.SetCache(p.ID, cache) + d.SetCache(v.ID, cache) return nil } // OnPersist implements the Contract interface. -func (p *PersonToken) OnPersist(ic *interop.Context) error { +func (v *Vita) OnPersist(ic *interop.Context) error { return nil } // PostPersist implements the Contract interface. -func (p *PersonToken) PostPersist(ic *interop.Context) error { +func (v *Vita) PostPersist(ic *interop.Context) error { return nil } // ActiveIn returns the hardfork this contract activates in. -// PersonToken is always active (returns nil). -func (p *PersonToken) ActiveIn() *config.Hardfork { +// Vita is always active (returns nil). +func (v *Vita) ActiveIn() *config.Hardfork { return nil } @@ -510,31 +510,31 @@ func makeActiveRecoveryKey(tokenID uint64) []byte { // Token counter methods -func (p *PersonToken) getTokenCounter(d *dao.Simple) uint64 { - si := d.GetStorageItem(p.ID, []byte{prefixTokenCounter}) +func (v *Vita) getTokenCounter(d *dao.Simple) uint64 { + si := d.GetStorageItem(v.ID, []byte{prefixTokenCounter}) if si == nil { return 0 } return binary.BigEndian.Uint64(si) } -func (p *PersonToken) setTokenCounter(d *dao.Simple, count uint64) { +func (v *Vita) setTokenCounter(d *dao.Simple, count uint64) { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, count) - d.PutStorageItem(p.ID, []byte{prefixTokenCounter}, buf) + d.PutStorageItem(v.ID, []byte{prefixTokenCounter}, buf) } -func (p *PersonToken) getAndIncrementTokenCounter(d *dao.Simple) uint64 { - cache := d.GetRWCache(p.ID).(*PersonTokenCache) +func (v *Vita) getAndIncrementTokenCounter(d *dao.Simple) uint64 { + cache := d.GetRWCache(v.ID).(*VitaCache) tokenID := cache.tokenCount cache.tokenCount++ - p.setTokenCounter(d, cache.tokenCount) + v.setTokenCounter(d, cache.tokenCount) return tokenID } // Token storage methods -func (p *PersonToken) putToken(d *dao.Simple, token *state.PersonToken) error { +func (v *Vita) putToken(d *dao.Simple, token *state.Vita) error { item, err := token.ToStackItem() if err != nil { return err @@ -544,29 +544,29 @@ func (p *PersonToken) putToken(d *dao.Simple, token *state.PersonToken) error { return err } // Store by ID - d.PutStorageItem(p.ID, makeTokenByIDKey(token.TokenID), data) + d.PutStorageItem(v.ID, makeTokenByIDKey(token.TokenID), data) // Store owner -> tokenID mapping tokenIDBytes := make([]byte, 8) binary.BigEndian.PutUint64(tokenIDBytes, token.TokenID) - d.PutStorageItem(p.ID, makeTokenByOwnerKey(token.Owner), tokenIDBytes) + d.PutStorageItem(v.ID, makeTokenByOwnerKey(token.Owner), tokenIDBytes) // Store personHash -> tokenID mapping for uniqueness if len(token.PersonHash) > 0 { - d.PutStorageItem(p.ID, makePersonHashKey(token.PersonHash), tokenIDBytes) + d.PutStorageItem(v.ID, makePersonHashKey(token.PersonHash), tokenIDBytes) } return nil } -func (p *PersonToken) getTokenByOwnerInternal(d *dao.Simple, owner util.Uint160) (*state.PersonToken, error) { - si := d.GetStorageItem(p.ID, makeTokenByOwnerKey(owner)) +func (v *Vita) getTokenByOwnerInternal(d *dao.Simple, owner util.Uint160) (*state.Vita, error) { + si := d.GetStorageItem(v.ID, makeTokenByOwnerKey(owner)) if si == nil { return nil, nil } tokenID := binary.BigEndian.Uint64(si) - return p.getTokenByIDInternal(d, tokenID) + return v.getTokenByIDInternal(d, tokenID) } -func (p *PersonToken) getTokenByIDInternal(d *dao.Simple, tokenID uint64) (*state.PersonToken, error) { - si := d.GetStorageItem(p.ID, makeTokenByIDKey(tokenID)) +func (v *Vita) getTokenByIDInternal(d *dao.Simple, tokenID uint64) (*state.Vita, error) { + si := d.GetStorageItem(v.ID, makeTokenByIDKey(tokenID)) if si == nil { return nil, nil } @@ -574,30 +574,30 @@ func (p *PersonToken) getTokenByIDInternal(d *dao.Simple, tokenID uint64) (*stat if err != nil { return nil, err } - token := new(state.PersonToken) + token := new(state.Vita) if err := token.FromStackItem(item); err != nil { return nil, err } return token, nil } -func (p *PersonToken) tokenExistsForOwner(d *dao.Simple, owner util.Uint160) bool { - si := d.GetStorageItem(p.ID, makeTokenByOwnerKey(owner)) +func (v *Vita) tokenExistsForOwner(d *dao.Simple, owner util.Uint160) bool { + si := d.GetStorageItem(v.ID, makeTokenByOwnerKey(owner)) return si != nil } -func (p *PersonToken) personHashExists(d *dao.Simple, personHash []byte) bool { +func (v *Vita) personHashExists(d *dao.Simple, personHash []byte) bool { if len(personHash) == 0 { return false } - si := d.GetStorageItem(p.ID, makePersonHashKey(personHash)) + si := d.GetStorageItem(v.ID, makePersonHashKey(personHash)) return si != nil } // Contract methods -// register creates a new PersonToken. -func (p *PersonToken) register(ic *interop.Context, args []stackitem.Item) stackitem.Item { +// register creates a new Vita. +func (v *Vita) register(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) personHash := toBytes(args[1]) isEntity, err := args[2].TryBool() @@ -614,24 +614,24 @@ func (p *PersonToken) register(ic *interop.Context, args []stackitem.Item) stack // Check witness for the owner ok, err := runtime.CheckHashedWitness(ic, owner) if err != nil || !ok { - panic(ErrPersonTokenInvalidWitness) + panic(ErrVitaInvalidWitness) } // Check if token already exists for this owner - if p.tokenExistsForOwner(ic.DAO, owner) { + if v.tokenExistsForOwner(ic.DAO, owner) { panic(ErrTokenAlreadyExists) } // Check if person hash is already linked to another token - if p.personHashExists(ic.DAO, personHash) { + if v.personHashExists(ic.DAO, personHash) { panic(ErrPersonHashExists) } // Get next token ID - tokenID := p.getAndIncrementTokenCounter(ic.DAO) + tokenID := v.getAndIncrementTokenCounter(ic.DAO) // Create token - token := &state.PersonToken{ + token := &state.Vita{ TokenID: tokenID, Owner: owner, PersonHash: personHash, @@ -644,7 +644,7 @@ func (p *PersonToken) register(ic *interop.Context, args []stackitem.Item) stack } // Store token - if err := p.putToken(ic.DAO, token); err != nil { + if err := v.putToken(ic.DAO, token); err != nil { panic(err) } @@ -652,7 +652,7 @@ func (p *PersonToken) register(ic *interop.Context, args []stackitem.Item) stack tokenIDBytes := hash.Sha256(append(owner.BytesBE(), personHash...)).BytesBE() // Emit event - err = ic.AddNotification(p.Hash, PersonTokenCreatedEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, VitaCreatedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray(tokenIDBytes), stackitem.NewByteArray(owner.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(ic.Block.Index))), @@ -665,10 +665,10 @@ func (p *PersonToken) register(ic *interop.Context, args []stackitem.Item) stack } // getToken returns the token for the given owner. -func (p *PersonToken) getToken(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) getToken(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) - token, err := p.getTokenByOwnerInternal(ic.DAO, owner) + token, err := v.getTokenByOwnerInternal(ic.DAO, owner) if err != nil { panic(err) } @@ -684,10 +684,10 @@ func (p *PersonToken) getToken(ic *interop.Context, args []stackitem.Item) stack } // getTokenByID returns the token for the given token ID. -func (p *PersonToken) getTokenByID(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) getTokenByID(ic *interop.Context, args []stackitem.Item) stackitem.Item { tokenID := toBigInt(args[0]).Uint64() - token, err := p.getTokenByIDInternal(ic.DAO, tokenID) + token, err := v.getTokenByIDInternal(ic.DAO, tokenID) if err != nil { panic(err) } @@ -703,29 +703,29 @@ func (p *PersonToken) getTokenByID(ic *interop.Context, args []stackitem.Item) s } // exists checks if a token exists for the given owner. -func (p *PersonToken) exists(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) exists(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) - return stackitem.NewBool(p.tokenExistsForOwner(ic.DAO, owner)) + return stackitem.NewBool(v.tokenExistsForOwner(ic.DAO, owner)) } // totalSupply returns the total number of tokens. -func (p *PersonToken) totalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - cache := ic.DAO.GetROCache(p.ID).(*PersonTokenCache) +func (v *Vita) totalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + cache := ic.DAO.GetROCache(v.ID).(*VitaCache) return stackitem.NewBigInteger(big.NewInt(int64(cache.tokenCount))) } // suspend temporarily suspends a token (committee only). -func (p *PersonToken) suspend(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) suspend(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) reason := toString(args[1]) // Check committee - if !p.checkCommittee(ic) { + if !v.checkCommittee(ic) { panic(ErrNotCommittee) } // Get token - token, err := p.getTokenByOwnerInternal(ic.DAO, owner) + token, err := v.getTokenByOwnerInternal(ic.DAO, owner) if err != nil { panic(err) } @@ -744,7 +744,7 @@ func (p *PersonToken) suspend(ic *interop.Context, args []stackitem.Item) stacki token.UpdatedAt = ic.Block.Index // Store updated token - if err := p.putToken(ic.DAO, token); err != nil { + if err := v.putToken(ic.DAO, token); err != nil { panic(err) } @@ -756,7 +756,7 @@ func (p *PersonToken) suspend(ic *interop.Context, args []stackitem.Item) stacki binary.BigEndian.PutUint64(tokenIDBytes, token.TokenID) // Emit event - err = ic.AddNotification(p.Hash, PersonTokenSuspendedEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, VitaSuspendedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray(tokenIDBytes), stackitem.NewByteArray([]byte(reason)), stackitem.NewByteArray(caller.BytesBE()), @@ -769,16 +769,16 @@ func (p *PersonToken) suspend(ic *interop.Context, args []stackitem.Item) stacki } // reinstate reinstates a suspended token (committee only). -func (p *PersonToken) reinstate(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) reinstate(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) // Check committee - if !p.checkCommittee(ic) { + if !v.checkCommittee(ic) { panic(ErrNotCommittee) } // Get token - token, err := p.getTokenByOwnerInternal(ic.DAO, owner) + token, err := v.getTokenByOwnerInternal(ic.DAO, owner) if err != nil { panic(err) } @@ -797,7 +797,7 @@ func (p *PersonToken) reinstate(ic *interop.Context, args []stackitem.Item) stac token.UpdatedAt = ic.Block.Index // Store updated token - if err := p.putToken(ic.DAO, token); err != nil { + if err := v.putToken(ic.DAO, token); err != nil { panic(err) } @@ -809,7 +809,7 @@ func (p *PersonToken) reinstate(ic *interop.Context, args []stackitem.Item) stac binary.BigEndian.PutUint64(tokenIDBytes, token.TokenID) // Emit event - err = ic.AddNotification(p.Hash, PersonTokenReinstatedEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, VitaReinstatedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray(tokenIDBytes), stackitem.NewByteArray(caller.BytesBE()), })) @@ -821,17 +821,17 @@ func (p *PersonToken) reinstate(ic *interop.Context, args []stackitem.Item) stac } // revoke permanently revokes a token (committee only). -func (p *PersonToken) revoke(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) revoke(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) reason := toString(args[1]) // Check committee - if !p.checkCommittee(ic) { + if !v.checkCommittee(ic) { panic(ErrNotCommittee) } // Get token - token, err := p.getTokenByOwnerInternal(ic.DAO, owner) + token, err := v.getTokenByOwnerInternal(ic.DAO, owner) if err != nil { panic(err) } @@ -850,7 +850,7 @@ func (p *PersonToken) revoke(ic *interop.Context, args []stackitem.Item) stackit token.UpdatedAt = ic.Block.Index // Store updated token - if err := p.putToken(ic.DAO, token); err != nil { + if err := v.putToken(ic.DAO, token); err != nil { panic(err) } @@ -862,7 +862,7 @@ func (p *PersonToken) revoke(ic *interop.Context, args []stackitem.Item) stackit binary.BigEndian.PutUint64(tokenIDBytes, token.TokenID) // Emit event - err = ic.AddNotification(p.Hash, PersonTokenRevokedEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, VitaRevokedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray(tokenIDBytes), stackitem.NewByteArray([]byte(reason)), stackitem.NewByteArray(caller.BytesBE()), @@ -877,31 +877,31 @@ func (p *PersonToken) revoke(ic *interop.Context, args []stackitem.Item) stackit // Public methods for cross-native access // GetTokenByOwner returns the token for the given owner (for cross-native access). -func (p *PersonToken) GetTokenByOwner(d *dao.Simple, owner util.Uint160) (*state.PersonToken, error) { - return p.getTokenByOwnerInternal(d, owner) +func (v *Vita) GetTokenByOwner(d *dao.Simple, owner util.Uint160) (*state.Vita, error) { + return v.getTokenByOwnerInternal(d, owner) } // GetTokenByIDPublic returns the token for the given ID (for cross-native access). -func (p *PersonToken) GetTokenByIDPublic(d *dao.Simple, tokenID uint64) (*state.PersonToken, error) { - return p.getTokenByIDInternal(d, tokenID) +func (v *Vita) GetTokenByIDPublic(d *dao.Simple, tokenID uint64) (*state.Vita, error) { + return v.getTokenByIDInternal(d, tokenID) } // TokenExists returns true if a token exists for the given owner. -func (p *PersonToken) TokenExists(d *dao.Simple, owner util.Uint160) bool { - return p.tokenExistsForOwner(d, owner) +func (v *Vita) TokenExists(d *dao.Simple, owner util.Uint160) bool { + return v.tokenExistsForOwner(d, owner) } // IsAdultVerified checks if the owner has a verified "age_verified" attribute // indicating they are 18+ years old. Used for age-restricted purchases. // The attribute must be non-revoked and not expired. -func (p *PersonToken) IsAdultVerified(d *dao.Simple, owner util.Uint160) bool { - token, err := p.getTokenByOwnerInternal(d, owner) +func (v *Vita) IsAdultVerified(d *dao.Simple, owner util.Uint160) bool { + token, err := v.getTokenByOwnerInternal(d, owner) if err != nil || token == nil { return false } // Check for "age_verified" attribute - attr, err := p.getAttributeInternal(d, token.TokenID, "age_verified") + attr, err := v.getAttributeInternal(d, token.TokenID, "age_verified") if err != nil || attr == nil { return false } @@ -919,7 +919,7 @@ func (p *PersonToken) IsAdultVerified(d *dao.Simple, owner util.Uint160) bool { // Attribute storage methods -func (p *PersonToken) putAttribute(d *dao.Simple, tokenID uint64, attr *state.Attribute) error { +func (v *Vita) putAttribute(d *dao.Simple, tokenID uint64, attr *state.Attribute) error { item, err := attr.ToStackItem() if err != nil { return err @@ -928,12 +928,12 @@ func (p *PersonToken) putAttribute(d *dao.Simple, tokenID uint64, attr *state.At if err != nil { return err } - d.PutStorageItem(p.ID, makeAttributeKey(tokenID, attr.Key), data) + d.PutStorageItem(v.ID, makeAttributeKey(tokenID, attr.Key), data) return nil } -func (p *PersonToken) getAttributeInternal(d *dao.Simple, tokenID uint64, key string) (*state.Attribute, error) { - si := d.GetStorageItem(p.ID, makeAttributeKey(tokenID, key)) +func (v *Vita) getAttributeInternal(d *dao.Simple, tokenID uint64, key string) (*state.Attribute, error) { + si := d.GetStorageItem(v.ID, makeAttributeKey(tokenID, key)) if si == nil { return nil, nil } @@ -950,7 +950,7 @@ func (p *PersonToken) getAttributeInternal(d *dao.Simple, tokenID uint64, key st // setAttribute sets or updates an attribute on a token. // The caller must be either the token owner (self-attestation) or any other account (third-party attestation). -func (p *PersonToken) setAttribute(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) setAttribute(ic *interop.Context, args []stackitem.Item) stackitem.Item { tokenID := toBigInt(args[0]).Uint64() key := toString(args[1]) valueHash := toBytes(args[2]) @@ -970,7 +970,7 @@ func (p *PersonToken) setAttribute(ic *interop.Context, args []stackitem.Item) s } // Get token - token, err := p.getTokenByIDInternal(ic.DAO, tokenID) + token, err := v.getTokenByIDInternal(ic.DAO, tokenID) if err != nil { panic(err) } @@ -991,7 +991,7 @@ func (p *PersonToken) setAttribute(ic *interop.Context, args []stackitem.Item) s if attestor.Equals(token.Owner) { ok, err := runtime.CheckHashedWitness(ic, token.Owner) if err != nil || !ok { - panic(ErrPersonTokenInvalidWitness) + panic(ErrVitaInvalidWitness) } } @@ -1009,12 +1009,12 @@ func (p *PersonToken) setAttribute(ic *interop.Context, args []stackitem.Item) s } // Store attribute - if err := p.putAttribute(ic.DAO, tokenID, attr); err != nil { + if err := v.putAttribute(ic.DAO, tokenID, attr); err != nil { panic(err) } // Emit event - err = ic.AddNotification(p.Hash, AttributeSetEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, AttributeSetEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(tokenID))), stackitem.NewByteArray([]byte(key)), stackitem.NewByteArray(attestor.BytesBE()), @@ -1028,11 +1028,11 @@ func (p *PersonToken) setAttribute(ic *interop.Context, args []stackitem.Item) s } // getAttribute returns an attribute for a token. -func (p *PersonToken) getAttribute(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) getAttribute(ic *interop.Context, args []stackitem.Item) stackitem.Item { tokenID := toBigInt(args[0]).Uint64() key := toString(args[1]) - attr, err := p.getAttributeInternal(ic.DAO, tokenID, key) + attr, err := v.getAttributeInternal(ic.DAO, tokenID, key) if err != nil { panic(err) } @@ -1049,13 +1049,13 @@ func (p *PersonToken) getAttribute(ic *interop.Context, args []stackitem.Item) s // revokeAttribute revokes an attribute. // Can be called by the token owner, the original attestor, or committee. -func (p *PersonToken) revokeAttribute(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) revokeAttribute(ic *interop.Context, args []stackitem.Item) stackitem.Item { tokenID := toBigInt(args[0]).Uint64() key := toString(args[1]) reason := toString(args[2]) // Get token - token, err := p.getTokenByIDInternal(ic.DAO, tokenID) + token, err := v.getTokenByIDInternal(ic.DAO, tokenID) if err != nil { panic(err) } @@ -1064,7 +1064,7 @@ func (p *PersonToken) revokeAttribute(ic *interop.Context, args []stackitem.Item } // Get attribute - attr, err := p.getAttributeInternal(ic.DAO, tokenID, key) + attr, err := v.getAttributeInternal(ic.DAO, tokenID, key) if err != nil { panic(err) } @@ -1081,15 +1081,15 @@ func (p *PersonToken) revokeAttribute(ic *interop.Context, args []stackitem.Item caller := ic.VM.GetCallingScriptHash() isOwner := caller.Equals(token.Owner) isAttestor := caller.Equals(attr.Attestor) - isCommittee := p.checkCommittee(ic) + isCommittee := v.checkCommittee(ic) if isOwner { ok, err := runtime.CheckHashedWitness(ic, token.Owner) if err != nil || !ok { - panic(ErrPersonTokenInvalidWitness) + panic(ErrVitaInvalidWitness) } } else if !isAttestor && !isCommittee { - panic(ErrPersonTokenInvalidWitness) + panic(ErrVitaInvalidWitness) } // Revoke attribute @@ -1097,12 +1097,12 @@ func (p *PersonToken) revokeAttribute(ic *interop.Context, args []stackitem.Item attr.RevokedAt = ic.Block.Index // Store updated attribute - if err := p.putAttribute(ic.DAO, tokenID, attr); err != nil { + if err := v.putAttribute(ic.DAO, tokenID, attr); err != nil { panic(err) } // Emit event - err = ic.AddNotification(p.Hash, AttributeRevokedEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, AttributeRevokedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(tokenID))), stackitem.NewByteArray([]byte(key)), stackitem.NewByteArray(caller.BytesBE()), @@ -1117,13 +1117,13 @@ func (p *PersonToken) revokeAttribute(ic *interop.Context, args []stackitem.Item // verifyAttribute verifies that an attribute exists and its hash matches. // Returns true if the attribute exists, is not revoked, not expired, and hash matches. -func (p *PersonToken) verifyAttribute(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) verifyAttribute(ic *interop.Context, args []stackitem.Item) stackitem.Item { tokenID := toBigInt(args[0]).Uint64() key := toString(args[1]) expectedHash := toBytes(args[2]) // Get attribute - attr, err := p.getAttributeInternal(ic.DAO, tokenID, key) + attr, err := v.getAttributeInternal(ic.DAO, tokenID, key) if err != nil { panic(err) } @@ -1157,13 +1157,13 @@ func (p *PersonToken) verifyAttribute(ic *interop.Context, args []stackitem.Item // Public methods for cross-native attribute access // GetAttribute returns an attribute for the given token and key (for cross-native access). -func (p *PersonToken) GetAttribute(d *dao.Simple, tokenID uint64, key string) (*state.Attribute, error) { - return p.getAttributeInternal(d, tokenID, key) +func (v *Vita) GetAttribute(d *dao.Simple, tokenID uint64, key string) (*state.Attribute, error) { + return v.getAttributeInternal(d, tokenID, key) } // Challenge storage methods -func (p *PersonToken) putChallenge(d *dao.Simple, challenge *state.AuthChallenge) error { +func (v *Vita) putChallenge(d *dao.Simple, challenge *state.AuthChallenge) error { item, err := challenge.ToStackItem() if err != nil { return err @@ -1172,12 +1172,12 @@ func (p *PersonToken) putChallenge(d *dao.Simple, challenge *state.AuthChallenge if err != nil { return err } - d.PutStorageItem(p.ID, makeChallengeKey(challenge.ChallengeID), data) + d.PutStorageItem(v.ID, makeChallengeKey(challenge.ChallengeID), data) return nil } -func (p *PersonToken) getChallengeInternal(d *dao.Simple, challengeID util.Uint256) (*state.AuthChallenge, error) { - si := d.GetStorageItem(p.ID, makeChallengeKey(challengeID)) +func (v *Vita) getChallengeInternal(d *dao.Simple, challengeID util.Uint256) (*state.AuthChallenge, error) { + si := d.GetStorageItem(v.ID, makeChallengeKey(challengeID)) if si == nil { return nil, nil } @@ -1192,14 +1192,14 @@ func (p *PersonToken) getChallengeInternal(d *dao.Simple, challengeID util.Uint2 return challenge, nil } -func (p *PersonToken) setLastAuth(d *dao.Simple, tokenID uint64, purpose string, blockHeight uint32) { +func (v *Vita) setLastAuth(d *dao.Simple, tokenID uint64, purpose string, blockHeight uint32) { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, blockHeight) - d.PutStorageItem(p.ID, makeLastAuthKey(tokenID, purpose), buf) + d.PutStorageItem(v.ID, makeLastAuthKey(tokenID, purpose), buf) } -func (p *PersonToken) getLastAuth(d *dao.Simple, tokenID uint64, purpose string) uint32 { - si := d.GetStorageItem(p.ID, makeLastAuthKey(tokenID, purpose)) +func (v *Vita) getLastAuth(d *dao.Simple, tokenID uint64, purpose string) uint32 { + si := d.GetStorageItem(v.ID, makeLastAuthKey(tokenID, purpose)) if si == nil { return 0 } @@ -1211,7 +1211,7 @@ const defaultChallengeExpiry uint32 = 20 // createChallenge creates a new authentication challenge for a token owner. // Anyone can create a challenge for any token owner. -func (p *PersonToken) createChallenge(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) createChallenge(ic *interop.Context, args []stackitem.Item) stackitem.Item { owner := toUint160(args[0]) purpose := toString(args[1]) @@ -1221,7 +1221,7 @@ func (p *PersonToken) createChallenge(ic *interop.Context, args []stackitem.Item } // Get token - token, err := p.getTokenByOwnerInternal(ic.DAO, owner) + token, err := v.getTokenByOwnerInternal(ic.DAO, owner) if err != nil { panic(err) } @@ -1257,12 +1257,12 @@ func (p *PersonToken) createChallenge(ic *interop.Context, args []stackitem.Item } // Store challenge - if err := p.putChallenge(ic.DAO, challenge); err != nil { + if err := v.putChallenge(ic.DAO, challenge); err != nil { panic(err) } // Emit event - err = ic.AddNotification(p.Hash, AuthChallengeCreatedEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, AuthChallengeCreatedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray(challengeID.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(token.TokenID))), stackitem.NewByteArray([]byte(purpose)), @@ -1281,7 +1281,7 @@ func (p *PersonToken) createChallenge(ic *interop.Context, args []stackitem.Item } // getChallenge returns a challenge by ID. -func (p *PersonToken) getChallenge(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) getChallenge(ic *interop.Context, args []stackitem.Item) stackitem.Item { challengeIDBytes := toBytes(args[0]) if len(challengeIDBytes) != util.Uint256Size { panic("invalid challenge ID length") @@ -1291,7 +1291,7 @@ func (p *PersonToken) getChallenge(ic *interop.Context, args []stackitem.Item) s panic(err) } - challenge, err := p.getChallengeInternal(ic.DAO, challengeID) + challenge, err := v.getChallengeInternal(ic.DAO, challengeID) if err != nil { panic(err) } @@ -1308,7 +1308,7 @@ func (p *PersonToken) getChallenge(ic *interop.Context, args []stackitem.Item) s // fulfillChallenge fulfills an authentication challenge by providing a valid signature. // The signature must be from the token owner over the challenge nonce. -func (p *PersonToken) fulfillChallenge(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) fulfillChallenge(ic *interop.Context, args []stackitem.Item) stackitem.Item { challengeIDBytes := toBytes(args[0]) if len(challengeIDBytes) != util.Uint256Size { panic("invalid challenge ID length") @@ -1320,7 +1320,7 @@ func (p *PersonToken) fulfillChallenge(ic *interop.Context, args []stackitem.Ite _ = toBytes(args[1]) // signature - validation happens via witness // Get challenge - challenge, err := p.getChallengeInternal(ic.DAO, challengeID) + challenge, err := v.getChallengeInternal(ic.DAO, challengeID) if err != nil { panic(err) } @@ -1339,7 +1339,7 @@ func (p *PersonToken) fulfillChallenge(ic *interop.Context, args []stackitem.Ite } // Get token - token, err := p.getTokenByIDInternal(ic.DAO, challenge.TokenID) + token, err := v.getTokenByIDInternal(ic.DAO, challenge.TokenID) if err != nil { panic(err) } @@ -1359,15 +1359,15 @@ func (p *PersonToken) fulfillChallenge(ic *interop.Context, args []stackitem.Ite challenge.FulfilledAt = ic.Block.Index // Store updated challenge - if err := p.putChallenge(ic.DAO, challenge); err != nil { + if err := v.putChallenge(ic.DAO, challenge); err != nil { panic(err) } // Record last auth time for this token and purpose - p.setLastAuth(ic.DAO, challenge.TokenID, challenge.Purpose, ic.Block.Index) + v.setLastAuth(ic.DAO, challenge.TokenID, challenge.Purpose, ic.Block.Index) // Emit success event - err = ic.AddNotification(p.Hash, AuthenticationSuccessEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, AuthenticationSuccessEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(challenge.TokenID))), stackitem.NewByteArray([]byte(challenge.Purpose)), stackitem.NewBigInteger(big.NewInt(int64(ic.Block.Index))), @@ -1381,13 +1381,13 @@ func (p *PersonToken) fulfillChallenge(ic *interop.Context, args []stackitem.Ite // verifyAuth checks if a token has authenticated recently for a given purpose. // Returns true if the last successful authentication for the purpose was within maxAge blocks. -func (p *PersonToken) verifyAuth(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) verifyAuth(ic *interop.Context, args []stackitem.Item) stackitem.Item { tokenID := toBigInt(args[0]).Uint64() purpose := toString(args[1]) maxAge := uint32(toBigInt(args[2]).Int64()) // Get last auth time - lastAuth := p.getLastAuth(ic.DAO, tokenID, purpose) + lastAuth := v.getLastAuth(ic.DAO, tokenID, purpose) if lastAuth == 0 { return stackitem.NewBool(false) } @@ -1403,8 +1403,8 @@ func (p *PersonToken) verifyAuth(ic *interop.Context, args []stackitem.Item) sta // Public methods for cross-native authentication access // GetLastAuth returns the last authentication block height for a token and purpose. -func (p *PersonToken) GetLastAuth(d *dao.Simple, tokenID uint64, purpose string) uint32 { - return p.getLastAuth(d, tokenID, purpose) +func (v *Vita) GetLastAuth(d *dao.Simple, tokenID uint64, purpose string) uint32 { + return v.getLastAuth(d, tokenID, purpose) } // Recovery configuration constants @@ -1419,7 +1419,7 @@ const ( // Recovery storage methods -func (p *PersonToken) putRecoveryRequest(d *dao.Simple, req *state.RecoveryRequest) error { +func (v *Vita) putRecoveryRequest(d *dao.Simple, req *state.RecoveryRequest) error { item, err := req.ToStackItem() if err != nil { return err @@ -1428,12 +1428,12 @@ func (p *PersonToken) putRecoveryRequest(d *dao.Simple, req *state.RecoveryReque if err != nil { return err } - d.PutStorageItem(p.ID, makeRecoveryKey(req.RequestID), data) + d.PutStorageItem(v.ID, makeRecoveryKey(req.RequestID), data) return nil } -func (p *PersonToken) getRecoveryRequestInternal(d *dao.Simple, requestID util.Uint256) (*state.RecoveryRequest, error) { - si := d.GetStorageItem(p.ID, makeRecoveryKey(requestID)) +func (v *Vita) getRecoveryRequestInternal(d *dao.Simple, requestID util.Uint256) (*state.RecoveryRequest, error) { + si := d.GetStorageItem(v.ID, makeRecoveryKey(requestID)) if si == nil { return nil, nil } @@ -1448,12 +1448,12 @@ func (p *PersonToken) getRecoveryRequestInternal(d *dao.Simple, requestID util.U return req, nil } -func (p *PersonToken) setActiveRecovery(d *dao.Simple, tokenID uint64, requestID util.Uint256) { - d.PutStorageItem(p.ID, makeActiveRecoveryKey(tokenID), requestID.BytesBE()) +func (v *Vita) setActiveRecovery(d *dao.Simple, tokenID uint64, requestID util.Uint256) { + d.PutStorageItem(v.ID, makeActiveRecoveryKey(tokenID), requestID.BytesBE()) } -func (p *PersonToken) getActiveRecovery(d *dao.Simple, tokenID uint64) *util.Uint256 { - si := d.GetStorageItem(p.ID, makeActiveRecoveryKey(tokenID)) +func (v *Vita) getActiveRecovery(d *dao.Simple, tokenID uint64) *util.Uint256 { + si := d.GetStorageItem(v.ID, makeActiveRecoveryKey(tokenID)) if si == nil { return nil } @@ -1464,13 +1464,13 @@ func (p *PersonToken) getActiveRecovery(d *dao.Simple, tokenID uint64) *util.Uin return &requestID } -func (p *PersonToken) clearActiveRecovery(d *dao.Simple, tokenID uint64) { - d.DeleteStorageItem(p.ID, makeActiveRecoveryKey(tokenID)) +func (v *Vita) clearActiveRecovery(d *dao.Simple, tokenID uint64) { + d.DeleteStorageItem(v.ID, makeActiveRecoveryKey(tokenID)) } // initiateRecovery starts a key recovery process for a token. // Anyone can initiate recovery for any token. -func (p *PersonToken) initiateRecovery(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) initiateRecovery(ic *interop.Context, args []stackitem.Item) stackitem.Item { tokenID := toBigInt(args[0]).Uint64() newOwner := toUint160(args[1]) evidence := toBytes(args[2]) @@ -1481,7 +1481,7 @@ func (p *PersonToken) initiateRecovery(ic *interop.Context, args []stackitem.Ite } // Get token - token, err := p.getTokenByIDInternal(ic.DAO, tokenID) + token, err := v.getTokenByIDInternal(ic.DAO, tokenID) if err != nil { panic(err) } @@ -1500,7 +1500,7 @@ func (p *PersonToken) initiateRecovery(ic *interop.Context, args []stackitem.Ite } // Check no active recovery exists - if p.getActiveRecovery(ic.DAO, tokenID) != nil { + if v.getActiveRecovery(ic.DAO, tokenID) != nil { panic(ErrRecoveryAlreadyActive) } @@ -1529,23 +1529,23 @@ func (p *PersonToken) initiateRecovery(ic *interop.Context, args []stackitem.Ite } // Store recovery request - if err := p.putRecoveryRequest(ic.DAO, req); err != nil { + if err := v.putRecoveryRequest(ic.DAO, req); err != nil { panic(err) } // Mark active recovery for token - p.setActiveRecovery(ic.DAO, tokenID, requestID) + v.setActiveRecovery(ic.DAO, tokenID, requestID) // Update token status to recovering token.Status = state.TokenStatusRecovering token.StatusReason = "recovery initiated" token.UpdatedAt = ic.Block.Index - if err := p.putToken(ic.DAO, token); err != nil { + if err := v.putToken(ic.DAO, token); err != nil { panic(err) } // Emit event - err = ic.AddNotification(p.Hash, RecoveryInitiatedEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, RecoveryInitiatedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(tokenID))), stackitem.NewByteArray(requestID.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(req.DelayUntil))), @@ -1558,7 +1558,7 @@ func (p *PersonToken) initiateRecovery(ic *interop.Context, args []stackitem.Ite } // approveRecovery approves a recovery request (committee member only). -func (p *PersonToken) approveRecovery(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) approveRecovery(ic *interop.Context, args []stackitem.Item) stackitem.Item { requestIDBytes := toBytes(args[0]) if len(requestIDBytes) != util.Uint256Size { panic("invalid request ID length") @@ -1569,12 +1569,12 @@ func (p *PersonToken) approveRecovery(ic *interop.Context, args []stackitem.Item } // Check committee - if !p.checkCommittee(ic) { + if !v.checkCommittee(ic) { panic(ErrNotCommittee) } // Get recovery request - req, err := p.getRecoveryRequestInternal(ic.DAO, requestID) + req, err := v.getRecoveryRequestInternal(ic.DAO, requestID) if err != nil { panic(err) } @@ -1611,12 +1611,12 @@ func (p *PersonToken) approveRecovery(ic *interop.Context, args []stackitem.Item } // Store updated request - if err := p.putRecoveryRequest(ic.DAO, req); err != nil { + if err := v.putRecoveryRequest(ic.DAO, req); err != nil { panic(err) } // Emit event - err = ic.AddNotification(p.Hash, RecoveryApprovalEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, RecoveryApprovalEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray(requestID.BytesBE()), stackitem.NewByteArray(approver.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(len(req.Approvals)))), @@ -1630,7 +1630,7 @@ func (p *PersonToken) approveRecovery(ic *interop.Context, args []stackitem.Item } // executeRecovery executes an approved recovery request after the delay period. -func (p *PersonToken) executeRecovery(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) executeRecovery(ic *interop.Context, args []stackitem.Item) stackitem.Item { requestIDBytes := toBytes(args[0]) if len(requestIDBytes) != util.Uint256Size { panic("invalid request ID length") @@ -1641,7 +1641,7 @@ func (p *PersonToken) executeRecovery(ic *interop.Context, args []stackitem.Item } // Get recovery request - req, err := p.getRecoveryRequestInternal(ic.DAO, requestID) + req, err := v.getRecoveryRequestInternal(ic.DAO, requestID) if err != nil { panic(err) } @@ -1665,7 +1665,7 @@ func (p *PersonToken) executeRecovery(ic *interop.Context, args []stackitem.Item } // Get token - token, err := p.getTokenByIDInternal(ic.DAO, req.TokenID) + token, err := v.getTokenByIDInternal(ic.DAO, req.TokenID) if err != nil { panic(err) } @@ -1677,7 +1677,7 @@ func (p *PersonToken) executeRecovery(ic *interop.Context, args []stackitem.Item oldOwner := token.Owner // Delete old owner -> tokenID mapping - ic.DAO.DeleteStorageItem(p.ID, makeTokenByOwnerKey(oldOwner)) + ic.DAO.DeleteStorageItem(v.ID, makeTokenByOwnerKey(oldOwner)) // Update token owner token.Owner = req.NewOwner @@ -1686,21 +1686,21 @@ func (p *PersonToken) executeRecovery(ic *interop.Context, args []stackitem.Item token.UpdatedAt = ic.Block.Index // Store updated token (this will create new owner -> tokenID mapping) - if err := p.putToken(ic.DAO, token); err != nil { + if err := v.putToken(ic.DAO, token); err != nil { panic(err) } // Update request status req.Status = state.RecoveryStatusExecuted - if err := p.putRecoveryRequest(ic.DAO, req); err != nil { + if err := v.putRecoveryRequest(ic.DAO, req); err != nil { panic(err) } // Clear active recovery - p.clearActiveRecovery(ic.DAO, req.TokenID) + v.clearActiveRecovery(ic.DAO, req.TokenID) // Emit event - err = ic.AddNotification(p.Hash, RecoveryExecutedEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, RecoveryExecutedEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(req.TokenID))), stackitem.NewByteArray(oldOwner.BytesBE()), stackitem.NewByteArray(req.NewOwner.BytesBE()), @@ -1714,7 +1714,7 @@ func (p *PersonToken) executeRecovery(ic *interop.Context, args []stackitem.Item // cancelRecovery cancels an active recovery request. // Can be called by the original token owner or the requester. -func (p *PersonToken) cancelRecovery(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) cancelRecovery(ic *interop.Context, args []stackitem.Item) stackitem.Item { requestIDBytes := toBytes(args[0]) if len(requestIDBytes) != util.Uint256Size { panic("invalid request ID length") @@ -1725,7 +1725,7 @@ func (p *PersonToken) cancelRecovery(ic *interop.Context, args []stackitem.Item) } // Get recovery request - req, err := p.getRecoveryRequestInternal(ic.DAO, requestID) + req, err := v.getRecoveryRequestInternal(ic.DAO, requestID) if err != nil { panic(err) } @@ -1739,7 +1739,7 @@ func (p *PersonToken) cancelRecovery(ic *interop.Context, args []stackitem.Item) } // Get token - token, err := p.getTokenByIDInternal(ic.DAO, req.TokenID) + token, err := v.getTokenByIDInternal(ic.DAO, req.TokenID) if err != nil { panic(err) } @@ -1751,20 +1751,20 @@ func (p *PersonToken) cancelRecovery(ic *interop.Context, args []stackitem.Item) caller := ic.VM.GetCallingScriptHash() isOwner := caller.Equals(token.Owner) isRequester := caller.Equals(req.Requester) - isCommittee := p.checkCommittee(ic) + isCommittee := v.checkCommittee(ic) if isOwner { ok, err := runtime.CheckHashedWitness(ic, token.Owner) if err != nil || !ok { - panic(ErrPersonTokenInvalidWitness) + panic(ErrVitaInvalidWitness) } } else if !isRequester && !isCommittee { - panic(ErrPersonTokenInvalidWitness) + panic(ErrVitaInvalidWitness) } // Update request status req.Status = state.RecoveryStatusDenied - if err := p.putRecoveryRequest(ic.DAO, req); err != nil { + if err := v.putRecoveryRequest(ic.DAO, req); err != nil { panic(err) } @@ -1772,15 +1772,15 @@ func (p *PersonToken) cancelRecovery(ic *interop.Context, args []stackitem.Item) token.Status = state.TokenStatusActive token.StatusReason = "" token.UpdatedAt = ic.Block.Index - if err := p.putToken(ic.DAO, token); err != nil { + if err := v.putToken(ic.DAO, token); err != nil { panic(err) } // Clear active recovery - p.clearActiveRecovery(ic.DAO, req.TokenID) + v.clearActiveRecovery(ic.DAO, req.TokenID) // Emit event - err = ic.AddNotification(p.Hash, RecoveryCancelledEvent, stackitem.NewArray([]stackitem.Item{ + err = ic.AddNotification(v.Hash, RecoveryCancelledEvent, stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray(requestID.BytesBE()), stackitem.NewByteArray(caller.BytesBE()), })) @@ -1792,7 +1792,7 @@ func (p *PersonToken) cancelRecovery(ic *interop.Context, args []stackitem.Item) } // getRecoveryRequest returns a recovery request by ID. -func (p *PersonToken) getRecoveryRequest(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) getRecoveryRequest(ic *interop.Context, args []stackitem.Item) stackitem.Item { requestIDBytes := toBytes(args[0]) if len(requestIDBytes) != util.Uint256Size { panic("invalid request ID length") @@ -1802,7 +1802,7 @@ func (p *PersonToken) getRecoveryRequest(ic *interop.Context, args []stackitem.I panic(err) } - req, err := p.getRecoveryRequestInternal(ic.DAO, requestID) + req, err := v.getRecoveryRequestInternal(ic.DAO, requestID) if err != nil { panic(err) } @@ -1820,29 +1820,29 @@ func (p *PersonToken) getRecoveryRequest(ic *interop.Context, args []stackitem.I // Public methods for cross-native recovery access // GetRecoveryRequest returns a recovery request by ID (for cross-native access). -func (p *PersonToken) GetRecoveryRequest(d *dao.Simple, requestID util.Uint256) (*state.RecoveryRequest, error) { - return p.getRecoveryRequestInternal(d, requestID) +func (v *Vita) GetRecoveryRequest(d *dao.Simple, requestID util.Uint256) (*state.RecoveryRequest, error) { + return v.getRecoveryRequestInternal(d, requestID) } // GetActiveRecoveryForToken returns the active recovery request ID for a token (for cross-native access). -func (p *PersonToken) GetActiveRecoveryForToken(d *dao.Simple, tokenID uint64) *util.Uint256 { - return p.getActiveRecovery(d, tokenID) +func (v *Vita) GetActiveRecoveryForToken(d *dao.Simple, tokenID uint64) *util.Uint256 { + return v.getActiveRecovery(d, tokenID) } // Cross-contract integration methods -// validateCaller validates that the calling contract's owner has a valid PersonToken. +// validateCaller validates that the calling contract's owner has a valid Vita. // Returns the tokenID and core roles for the caller. -func (p *PersonToken) validateCaller(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) validateCaller(ic *interop.Context, args []stackitem.Item) stackitem.Item { caller := ic.VM.GetCallingScriptHash() // Get token by caller address - token, err := p.getTokenByOwnerInternal(ic.DAO, caller) + token, err := v.getTokenByOwnerInternal(ic.DAO, caller) if err != nil { panic(err) } if token == nil { - panic(ErrCallerHasNoToken) + panic(ErrCallerHasNoVita) } // Check token is active @@ -1860,7 +1860,7 @@ func (p *PersonToken) validateCaller(ic *interop.Context, args []stackitem.Item) } // Determine core roles - roles := p.getCoreRoles(ic, token) + roles := v.getCoreRoles(ic, token) // Return struct with tokenID and roles return stackitem.NewArray([]stackitem.Item{ @@ -1870,19 +1870,19 @@ func (p *PersonToken) validateCaller(ic *interop.Context, args []stackitem.Item) } // getCoreRoles determines the core roles for a token holder. -func (p *PersonToken) getCoreRoles(ic *interop.Context, token *state.PersonToken) uint64 { +func (v *Vita) getCoreRoles(ic *interop.Context, token *state.Vita) uint64 { var roles uint64 // All active token holders have User role roles |= 1 << uint64(CoreRoleUser) // Check if user is verified (has non-expired, non-revoked attributes) - if p.hasVerifiedAttributes(ic.DAO, token.TokenID) { + if v.hasVerifiedAttributes(ic.DAO, token.TokenID) { roles |= 1 << uint64(CoreRoleVerified) } // Check if user is a committee member - if p.checkCommittee(ic) { + if v.checkCommittee(ic) { roles |= 1 << uint64(CoreRoleCommittee) roles |= 1 << uint64(CoreRoleAttestor) // Committee members can attest roles |= 1 << uint64(CoreRoleRecovery) // Committee members participate in recovery @@ -1892,7 +1892,7 @@ func (p *PersonToken) getCoreRoles(ic *interop.Context, token *state.PersonToken } // hasVerifiedAttributes checks if a token has any valid (non-expired, non-revoked) attributes. -func (p *PersonToken) hasVerifiedAttributes(d *dao.Simple, tokenID uint64) bool { +func (v *Vita) hasVerifiedAttributes(d *dao.Simple, tokenID uint64) bool { // For now, just check if any attribute exists // In a full implementation, this would scan attributes and check expiry/revocation prefix := make([]byte, 9) @@ -1901,7 +1901,7 @@ func (p *PersonToken) hasVerifiedAttributes(d *dao.Simple, tokenID uint64) bool // Check if any attribute storage items exist for this token var found bool - d.Seek(p.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool { + d.Seek(v.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool { found = true return false // Stop after finding first }) @@ -1910,18 +1910,18 @@ func (p *PersonToken) hasVerifiedAttributes(d *dao.Simple, tokenID uint64) bool // requireRole checks if the caller has a specific role ID (for RoleRegistry integration). // This is a stub that will be extended when RoleRegistry is implemented. -func (p *PersonToken) requireRole(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (v *Vita) requireRole(ic *interop.Context, args []stackitem.Item) stackitem.Item { roleID := toBigInt(args[0]).Uint64() caller := ic.VM.GetCallingScriptHash() // Get token by caller address - token, err := p.getTokenByOwnerInternal(ic.DAO, caller) + token, err := v.getTokenByOwnerInternal(ic.DAO, caller) if err != nil { panic(err) } if token == nil { - panic(ErrCallerHasNoToken) + panic(ErrCallerHasNoVita) } // Check token is active @@ -1938,7 +1938,7 @@ func (p *PersonToken) requireRole(ic *interop.Context, args []stackitem.Item) st // For non-zero roles, check if it maps to a core role if roleID <= uint64(CoreRoleRecovery) { - roles := p.getCoreRoles(ic, token) + roles := v.getCoreRoles(ic, token) if roles&(1< CoreRoleRecovery { - panic(ErrPersonTokenInvalidRole) + panic(ErrVitaInvalidRole) } caller := ic.VM.GetCallingScriptHash() // Get token by caller address - token, err := p.getTokenByOwnerInternal(ic.DAO, caller) + token, err := v.getTokenByOwnerInternal(ic.DAO, caller) if err != nil { panic(err) } if token == nil { - panic(ErrCallerHasNoToken) + panic(ErrCallerHasNoVita) } // Check token is active @@ -1977,7 +1977,7 @@ func (p *PersonToken) requireCoreRole(ic *interop.Context, args []stackitem.Item } // Check if user has the required core role - roles := p.getCoreRoles(ic, token) + roles := v.getCoreRoles(ic, token) if roles&(1<