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

704 lines
21 KiB
Go
Executable File

package native
import (
"encoding/binary"
"errors"
"math/big"
"git.marketally.com/tutus-one/tutus-chain/pkg/config"
"git.marketally.com/tutus-one/tutus-chain/pkg/core/dao"
"git.marketally.com/tutus-one/tutus-chain/pkg/core/interop"
"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativeids"
"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
"git.marketally.com/tutus-one/tutus-chain/pkg/core/state"
"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract"
"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/manifest"
"git.marketally.com/tutus-one/tutus-chain/pkg/util"
"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)
// Annos represents the lifespan/years native contract for tracking Vita holder ages.
type Annos struct {
interop.ContractMD
Tutus ITutus
Vita IVita
}
// AnnosCache represents the cached state for Annos contract.
type AnnosCache struct {
recordCount uint64
}
// Storage key prefixes for Annos.
const (
annosPrefixLifespan byte = 0x01 // vitaID -> LifespanRecord
annosPrefixByOwner byte = 0x02 // owner -> vitaID
annosPrefixRecordCounter byte = 0xF0 // -> uint64
annosPrefixConfig byte = 0xFF // -> AnnosConfig
)
// Event names for Annos.
const (
BirthRegisteredEvent = "BirthRegistered"
DeathRecordedEvent = "DeathRecorded"
LifeStageChangedEvent = "LifeStageChanged"
)
// Seconds per year (approximately, for age calculation).
const secondsPerYear = 365 * 24 * 60 * 60
// Various errors for Annos.
var (
ErrAnnosRecordNotFound = errors.New("lifespan record not found")
ErrAnnosRecordExists = errors.New("lifespan record already exists")
ErrAnnosNoVita = errors.New("owner must have an active Vita")
ErrAnnosNotCommittee = errors.New("invalid committee signature")
ErrAnnosAlreadyDeceased = errors.New("person is already marked as deceased")
ErrAnnosInvalidTimestamp = errors.New("invalid birth timestamp")
ErrAnnosNotVita = errors.New("only Vita contract can call this method")
)
var (
_ interop.Contract = (*Annos)(nil)
_ dao.NativeContractCache = (*AnnosCache)(nil)
)
// Copy implements NativeContractCache interface.
func (c *AnnosCache) Copy() dao.NativeContractCache {
return &AnnosCache{
recordCount: c.recordCount,
}
}
// checkCommittee checks if the caller has committee authority.
func (a *Annos) checkCommittee(ic *interop.Context) bool {
return a.Tutus.CheckCommittee(ic)
}
// newAnnos creates a new Annos native contract.
func newAnnos() *Annos {
ann := &Annos{
ContractMD: *interop.NewContractMD(nativenames.Annos, nativeids.Annos),
}
defer ann.BuildHFSpecificMD(ann.ActiveIn())
// ===== Age Queries =====
// getAge - Get current age in years
desc := NewDescriptor("getAge", smartcontract.IntegerType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md := NewMethodAndPrice(ann.getAge, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// getAgeAtTime - Get age at a specific timestamp
desc = NewDescriptor("getAgeAtTime", smartcontract.IntegerType,
manifest.NewParameter("owner", smartcontract.Hash160Type),
manifest.NewParameter("timestamp", smartcontract.IntegerType))
md = NewMethodAndPrice(ann.getAgeAtTime, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// getLifeStage - Get life stage (Child, Youth, Adult, Elder)
desc = NewDescriptor("getLifeStage", smartcontract.IntegerType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(ann.getLifeStage, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// getBirthTimestamp - Get actual birth timestamp
desc = NewDescriptor("getBirthTimestamp", smartcontract.IntegerType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(ann.getBirthTimestamp, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// getRecord - Get full lifespan record
desc = NewDescriptor("getRecord", smartcontract.ArrayType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(ann.getRecord, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// getRecordByVitaID - Get record by Vita ID
desc = NewDescriptor("getRecordByVitaID", smartcontract.ArrayType,
manifest.NewParameter("vitaID", smartcontract.IntegerType))
md = NewMethodAndPrice(ann.getRecordByVitaID, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// ===== Entitlement Checks =====
// isVotingAge - Check if at voting age
desc = NewDescriptor("isVotingAge", smartcontract.BoolType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(ann.isVotingAge, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// isAdult - Check if adult
desc = NewDescriptor("isAdult", smartcontract.BoolType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(ann.isAdult, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// isRetirementAge - Check if at retirement age
desc = NewDescriptor("isRetirementAge", smartcontract.BoolType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(ann.isRetirementAge, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// isAlive - Check if person is alive
desc = NewDescriptor("isAlive", smartcontract.BoolType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = NewMethodAndPrice(ann.isAlive, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// ===== Life Events (Committee Only) =====
// recordDeath - Record death (committee only)
desc = NewDescriptor("recordDeath", smartcontract.BoolType,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("deathTimestamp", smartcontract.IntegerType))
md = NewMethodAndPrice(ann.recordDeath, 1<<17, callflag.States|callflag.AllowNotify)
ann.AddMethod(md, desc)
// ===== Query Methods =====
// getConfig - Get Annos configuration
desc = NewDescriptor("getConfig", smartcontract.ArrayType)
md = NewMethodAndPrice(ann.getConfig, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// getTotalRecords - Get total lifespan records
desc = NewDescriptor("getTotalRecords", smartcontract.IntegerType)
md = NewMethodAndPrice(ann.getTotalRecords, 1<<15, callflag.ReadStates)
ann.AddMethod(md, desc)
// ===== Events =====
// BirthRegistered event
eDesc := NewEventDescriptor(BirthRegisteredEvent,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("owner", smartcontract.Hash160Type),
manifest.NewParameter("birthTimestamp", smartcontract.IntegerType))
ann.AddEvent(NewEvent(eDesc))
// DeathRecorded event
eDesc = NewEventDescriptor(DeathRecordedEvent,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("deathTimestamp", smartcontract.IntegerType))
ann.AddEvent(NewEvent(eDesc))
// LifeStageChanged event
eDesc = NewEventDescriptor(LifeStageChangedEvent,
manifest.NewParameter("vitaID", smartcontract.IntegerType),
manifest.NewParameter("previousStage", smartcontract.IntegerType),
manifest.NewParameter("newStage", smartcontract.IntegerType))
ann.AddEvent(NewEvent(eDesc))
return ann
}
// Metadata returns contract metadata.
func (a *Annos) Metadata() *interop.ContractMD {
return &a.ContractMD
}
// Initialize initializes the Annos contract.
func (a *Annos) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != a.ActiveIn() {
return nil
}
// Initialize counter
a.setRecordCounter(ic.DAO, 0)
// Initialize config with defaults
cfg := state.DefaultAnnosConfig()
a.setConfig(ic.DAO, &cfg)
// Initialize cache
cache := &AnnosCache{
recordCount: 0,
}
ic.DAO.SetCache(a.ID, cache)
return nil
}
// InitializeCache initializes the cache from storage.
func (a *Annos) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
cache := &AnnosCache{
recordCount: a.getRecordCounter(d),
}
d.SetCache(a.ID, cache)
return nil
}
// OnPersist is called before block is committed.
func (a *Annos) OnPersist(ic *interop.Context) error {
return nil
}
// PostPersist is called after block is committed.
func (a *Annos) PostPersist(ic *interop.Context) error {
return nil
}
// ActiveIn returns the hardfork at which this contract is activated.
func (a *Annos) ActiveIn() *config.Hardfork {
return nil // Always active
}
// ===== Storage Helpers =====
func (a *Annos) makeLifespanKey(vitaID uint64) []byte {
key := make([]byte, 9)
key[0] = annosPrefixLifespan
binary.BigEndian.PutUint64(key[1:], vitaID)
return key
}
func (a *Annos) makeByOwnerKey(owner util.Uint160) []byte {
key := make([]byte, 21)
key[0] = annosPrefixByOwner
copy(key[1:], owner.BytesBE())
return key
}
func (a *Annos) getRecordCounter(d *dao.Simple) uint64 {
si := d.GetStorageItem(a.ID, []byte{annosPrefixRecordCounter})
if si == nil {
return 0
}
return binary.BigEndian.Uint64(si)
}
func (a *Annos) setRecordCounter(d *dao.Simple, count uint64) {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, count)
d.PutStorageItem(a.ID, []byte{annosPrefixRecordCounter}, buf)
}
func (a *Annos) getConfigInternal(d *dao.Simple) *state.AnnosConfig {
si := d.GetStorageItem(a.ID, []byte{annosPrefixConfig})
if si == nil {
cfg := state.DefaultAnnosConfig()
return &cfg
}
cfg := new(state.AnnosConfig)
item, _ := stackitem.Deserialize(si)
cfg.FromStackItem(item)
return cfg
}
func (a *Annos) setConfig(d *dao.Simple, cfg *state.AnnosConfig) {
item, _ := cfg.ToStackItem()
data, _ := stackitem.Serialize(item)
d.PutStorageItem(a.ID, []byte{annosPrefixConfig}, data)
}
func (a *Annos) getRecordInternal(d *dao.Simple, vitaID uint64) *state.LifespanRecord {
si := d.GetStorageItem(a.ID, a.makeLifespanKey(vitaID))
if si == nil {
return nil
}
rec := new(state.LifespanRecord)
item, _ := stackitem.Deserialize(si)
rec.FromStackItem(item)
return rec
}
func (a *Annos) putRecord(d *dao.Simple, rec *state.LifespanRecord) {
item, _ := rec.ToStackItem()
data, _ := stackitem.Serialize(item)
d.PutStorageItem(a.ID, a.makeLifespanKey(rec.VitaID), data)
}
func (a *Annos) getVitaIDByOwner(d *dao.Simple, owner util.Uint160) (uint64, bool) {
si := d.GetStorageItem(a.ID, a.makeByOwnerKey(owner))
if si == nil {
return 0, false
}
return binary.BigEndian.Uint64(si), true
}
func (a *Annos) setOwnerToVitaID(d *dao.Simple, owner util.Uint160, vitaID uint64) {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, vitaID)
d.PutStorageItem(a.ID, a.makeByOwnerKey(owner), buf)
}
// calculateAge calculates age in years from birth timestamp to current timestamp.
func calculateAge(birthTimestamp, currentTimestamp uint64) uint8 {
if currentTimestamp <= birthTimestamp {
return 0
}
ageSeconds := currentTimestamp - birthTimestamp
ageYears := ageSeconds / secondsPerYear
if ageYears > 255 {
return 255 // Cap at max uint8
}
return uint8(ageYears)
}
// calculateLifeStage determines the life stage based on age.
func (a *Annos) calculateLifeStage(d *dao.Simple, age uint8) state.LifeStage {
cfg := a.getConfigInternal(d)
if age < cfg.AdultAge {
return state.LifeStageChild
} else if age <= cfg.YouthMaxAge {
return state.LifeStageYouth
} else if age < cfg.ElderMinAge {
return state.LifeStageAdult
}
return state.LifeStageElder
}
// ===== Contract Methods =====
// getAge returns the current age in years.
func (a *Annos) getAge(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
vitaID, found := a.getVitaIDByOwner(ic.DAO, owner)
if !found {
return stackitem.NewBigInteger(big.NewInt(0))
}
rec := a.getRecordInternal(ic.DAO, vitaID)
if rec == nil {
return stackitem.NewBigInteger(big.NewInt(0))
}
// If deceased, return age at death
currentTime := ic.Block.Timestamp / 1000 // Block timestamp is in ms
if rec.Status == state.LifespanDeceased && rec.DeathTimestamp > 0 {
currentTime = rec.DeathTimestamp
}
age := calculateAge(rec.BirthTimestamp, currentTime)
return stackitem.NewBigInteger(big.NewInt(int64(age)))
}
// getAgeAtTime returns the age at a specific timestamp.
func (a *Annos) getAgeAtTime(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
timestamp := toUint64(args[1])
vitaID, found := a.getVitaIDByOwner(ic.DAO, owner)
if !found {
return stackitem.NewBigInteger(big.NewInt(0))
}
rec := a.getRecordInternal(ic.DAO, vitaID)
if rec == nil {
return stackitem.NewBigInteger(big.NewInt(0))
}
// If deceased and query time is after death, use death time
queryTime := timestamp
if rec.Status == state.LifespanDeceased && rec.DeathTimestamp > 0 && timestamp > rec.DeathTimestamp {
queryTime = rec.DeathTimestamp
}
age := calculateAge(rec.BirthTimestamp, queryTime)
return stackitem.NewBigInteger(big.NewInt(int64(age)))
}
// getLifeStage returns the current life stage.
func (a *Annos) getLifeStage(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
vitaID, found := a.getVitaIDByOwner(ic.DAO, owner)
if !found {
return stackitem.NewBigInteger(big.NewInt(0))
}
rec := a.getRecordInternal(ic.DAO, vitaID)
if rec == nil {
return stackitem.NewBigInteger(big.NewInt(0))
}
currentTime := ic.Block.Timestamp / 1000
if rec.Status == state.LifespanDeceased && rec.DeathTimestamp > 0 {
currentTime = rec.DeathTimestamp
}
age := calculateAge(rec.BirthTimestamp, currentTime)
stage := a.calculateLifeStage(ic.DAO, age)
return stackitem.NewBigInteger(big.NewInt(int64(stage)))
}
// getBirthTimestamp returns the birth timestamp.
func (a *Annos) getBirthTimestamp(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
vitaID, found := a.getVitaIDByOwner(ic.DAO, owner)
if !found {
return stackitem.NewBigInteger(big.NewInt(0))
}
rec := a.getRecordInternal(ic.DAO, vitaID)
if rec == nil {
return stackitem.NewBigInteger(big.NewInt(0))
}
return stackitem.NewBigInteger(new(big.Int).SetUint64(rec.BirthTimestamp))
}
// getRecord returns the full lifespan record.
func (a *Annos) getRecord(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
vitaID, found := a.getVitaIDByOwner(ic.DAO, owner)
if !found {
return stackitem.Null{}
}
rec := a.getRecordInternal(ic.DAO, vitaID)
if rec == nil {
return stackitem.Null{}
}
item, _ := rec.ToStackItem()
return item
}
// getRecordByVitaID returns the record by Vita ID.
func (a *Annos) getRecordByVitaID(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
rec := a.getRecordInternal(ic.DAO, vitaID)
if rec == nil {
return stackitem.Null{}
}
item, _ := rec.ToStackItem()
return item
}
// isVotingAge checks if the owner is at voting age.
func (a *Annos) isVotingAge(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
return stackitem.NewBool(a.IsVotingAgeInternal(ic.DAO, owner, ic.Block.Timestamp/1000))
}
// isAdult checks if the owner is an adult.
func (a *Annos) isAdult(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
return stackitem.NewBool(a.IsAdultInternal(ic.DAO, owner, ic.Block.Timestamp/1000))
}
// isRetirementAge checks if the owner is at retirement age.
func (a *Annos) isRetirementAge(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
return stackitem.NewBool(a.IsRetirementAgeInternal(ic.DAO, owner, ic.Block.Timestamp/1000))
}
// isAlive checks if the owner is alive.
func (a *Annos) isAlive(ic *interop.Context, args []stackitem.Item) stackitem.Item {
owner := toUint160(args[0])
vitaID, found := a.getVitaIDByOwner(ic.DAO, owner)
if !found {
return stackitem.NewBool(false)
}
rec := a.getRecordInternal(ic.DAO, vitaID)
if rec == nil {
return stackitem.NewBool(false)
}
return stackitem.NewBool(rec.Status == state.LifespanActive)
}
// recordDeath records a death (committee only).
func (a *Annos) recordDeath(ic *interop.Context, args []stackitem.Item) stackitem.Item {
vitaID := toUint64(args[0])
deathTimestamp := toUint64(args[1])
// Committee only
if !a.checkCommittee(ic) {
panic(ErrAnnosNotCommittee)
}
rec := a.getRecordInternal(ic.DAO, vitaID)
if rec == nil {
panic(ErrAnnosRecordNotFound)
}
if rec.Status == state.LifespanDeceased {
panic(ErrAnnosAlreadyDeceased)
}
// Validate death timestamp
if deathTimestamp == 0 {
deathTimestamp = ic.Block.Timestamp / 1000
}
if deathTimestamp < rec.BirthTimestamp {
panic(ErrAnnosInvalidTimestamp)
}
// Update record
rec.DeathTimestamp = deathTimestamp
rec.DeathBlock = ic.Block.Index
rec.Status = state.LifespanDeceased
a.putRecord(ic.DAO, rec)
// Emit event
ic.AddNotification(a.Hash, DeathRecordedEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
stackitem.NewBigInteger(new(big.Int).SetUint64(deathTimestamp)),
}))
return stackitem.NewBool(true)
}
// getConfig returns the Annos configuration.
func (a *Annos) getConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item {
cfg := a.getConfigInternal(ic.DAO)
item, _ := cfg.ToStackItem()
return item
}
// getTotalRecords returns the total number of lifespan records.
func (a *Annos) getTotalRecords(ic *interop.Context, args []stackitem.Item) stackitem.Item {
cache := ic.DAO.GetROCache(a.ID).(*AnnosCache)
return stackitem.NewBigInteger(big.NewInt(int64(cache.recordCount)))
}
// ===== Public Interface Methods for Cross-Contract Access =====
// RegisterBirthInternal is called by Vita contract to register a birth.
// This should only be called when minting a new Vita token.
func (a *Annos) RegisterBirthInternal(d *dao.Simple, ic *interop.Context, vitaID uint64, owner util.Uint160, birthTimestamp uint64) error {
// Check if record already exists
existing := a.getRecordInternal(d, vitaID)
if existing != nil {
return ErrAnnosRecordExists
}
// Validate birth timestamp (must not be in the future)
currentTime := ic.Block.Timestamp / 1000
if birthTimestamp > currentTime {
birthTimestamp = currentTime // Cap at current time
}
// Increment counter
cache := d.GetRWCache(a.ID).(*AnnosCache)
cache.recordCount++
a.setRecordCounter(d, cache.recordCount)
// Create lifespan record
rec := &state.LifespanRecord{
VitaID: vitaID,
Owner: owner,
BirthTimestamp: birthTimestamp,
RegistrationBlock: ic.Block.Index,
RegistrationTime: currentTime,
DeathTimestamp: 0,
DeathBlock: 0,
Status: state.LifespanActive,
}
a.putRecord(d, rec)
a.setOwnerToVitaID(d, owner, vitaID)
// Emit event
ic.AddNotification(a.Hash, BirthRegisteredEvent, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
stackitem.NewByteArray(owner.BytesBE()),
stackitem.NewBigInteger(new(big.Int).SetUint64(birthTimestamp)),
}))
return nil
}
// GetAgeInternal returns the age of an owner at the current timestamp.
func (a *Annos) GetAgeInternal(d *dao.Simple, owner util.Uint160, currentTimestamp uint64) (uint8, error) {
vitaID, found := a.getVitaIDByOwner(d, owner)
if !found {
return 0, ErrAnnosRecordNotFound
}
rec := a.getRecordInternal(d, vitaID)
if rec == nil {
return 0, ErrAnnosRecordNotFound
}
// If deceased, use death time
queryTime := currentTimestamp
if rec.Status == state.LifespanDeceased && rec.DeathTimestamp > 0 {
queryTime = rec.DeathTimestamp
}
return calculateAge(rec.BirthTimestamp, queryTime), nil
}
// IsVotingAgeInternal checks if an owner is at voting age.
func (a *Annos) IsVotingAgeInternal(d *dao.Simple, owner util.Uint160, currentTimestamp uint64) bool {
age, err := a.GetAgeInternal(d, owner, currentTimestamp)
if err != nil {
return false
}
cfg := a.getConfigInternal(d)
return age >= cfg.VotingAge
}
// IsAdultInternal checks if an owner is an adult.
func (a *Annos) IsAdultInternal(d *dao.Simple, owner util.Uint160, currentTimestamp uint64) bool {
age, err := a.GetAgeInternal(d, owner, currentTimestamp)
if err != nil {
return false
}
cfg := a.getConfigInternal(d)
return age >= cfg.AdultAge
}
// IsRetirementAgeInternal checks if an owner is at retirement age.
func (a *Annos) IsRetirementAgeInternal(d *dao.Simple, owner util.Uint160, currentTimestamp uint64) bool {
age, err := a.GetAgeInternal(d, owner, currentTimestamp)
if err != nil {
return false
}
cfg := a.getConfigInternal(d)
return age >= cfg.RetirementAge
}
// GetLifeStageInternal returns the life stage of an owner.
func (a *Annos) GetLifeStageInternal(d *dao.Simple, owner util.Uint160, currentTimestamp uint64) state.LifeStage {
age, err := a.GetAgeInternal(d, owner, currentTimestamp)
if err != nil {
return state.LifeStageChild // Default
}
return a.calculateLifeStage(d, age)
}
// IsAliveInternal checks if an owner is alive.
func (a *Annos) IsAliveInternal(d *dao.Simple, owner util.Uint160) bool {
vitaID, found := a.getVitaIDByOwner(d, owner)
if !found {
return false
}
rec := a.getRecordInternal(d, vitaID)
if rec == nil {
return false
}
return rec.Status == state.LifespanActive
}
// GetRecordByOwner returns the lifespan record for an owner.
func (a *Annos) GetRecordByOwner(d *dao.Simple, owner util.Uint160) (*state.LifespanRecord, error) {
vitaID, found := a.getVitaIDByOwner(d, owner)
if !found {
return nil, ErrAnnosRecordNotFound
}
rec := a.getRecordInternal(d, vitaID)
if rec == nil {
return nil, ErrAnnosRecordNotFound
}
return rec, nil
}
// Address returns the contract's script hash.
func (a *Annos) Address() util.Uint160 {
return a.Hash
}