From 6d834ff9c22a85c547814a6af47f777ed1559d6f Mon Sep 17 00:00:00 2001 From: Tutus Development Date: Sat, 20 Dec 2025 05:33:26 +0000 Subject: [PATCH] Add Lex native contract for universal law and rights framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement comprehensive legal framework ensuring equality for all citizens: - Add 14 immutable constitutional rights (life, liberty, property, equality, due process, privacy, expression, assembly, movement, education, healthcare, labor, vote, asylum) - Implement hierarchical law registry with categories: Constitutional, Federal, Regional, Local, Administrative - Add configurable enforcement types per law: Automatic (blocks violations), Logging (emits events), Advisory - Implement rights restriction system requiring due process: - All restrictions require judicial authority (RoleJudge) - All restrictions require a caseID (legal proceeding) - All restrictions must have expiration (no indefinite detention) - Add cross-contract integration methods: HasRightInternal, IsRestrictedInternal, CheckPropertyRight, CheckMovementRight, CheckLibertyRight - Update test data for native contract state serialization Constitutional rights are code, not data - they cannot be amended or removed, ensuring true immutability and universal equality. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- pkg/core/native/contract.go | 33 + pkg/core/native/lex.go | 935 ++++++++++++++++++ .../native/native_test/management_test.go | 18 +- pkg/core/native/nativehashes/hashes.go | 2 + pkg/core/native/nativeids/ids.go | 2 + pkg/core/native/nativenames/names.go | 5 +- pkg/core/state/lex.go | 410 ++++++++ 7 files changed, 1396 insertions(+), 9 deletions(-) create mode 100644 pkg/core/native/lex.go create mode 100644 pkg/core/state/lex.go diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 6f0fdca..8ebdb39 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -191,6 +191,25 @@ type ( // GetDeficit returns the current cumulative deficit. GetDeficit(d *dao.Simple) *big.Int } + + // ILex is an interface required from native Lex contract for + // interaction with Blockchain and other native contracts. + // Lex provides universal law registry and rights enforcement. + ILex interface { + interop.Contract + // HasRightInternal checks if subject has a specific constitutional right. + HasRightInternal(d *dao.Simple, subject util.Uint160, rightID uint64, blockHeight uint32) bool + // IsRestrictedInternal checks if a subject's right is restricted. + IsRestrictedInternal(d *dao.Simple, subject util.Uint160, rightID uint64, blockHeight uint32) bool + // CheckPropertyRight checks if subject has property rights. + CheckPropertyRight(d *dao.Simple, subject util.Uint160, blockHeight uint32) bool + // CheckMovementRight checks if subject has movement rights. + CheckMovementRight(d *dao.Simple, subject util.Uint160, blockHeight uint32) bool + // CheckLibertyRight checks if subject has liberty rights. + CheckLibertyRight(d *dao.Simple, subject util.Uint160, blockHeight uint32) bool + // Address returns the contract's script hash. + Address() util.Uint160 + } ) // Contracts is a convenient wrapper around an arbitrary set of native contracts @@ -336,6 +355,12 @@ func (cs *Contracts) Treasury() ITreasury { return cs.ByName(nativenames.Treasury).(ITreasury) } +// Lex returns native ILex contract implementation. It panics if +// there's no contract with proper name in cs. +func (cs *Contracts) Lex() ILex { + return cs.ByName(nativenames.Lex).(ILex) +} + // NewDefaultContracts returns a new set of default native contracts. func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { mgmt := NewManagement() @@ -404,6 +429,13 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { federation := newFederation() federation.NEO = neo + // Create Lex (Law Registry) contract for universal rights and law enforcement + lex := newLex() + lex.NEO = neo + lex.Vita = vita + lex.RoleRegistry = roleRegistry + lex.Federation = federation + // Wire GAS dependencies for Vita fee exemption gas.Vita = vita gas.Federation = federation @@ -425,5 +457,6 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { roleRegistry, vts, federation, + lex, } } diff --git a/pkg/core/native/lex.go b/pkg/core/native/lex.go new file mode 100644 index 0000000..e056e05 --- /dev/null +++ b/pkg/core/native/lex.go @@ -0,0 +1,935 @@ +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/core/state" + "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" +) + +// Lex represents the Lex native contract for universal law and rights enforcement. +// It provides: +// - Immutable constitutional rights (14 fundamental rights) +// - Hierarchical law registry (Federal > Regional > Local) +// - Rights restriction system (judicial orders) +// - Cross-contract compliance checking +type Lex struct { + interop.ContractMD + NEO INEO + Vita IVita + RoleRegistry IRoleRegistry + Federation IFederation +} + +// Storage key prefixes for Lex. +const ( + lexPrefixLaw byte = 0x01 // lawID -> Law + lexPrefixLawName byte = 0x02 // name -> lawID + lexPrefixLawCategory byte = 0x03 // category + lawID -> exists + lexPrefixLawJurisdiction byte = 0x04 // jurisdiction + lawID -> exists + lexPrefixLawHierarchy byte = 0x05 // parentID + childID -> exists + lexPrefixRestriction byte = 0x20 // subject + rightID -> RightsRestriction + lexPrefixSubjectRights byte = 0x21 // subject -> []restrictedRightIDs + lexPrefixLawCounter byte = 0xF0 // -> nextLawID +) + +// Maximum lengths. +const ( + maxLawNameLength = 256 + maxReasonLength = 1024 + maxLawsPerCategory = 10000 +) + +// Event names for Lex. +const ( + LawEnactedEvent = "LawEnacted" + LawAmendedEvent = "LawAmended" + LawRepealedEvent = "LawRepealed" + LawSuspendedEvent = "LawSuspended" + LawReinstatedEvent = "LawReinstated" + RightRestrictedEvent = "RightRestricted" + RestrictionLiftedEvent = "RestrictionLifted" + RightViolationEvent = "RightViolation" +) + +// Judicial authority roles (defined in RoleRegistry). +const ( + RoleLegislator uint64 = 10 // Can propose and enact laws + RoleJudge uint64 = 11 // Can issue rights restrictions + RoleProsecutor uint64 = 12 // Can initiate cases + RoleDefender uint64 = 13 // Public defender + RoleEnforcer uint64 = 14 // Can execute judicial orders +) + +// Various errors. +var ( + ErrLawNotFound = errors.New("law not found") + ErrLawNameExists = errors.New("law name already exists") + ErrLawNameTooLong = errors.New("law name too long") + ErrInvalidLawCategory = errors.New("invalid law category") + ErrInvalidParentLaw = errors.New("invalid parent law") + ErrLawNotActive = errors.New("law is not active") + ErrCannotModifyConst = errors.New("cannot modify constitutional rights") + ErrRestrictionNotFound = errors.New("restriction not found") + ErrRestrictionExists = errors.New("restriction already exists") + ErrInvalidRightID = errors.New("invalid right ID") + ErrReasonTooLong = errors.New("reason too long") + ErrNoCaseID = errors.New("case ID required for due process") + ErrNoExpiration = errors.New("expiration required (no indefinite restrictions)") + ErrNotAuthorized = errors.New("not authorized") + ErrRightRestricted = errors.New("right is restricted") + ErrNoActiveVita = errors.New("no active Vita token") + ErrJudicialAuthRequired = errors.New("judicial authority required") +) + +var _ interop.Contract = (*Lex)(nil) + +// newLex creates a new Lex native contract. +func newLex() *Lex { + l := &Lex{ + ContractMD: *interop.NewContractMD(nativenames.Lex, nativeids.Lex), + } + defer l.BuildHFSpecificMD(l.ActiveIn()) + + // Query methods (safe, read-only) + + // getLaw returns a law by ID + desc := NewDescriptor("getLaw", smartcontract.ArrayType, + manifest.NewParameter("lawID", smartcontract.IntegerType)) + md := NewMethodAndPrice(l.getLaw, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + + // getLawByName returns a law by name + desc = NewDescriptor("getLawByName", smartcontract.ArrayType, + manifest.NewParameter("name", smartcontract.StringType)) + md = NewMethodAndPrice(l.getLawByName, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + + // isLawActive checks if a law is currently active + desc = NewDescriptor("isLawActive", smartcontract.BoolType, + manifest.NewParameter("lawID", smartcontract.IntegerType)) + md = NewMethodAndPrice(l.isLawActive, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + + // getConstitutionalRight returns a constitutional right by ID + desc = NewDescriptor("getConstitutionalRight", smartcontract.ArrayType, + manifest.NewParameter("rightID", smartcontract.IntegerType)) + md = NewMethodAndPrice(l.getConstitutionalRight, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + + // getAllConstitutionalRights returns all 14 constitutional rights + desc = NewDescriptor("getAllConstitutionalRights", smartcontract.ArrayType) + md = NewMethodAndPrice(l.getAllConstitutionalRights, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + + // hasRight checks if a subject has a specific right (not restricted) + desc = NewDescriptor("hasRight", smartcontract.BoolType, + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("rightID", smartcontract.IntegerType)) + md = NewMethodAndPrice(l.hasRight, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + + // isRestricted checks if a subject's right is restricted + desc = NewDescriptor("isRestricted", smartcontract.BoolType, + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("rightID", smartcontract.IntegerType)) + md = NewMethodAndPrice(l.isRestricted, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + + // getRestriction returns the restriction on a subject's right + desc = NewDescriptor("getRestriction", smartcontract.ArrayType, + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("rightID", smartcontract.IntegerType)) + md = NewMethodAndPrice(l.getRestriction, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + + // getSubjectRestrictions returns all restrictions on a subject + desc = NewDescriptor("getSubjectRestrictions", smartcontract.ArrayType, + manifest.NewParameter("subject", smartcontract.Hash160Type)) + md = NewMethodAndPrice(l.getSubjectRestrictions, 1<<16, callflag.ReadStates) + l.AddMethod(md, desc) + + // getLawCount returns the total number of laws + desc = NewDescriptor("getLawCount", smartcontract.IntegerType) + md = NewMethodAndPrice(l.getLawCount, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + + // Administrative methods (committee/authority only) + + // enactLaw creates a new law + desc = NewDescriptor("enactLaw", smartcontract.IntegerType, + manifest.NewParameter("name", smartcontract.StringType), + manifest.NewParameter("contentHash", smartcontract.Hash256Type), + manifest.NewParameter("category", smartcontract.IntegerType), + manifest.NewParameter("jurisdiction", smartcontract.IntegerType), + manifest.NewParameter("parentID", smartcontract.IntegerType), + manifest.NewParameter("effectiveAt", smartcontract.IntegerType), + manifest.NewParameter("enforcement", smartcontract.IntegerType)) + md = NewMethodAndPrice(l.enactLaw, 1<<17, callflag.States|callflag.AllowNotify) + l.AddMethod(md, desc) + + // repealLaw permanently removes a law + desc = NewDescriptor("repealLaw", smartcontract.BoolType, + manifest.NewParameter("lawID", smartcontract.IntegerType), + manifest.NewParameter("reason", smartcontract.StringType)) + md = NewMethodAndPrice(l.repealLaw, 1<<16, callflag.States|callflag.AllowNotify) + l.AddMethod(md, desc) + + // suspendLaw temporarily suspends a law + desc = NewDescriptor("suspendLaw", smartcontract.BoolType, + manifest.NewParameter("lawID", smartcontract.IntegerType), + manifest.NewParameter("reason", smartcontract.StringType), + manifest.NewParameter("duration", smartcontract.IntegerType)) + md = NewMethodAndPrice(l.suspendLaw, 1<<16, callflag.States|callflag.AllowNotify) + l.AddMethod(md, desc) + + // reinstateLaw reinstates a suspended law + desc = NewDescriptor("reinstateLaw", smartcontract.BoolType, + manifest.NewParameter("lawID", smartcontract.IntegerType)) + md = NewMethodAndPrice(l.reinstateLaw, 1<<16, callflag.States|callflag.AllowNotify) + l.AddMethod(md, desc) + + // Rights restriction methods (judicial authority only) + + // restrictRight restricts a subject's right + desc = NewDescriptor("restrictRight", smartcontract.BoolType, + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("rightID", smartcontract.IntegerType), + manifest.NewParameter("restrictionType", smartcontract.IntegerType), + manifest.NewParameter("duration", smartcontract.IntegerType), + manifest.NewParameter("reason", smartcontract.StringType), + manifest.NewParameter("caseID", smartcontract.Hash256Type)) + md = NewMethodAndPrice(l.restrictRight, 1<<17, callflag.States|callflag.AllowNotify) + l.AddMethod(md, desc) + + // liftRestriction removes a restriction + desc = NewDescriptor("liftRestriction", smartcontract.BoolType, + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("rightID", smartcontract.IntegerType), + manifest.NewParameter("reason", smartcontract.StringType)) + md = NewMethodAndPrice(l.liftRestriction, 1<<16, callflag.States|callflag.AllowNotify) + l.AddMethod(md, desc) + + // Events + eDesc := NewEventDescriptor(LawEnactedEvent, + manifest.NewParameter("lawID", smartcontract.IntegerType), + manifest.NewParameter("name", smartcontract.StringType), + manifest.NewParameter("category", smartcontract.IntegerType), + manifest.NewParameter("enactedBy", smartcontract.Hash160Type)) + l.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(LawRepealedEvent, + manifest.NewParameter("lawID", smartcontract.IntegerType), + manifest.NewParameter("reason", smartcontract.StringType)) + l.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(LawSuspendedEvent, + manifest.NewParameter("lawID", smartcontract.IntegerType), + manifest.NewParameter("reason", smartcontract.StringType), + manifest.NewParameter("duration", smartcontract.IntegerType)) + l.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(LawReinstatedEvent, + manifest.NewParameter("lawID", smartcontract.IntegerType)) + l.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(RightRestrictedEvent, + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("rightID", smartcontract.IntegerType), + manifest.NewParameter("restrictionType", smartcontract.IntegerType), + manifest.NewParameter("expiresAt", smartcontract.IntegerType), + manifest.NewParameter("caseID", smartcontract.Hash256Type)) + l.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(RestrictionLiftedEvent, + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("rightID", smartcontract.IntegerType), + manifest.NewParameter("reason", smartcontract.StringType)) + l.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(RightViolationEvent, + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("rightID", smartcontract.IntegerType), + manifest.NewParameter("action", smartcontract.StringType)) + l.AddEvent(NewEvent(eDesc)) + + return l +} + +// Metadata returns contract metadata. +func (l *Lex) Metadata() *interop.ContractMD { + return &l.ContractMD +} + +// Initialize implements the Contract interface. +func (l *Lex) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { + if hf != l.ActiveIn() { + return nil + } + // Initialize law counter starting at 1 (0 is reserved for root) + l.putLawCounter(ic.DAO, 1) + return nil +} + +// InitializeCache implements the Contract interface. +func (l *Lex) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error { + return nil +} + +// OnPersist implements the Contract interface. +func (l *Lex) OnPersist(ic *interop.Context) error { + return nil +} + +// PostPersist implements the Contract interface. +func (l *Lex) PostPersist(ic *interop.Context) error { + return nil +} + +// ActiveIn implements the Contract interface. +func (l *Lex) ActiveIn() *config.Hardfork { + return nil +} + +// Address returns the contract's script hash. +func (l *Lex) Address() util.Uint160 { + return l.Hash +} + +// ==================== Storage Key Helpers ==================== + +func (l *Lex) makeLawKey(lawID uint64) []byte { + key := make([]byte, 9) + key[0] = lexPrefixLaw + binary.BigEndian.PutUint64(key[1:], lawID) + return key +} + +func (l *Lex) makeLawNameKey(name string) []byte { + key := make([]byte, 1+len(name)) + key[0] = lexPrefixLawName + copy(key[1:], name) + return key +} + +func (l *Lex) makeRestrictionKey(subject util.Uint160, rightID uint64) []byte { + key := make([]byte, 29) + key[0] = lexPrefixRestriction + copy(key[1:21], subject[:]) + binary.BigEndian.PutUint64(key[21:], rightID) + return key +} + +func (l *Lex) makeSubjectRightsKey(subject util.Uint160) []byte { + key := make([]byte, 21) + key[0] = lexPrefixSubjectRights + copy(key[1:], subject[:]) + return key +} + +// ==================== Law Counter ==================== + +func (l *Lex) getLawCounter(d *dao.Simple) uint64 { + si := d.GetStorageItem(l.ID, []byte{lexPrefixLawCounter}) + if si == nil { + return 1 + } + return binary.BigEndian.Uint64(si) +} + +func (l *Lex) putLawCounter(d *dao.Simple, count uint64) { + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, count) + d.PutStorageItem(l.ID, []byte{lexPrefixLawCounter}, buf) +} + +// ==================== Internal Storage Methods ==================== + +func (l *Lex) getLawInternal(d *dao.Simple, lawID uint64) *state.Law { + si := d.GetStorageItem(l.ID, l.makeLawKey(lawID)) + if si == nil { + return nil + } + law, err := state.LawFromBytes(si) + if err != nil { + return nil + } + return law +} + +func (l *Lex) putLaw(d *dao.Simple, law *state.Law) { + d.PutStorageItem(l.ID, l.makeLawKey(law.ID), law.Bytes()) +} + +func (l *Lex) getLawIDByName(d *dao.Simple, name string) (uint64, bool) { + si := d.GetStorageItem(l.ID, l.makeLawNameKey(name)) + if si == nil { + return 0, false + } + return binary.BigEndian.Uint64(si), true +} + +func (l *Lex) putLawNameIndex(d *dao.Simple, name string, lawID uint64) { + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, lawID) + d.PutStorageItem(l.ID, l.makeLawNameKey(name), buf) +} + +func (l *Lex) getRestrictionInternal(d *dao.Simple, subject util.Uint160, rightID uint64) *state.RightsRestriction { + si := d.GetStorageItem(l.ID, l.makeRestrictionKey(subject, rightID)) + if si == nil { + return nil + } + r, err := state.RightsRestrictionFromBytes(si) + if err != nil { + return nil + } + return r +} + +func (l *Lex) putRestriction(d *dao.Simple, r *state.RightsRestriction) { + d.PutStorageItem(l.ID, l.makeRestrictionKey(r.Subject, r.RightID), r.Bytes()) +} + +func (l *Lex) deleteRestriction(d *dao.Simple, subject util.Uint160, rightID uint64) { + d.DeleteStorageItem(l.ID, l.makeRestrictionKey(subject, rightID)) +} + +func (l *Lex) getSubjectRestrictedRights(d *dao.Simple, subject util.Uint160) []uint64 { + si := d.GetStorageItem(l.ID, l.makeSubjectRightsKey(subject)) + if si == nil { + return nil + } + count := len(si) / 8 + rights := make([]uint64, count) + for i := 0; i < count; i++ { + rights[i] = binary.BigEndian.Uint64(si[i*8:]) + } + return rights +} + +func (l *Lex) putSubjectRestrictedRights(d *dao.Simple, subject util.Uint160, rights []uint64) { + if len(rights) == 0 { + d.DeleteStorageItem(l.ID, l.makeSubjectRightsKey(subject)) + return + } + buf := make([]byte, len(rights)*8) + for i, r := range rights { + binary.BigEndian.PutUint64(buf[i*8:], r) + } + d.PutStorageItem(l.ID, l.makeSubjectRightsKey(subject), buf) +} + +func (l *Lex) addSubjectRestrictedRight(d *dao.Simple, subject util.Uint160, rightID uint64) { + rights := l.getSubjectRestrictedRights(d, subject) + for _, r := range rights { + if r == rightID { + return // Already exists + } + } + rights = append(rights, rightID) + l.putSubjectRestrictedRights(d, subject, rights) +} + +func (l *Lex) removeSubjectRestrictedRight(d *dao.Simple, subject util.Uint160, rightID uint64) { + rights := l.getSubjectRestrictedRights(d, subject) + for i, r := range rights { + if r == rightID { + rights = append(rights[:i], rights[i+1:]...) + break + } + } + l.putSubjectRestrictedRights(d, subject, rights) +} + +// ==================== Authorization Helpers ==================== + +func (l *Lex) checkCommittee(ic *interop.Context) error { + if l.RoleRegistry != nil && l.RoleRegistry.CheckCommittee(ic) { + return nil + } + if l.NEO != nil && l.NEO.CheckCommittee(ic) { + return nil + } + return ErrNotAuthorized +} + +func (l *Lex) checkJudicialAuthority(ic *interop.Context) error { + if l.RoleRegistry == nil { + // Fall back to committee check if no RoleRegistry + return l.checkCommittee(ic) + } + caller := ic.VM.GetCallingScriptHash() + if l.RoleRegistry.HasRoleInternal(ic.DAO, caller, RoleJudge, ic.Block.Index) { + return nil + } + // Also allow committee + if l.RoleRegistry.CheckCommittee(ic) { + return nil + } + return ErrJudicialAuthRequired +} + +// ==================== Query Methods ==================== + +func (l *Lex) getLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item { + lawID := toUint64(args[0]) + law := l.getLawInternal(ic.DAO, lawID) + if law == nil { + return stackitem.Null{} + } + item, _ := law.ToStackItem() + return item +} + +func (l *Lex) getLawByName(ic *interop.Context, args []stackitem.Item) stackitem.Item { + name := toString(args[0]) + lawID, found := l.getLawIDByName(ic.DAO, name) + if !found { + return stackitem.Null{} + } + law := l.getLawInternal(ic.DAO, lawID) + if law == nil { + return stackitem.Null{} + } + item, _ := law.ToStackItem() + return item +} + +func (l *Lex) isLawActive(ic *interop.Context, args []stackitem.Item) stackitem.Item { + lawID := toUint64(args[0]) + law := l.getLawInternal(ic.DAO, lawID) + if law == nil { + return stackitem.NewBool(false) + } + // Check status and effective dates + if law.Status != state.LawStatusActive { + return stackitem.NewBool(false) + } + if law.EffectiveAt > ic.Block.Index { + return stackitem.NewBool(false) + } + if law.ExpiresAt != 0 && law.ExpiresAt <= ic.Block.Index { + return stackitem.NewBool(false) + } + return stackitem.NewBool(true) +} + +func (l *Lex) getConstitutionalRight(ic *interop.Context, args []stackitem.Item) stackitem.Item { + rightID := toUint64(args[0]) + right := state.GetConstitutionalRight(rightID) + if right == nil { + return stackitem.Null{} + } + return stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(right.ID))), + stackitem.NewByteArray([]byte(right.Name)), + stackitem.NewByteArray([]byte(right.Description)), + stackitem.NewBigInteger(big.NewInt(int64(right.Enforcement))), + }) +} + +func (l *Lex) getAllConstitutionalRights(ic *interop.Context, args []stackitem.Item) stackitem.Item { + rights := state.GetConstitutionalRights() + items := make([]stackitem.Item, len(rights)) + for i, r := range rights { + items[i] = stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(r.ID))), + stackitem.NewByteArray([]byte(r.Name)), + stackitem.NewByteArray([]byte(r.Description)), + stackitem.NewBigInteger(big.NewInt(int64(r.Enforcement))), + }) + } + return stackitem.NewArray(items) +} + +func (l *Lex) hasRight(ic *interop.Context, args []stackitem.Item) stackitem.Item { + subject := toUint160(args[0]) + rightID := toUint64(args[1]) + return stackitem.NewBool(l.HasRightInternal(ic.DAO, subject, rightID, ic.Block.Index)) +} + +func (l *Lex) isRestricted(ic *interop.Context, args []stackitem.Item) stackitem.Item { + subject := toUint160(args[0]) + rightID := toUint64(args[1]) + return stackitem.NewBool(l.IsRestrictedInternal(ic.DAO, subject, rightID, ic.Block.Index)) +} + +func (l *Lex) getRestriction(ic *interop.Context, args []stackitem.Item) stackitem.Item { + subject := toUint160(args[0]) + rightID := toUint64(args[1]) + r := l.getRestrictionInternal(ic.DAO, subject, rightID) + if r == nil { + return stackitem.Null{} + } + item, _ := r.ToStackItem() + return item +} + +func (l *Lex) getSubjectRestrictions(ic *interop.Context, args []stackitem.Item) stackitem.Item { + subject := toUint160(args[0]) + rightIDs := l.getSubjectRestrictedRights(ic.DAO, subject) + items := make([]stackitem.Item, 0) + for _, rightID := range rightIDs { + r := l.getRestrictionInternal(ic.DAO, subject, rightID) + if r != nil && !r.IsExpired(ic.Block.Index) { + item, _ := r.ToStackItem() + items = append(items, item) + } + } + return stackitem.NewArray(items) +} + +func (l *Lex) getLawCount(ic *interop.Context, args []stackitem.Item) stackitem.Item { + count := l.getLawCounter(ic.DAO) + return stackitem.NewBigInteger(big.NewInt(int64(count - 1))) // Counter starts at 1 +} + +// ==================== Law Management Methods ==================== + +func (l *Lex) enactLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item { + if err := l.checkCommittee(ic); err != nil { + panic(err) + } + + name := toString(args[0]) + if len(name) > maxLawNameLength { + panic(ErrLawNameTooLong) + } + + contentHashBytes, err := args[1].TryBytes() + if err != nil { + panic("invalid content hash") + } + var contentHash util.Uint256 + if len(contentHashBytes) == 32 { + copy(contentHash[:], contentHashBytes) + } + + category := state.LawCategory(toUint64(args[2])) + if category < state.LawCategoryFederal || category > state.LawCategoryAdministrative { + panic(ErrInvalidLawCategory) + } + // Cannot create constitutional laws via enactLaw - they are immutable + if category == state.LawCategoryConstitutional { + panic(ErrCannotModifyConst) + } + + jurisdiction := uint32(toUint64(args[3])) + parentID := toUint64(args[4]) + effectiveAt := uint32(toUint64(args[5])) + enforcement := state.EnforcementType(toUint64(args[6])) + + // Check if name exists + if _, exists := l.getLawIDByName(ic.DAO, name); exists { + panic(ErrLawNameExists) + } + + // Validate parent law if specified + if parentID != 0 { + parent := l.getLawInternal(ic.DAO, parentID) + if parent == nil { + panic(ErrInvalidParentLaw) + } + if parent.Status != state.LawStatusActive { + panic(ErrLawNotActive) + } + // Child category must be lower or equal precedence than parent + if category < parent.Category { + panic(ErrInvalidLawCategory) + } + } + + // Create law + lawID := l.getLawCounter(ic.DAO) + law := &state.Law{ + ID: lawID, + Name: name, + ContentHash: contentHash, + Category: category, + Jurisdiction: jurisdiction, + ParentID: parentID, + EffectiveAt: effectiveAt, + ExpiresAt: 0, // Perpetual by default + EnactedAt: ic.Block.Index, + EnactedBy: ic.VM.GetCallingScriptHash(), + Status: state.LawStatusActive, + SupersededBy: 0, + RequiresVita: true, + Enforcement: enforcement, + } + + l.putLaw(ic.DAO, law) + l.putLawNameIndex(ic.DAO, name, lawID) + l.putLawCounter(ic.DAO, lawID+1) + + ic.AddNotification(l.Hash, LawEnactedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(lawID))), + stackitem.NewByteArray([]byte(name)), + stackitem.NewBigInteger(big.NewInt(int64(category))), + stackitem.NewByteArray(law.EnactedBy.BytesBE()), + })) + + return stackitem.NewBigInteger(big.NewInt(int64(lawID))) +} + +func (l *Lex) repealLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item { + if err := l.checkCommittee(ic); err != nil { + panic(err) + } + + lawID := toUint64(args[0]) + reason := toString(args[1]) + if len(reason) > maxReasonLength { + panic(ErrReasonTooLong) + } + + law := l.getLawInternal(ic.DAO, lawID) + if law == nil { + panic(ErrLawNotFound) + } + if law.Category == state.LawCategoryConstitutional { + panic(ErrCannotModifyConst) + } + if law.Status == state.LawStatusRepealed { + return stackitem.NewBool(false) + } + + law.Status = state.LawStatusRepealed + l.putLaw(ic.DAO, law) + + ic.AddNotification(l.Hash, LawRepealedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(lawID))), + stackitem.NewByteArray([]byte(reason)), + })) + + return stackitem.NewBool(true) +} + +func (l *Lex) suspendLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item { + if err := l.checkCommittee(ic); err != nil { + panic(err) + } + + lawID := toUint64(args[0]) + reason := toString(args[1]) + duration := uint32(toUint64(args[2])) + + if len(reason) > maxReasonLength { + panic(ErrReasonTooLong) + } + + law := l.getLawInternal(ic.DAO, lawID) + if law == nil { + panic(ErrLawNotFound) + } + if law.Category == state.LawCategoryConstitutional { + panic(ErrCannotModifyConst) + } + if law.Status != state.LawStatusActive { + panic(ErrLawNotActive) + } + + law.Status = state.LawStatusSuspended + l.putLaw(ic.DAO, law) + + ic.AddNotification(l.Hash, LawSuspendedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(lawID))), + stackitem.NewByteArray([]byte(reason)), + stackitem.NewBigInteger(big.NewInt(int64(duration))), + })) + + return stackitem.NewBool(true) +} + +func (l *Lex) reinstateLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item { + if err := l.checkCommittee(ic); err != nil { + panic(err) + } + + lawID := toUint64(args[0]) + + law := l.getLawInternal(ic.DAO, lawID) + if law == nil { + panic(ErrLawNotFound) + } + if law.Status != state.LawStatusSuspended { + return stackitem.NewBool(false) + } + + law.Status = state.LawStatusActive + l.putLaw(ic.DAO, law) + + ic.AddNotification(l.Hash, LawReinstatedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(lawID))), + })) + + return stackitem.NewBool(true) +} + +// ==================== Rights Restriction Methods ==================== + +func (l *Lex) restrictRight(ic *interop.Context, args []stackitem.Item) stackitem.Item { + if err := l.checkJudicialAuthority(ic); err != nil { + panic(err) + } + + subject := toUint160(args[0]) + rightID := toUint64(args[1]) + restrictionType := state.RestrictionType(toUint64(args[2])) + duration := uint32(toUint64(args[3])) + reason := toString(args[4]) + + caseIDBytes, err := args[5].TryBytes() + if err != nil { + panic("invalid case ID") + } + var caseID util.Uint256 + if len(caseIDBytes) == 32 { + copy(caseID[:], caseIDBytes) + } + + // Validate inputs + if !state.IsConstitutionalRight(rightID) { + panic(ErrInvalidRightID) + } + if len(reason) > maxReasonLength { + panic(ErrReasonTooLong) + } + // Due process requires a case ID + if caseID == (util.Uint256{}) { + panic(ErrNoCaseID) + } + // No indefinite restrictions allowed + if duration == 0 { + panic(ErrNoExpiration) + } + + // Check if restriction already exists + existing := l.getRestrictionInternal(ic.DAO, subject, rightID) + if existing != nil && !existing.IsExpired(ic.Block.Index) { + panic(ErrRestrictionExists) + } + + r := &state.RightsRestriction{ + Subject: subject, + RightID: rightID, + RestrictionType: restrictionType, + IssuedBy: ic.VM.GetCallingScriptHash(), + IssuedAt: ic.Block.Index, + ExpiresAt: ic.Block.Index + duration, + Reason: reason, + CaseID: caseID, + } + + l.putRestriction(ic.DAO, r) + l.addSubjectRestrictedRight(ic.DAO, subject, rightID) + + ic.AddNotification(l.Hash, RightRestrictedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(subject.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(rightID))), + stackitem.NewBigInteger(big.NewInt(int64(restrictionType))), + stackitem.NewBigInteger(big.NewInt(int64(r.ExpiresAt))), + stackitem.NewByteArray(caseID[:]), + })) + + return stackitem.NewBool(true) +} + +func (l *Lex) liftRestriction(ic *interop.Context, args []stackitem.Item) stackitem.Item { + if err := l.checkJudicialAuthority(ic); err != nil { + panic(err) + } + + subject := toUint160(args[0]) + rightID := toUint64(args[1]) + reason := toString(args[2]) + + if len(reason) > maxReasonLength { + panic(ErrReasonTooLong) + } + + r := l.getRestrictionInternal(ic.DAO, subject, rightID) + if r == nil { + panic(ErrRestrictionNotFound) + } + + l.deleteRestriction(ic.DAO, subject, rightID) + l.removeSubjectRestrictedRight(ic.DAO, subject, rightID) + + ic.AddNotification(l.Hash, RestrictionLiftedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(subject.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(rightID))), + stackitem.NewByteArray([]byte(reason)), + })) + + return stackitem.NewBool(true) +} + +// ==================== Cross-Contract Access Methods ==================== + +// HasRightInternal checks if a subject has a specific right (for cross-native access). +// Returns true if the subject has an active Vita and the right is not restricted. +func (l *Lex) HasRightInternal(d *dao.Simple, subject util.Uint160, rightID uint64, blockHeight uint32) bool { + // Check if it's a constitutional right + if !state.IsConstitutionalRight(rightID) { + return false + } + + // Must have active Vita to have rights + if l.Vita != nil { + token, err := l.Vita.GetTokenByOwner(d, subject) + if err != nil || token == nil || token.Status != state.TokenStatusActive { + // Check asylum (humanitarian override) + if l.Federation != nil && l.Federation.HasAsylum(d, subject) { + // Asylum seekers retain rights even without local active Vita + } else { + return false + } + } + } + + // Check if right is restricted + return !l.IsRestrictedInternal(d, subject, rightID, blockHeight) +} + +// IsRestrictedInternal checks if a subject's right is restricted (for cross-native access). +func (l *Lex) IsRestrictedInternal(d *dao.Simple, subject util.Uint160, rightID uint64, blockHeight uint32) bool { + r := l.getRestrictionInternal(d, subject, rightID) + if r == nil { + return false + } + // Check if expired + return !r.IsExpired(blockHeight) +} + +// GetConstitutionalRightInternal returns a constitutional right (for cross-native access). +func (l *Lex) GetConstitutionalRightInternal(rightID uint64) *state.ConstitutionalRight { + return state.GetConstitutionalRight(rightID) +} + +// CheckPropertyRight checks if subject has property rights (for VTS integration). +func (l *Lex) CheckPropertyRight(d *dao.Simple, subject util.Uint160, blockHeight uint32) bool { + return l.HasRightInternal(d, subject, state.RightProperty, blockHeight) +} + +// CheckMovementRight checks if subject has movement rights (for Federation integration). +func (l *Lex) CheckMovementRight(d *dao.Simple, subject util.Uint160, blockHeight uint32) bool { + return l.HasRightInternal(d, subject, state.RightMovement, blockHeight) +} + +// CheckLibertyRight checks if subject has liberty rights. +func (l *Lex) CheckLibertyRight(d *dao.Simple, subject util.Uint160, blockHeight uint32) bool { + return l.HasRightInternal(d, subject, state.RightLiberty, blockHeight) +} diff --git a/pkg/core/native/native_test/management_test.go b/pkg/core/native/native_test/management_test.go index fb51d4e..7ab4ae4 100644 --- a/pkg/core/native/native_test/management_test.go +++ b/pkg/core/native/native_test/management_test.go @@ -53,7 +53,8 @@ var ( 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.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}`, + 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"},{"name":"ageRestricted","type":"Boolean"}],"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"},{"name":"ageRestricted","type":"Boolean"}],"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":"ageRestricted","type":"Boolean"}]},{"name":"VendorUpdated","parameters":[{"name":"address","type":"Hash160"},{"name":"name","type":"String"},{"name":"categories","type":"Integer"},{"name":"ageRestricted","type":"Boolean"}]},{"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}`, + nativenames.Lex: `{"id":-16,"hash":"0xe696922df6c6ded4c468bec2b1ef170805b73f2e","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":1841570703},"manifest":{"name":"Lex","abi":{"methods":[{"name":"enactLaw","offset":0,"parameters":[{"name":"name","type":"String"},{"name":"contentHash","type":"Hash256"},{"name":"category","type":"Integer"},{"name":"jurisdiction","type":"Integer"},{"name":"parentID","type":"Integer"},{"name":"effectiveAt","type":"Integer"},{"name":"enforcement","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"getAllConstitutionalRights","offset":7,"parameters":[],"returntype":"Array","safe":true},{"name":"getConstitutionalRight","offset":14,"parameters":[{"name":"rightID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getLaw","offset":21,"parameters":[{"name":"lawID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getLawByName","offset":28,"parameters":[{"name":"name","type":"String"}],"returntype":"Array","safe":true},{"name":"getLawCount","offset":35,"parameters":[],"returntype":"Integer","safe":true},{"name":"getRestriction","offset":42,"parameters":[{"name":"subject","type":"Hash160"},{"name":"rightID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getSubjectRestrictions","offset":49,"parameters":[{"name":"subject","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"hasRight","offset":56,"parameters":[{"name":"subject","type":"Hash160"},{"name":"rightID","type":"Integer"}],"returntype":"Boolean","safe":true},{"name":"isLawActive","offset":63,"parameters":[{"name":"lawID","type":"Integer"}],"returntype":"Boolean","safe":true},{"name":"isRestricted","offset":70,"parameters":[{"name":"subject","type":"Hash160"},{"name":"rightID","type":"Integer"}],"returntype":"Boolean","safe":true},{"name":"liftRestriction","offset":77,"parameters":[{"name":"subject","type":"Hash160"},{"name":"rightID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"reinstateLaw","offset":84,"parameters":[{"name":"lawID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"repealLaw","offset":91,"parameters":[{"name":"lawID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"restrictRight","offset":98,"parameters":[{"name":"subject","type":"Hash160"},{"name":"rightID","type":"Integer"},{"name":"restrictionType","type":"Integer"},{"name":"duration","type":"Integer"},{"name":"reason","type":"String"},{"name":"caseID","type":"Hash256"}],"returntype":"Boolean","safe":false},{"name":"suspendLaw","offset":105,"parameters":[{"name":"lawID","type":"Integer"},{"name":"reason","type":"String"},{"name":"duration","type":"Integer"}],"returntype":"Boolean","safe":false}],"events":[{"name":"LawEnacted","parameters":[{"name":"lawID","type":"Integer"},{"name":"name","type":"String"},{"name":"category","type":"Integer"},{"name":"enactedBy","type":"Hash160"}]},{"name":"LawRepealed","parameters":[{"name":"lawID","type":"Integer"},{"name":"reason","type":"String"}]},{"name":"LawSuspended","parameters":[{"name":"lawID","type":"Integer"},{"name":"reason","type":"String"},{"name":"duration","type":"Integer"}]},{"name":"LawReinstated","parameters":[{"name":"lawID","type":"Integer"}]},{"name":"RightRestricted","parameters":[{"name":"subject","type":"Hash160"},{"name":"rightID","type":"Integer"},{"name":"restrictionType","type":"Integer"},{"name":"expiresAt","type":"Integer"},{"name":"caseID","type":"Hash256"}]},{"name":"RestrictionLifted","parameters":[{"name":"subject","type":"Hash160"},{"name":"rightID","type":"Integer"},{"name":"reason","type":"String"}]},{"name":"RightViolation","parameters":[{"name":"subject","type":"Hash160"},{"name":"rightID","type":"Integer"},{"name":"action","type":"String"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, } // cockatriceCSS holds serialized native contract states built for genesis block (with UpdateCounter 0) // under assumption that hardforks from Aspidochelone to Cockatrice (included) are enabled. @@ -77,7 +78,8 @@ var ( faunCSS = map[string]string{ nativenames.StdLib: `{"id":-2,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":2426471238},"manifest":{"name":"StdLib","abi":{"methods":[{"name":"atoi","offset":0,"parameters":[{"name":"value","type":"String"}],"returntype":"Integer","safe":true},{"name":"atoi","offset":7,"parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"base58CheckDecode","offset":14,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base58CheckEncode","offset":21,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base58Decode","offset":28,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base58Encode","offset":35,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base64Decode","offset":42,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base64Encode","offset":49,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base64UrlDecode","offset":56,"parameters":[{"name":"s","type":"String"}],"returntype":"String","safe":true},{"name":"base64UrlEncode","offset":63,"parameters":[{"name":"data","type":"String"}],"returntype":"String","safe":true},{"name":"deserialize","offset":70,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"hexDecode","offset":77,"parameters":[{"name":"str","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"hexEncode","offset":84,"parameters":[{"name":"bytes","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"itoa","offset":91,"parameters":[{"name":"value","type":"Integer"}],"returntype":"String","safe":true},{"name":"itoa","offset":98,"parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","safe":true},{"name":"jsonDeserialize","offset":105,"parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"jsonSerialize","offset":112,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"memoryCompare","offset":119,"parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":126,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":133,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":140,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","safe":true},{"name":"serialize","offset":147,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"strLen","offset":154,"parameters":[{"name":"str","type":"String"}],"returntype":"Integer","safe":true},{"name":"stringSplit","offset":161,"parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","safe":true},{"name":"stringSplit","offset":168,"parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.Policy: `{"id":-7,"hash":"0xcc5e4edd9f5f8dba8bb65734541df7a1c081c67b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":751055395},"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":"getBlockedAccounts","offset":14,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"getExecFeeFactor","offset":21,"parameters":[],"returntype":"Integer","safe":true},{"name":"getExecPicoFeeFactor","offset":28,"parameters":[],"returntype":"Integer","safe":true},{"name":"getFeePerByte","offset":35,"parameters":[],"returntype":"Integer","safe":true},{"name":"getMaxTraceableBlocks","offset":42,"parameters":[],"returntype":"Integer","safe":true},{"name":"getMaxValidUntilBlockIncrement","offset":49,"parameters":[],"returntype":"Integer","safe":true},{"name":"getMillisecondsPerBlock","offset":56,"parameters":[],"returntype":"Integer","safe":true},{"name":"getStoragePrice","offset":63,"parameters":[],"returntype":"Integer","safe":true},{"name":"getWhitelistFeeContracts","offset":70,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"isBlocked","offset":77,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"removeWhitelistFeeContract","offset":84,"parameters":[{"name":"contractHash","type":"Hash160"},{"name":"method","type":"String"},{"name":"argCount","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setAttributeFee","offset":91,"parameters":[{"name":"attributeType","type":"Integer"},{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setExecFeeFactor","offset":98,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setFeePerByte","offset":105,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setMaxTraceableBlocks","offset":112,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setMaxValidUntilBlockIncrement","offset":119,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setMillisecondsPerBlock","offset":126,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setStoragePrice","offset":133,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setWhitelistFeeContract","offset":140,"parameters":[{"name":"contractHash","type":"Hash160"},{"name":"method","type":"String"},{"name":"argCount","type":"Integer"},{"name":"fixedFee","type":"Integer"}],"returntype":"Void","safe":false},{"name":"unblockAccount","offset":147,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":false}],"events":[{"name":"MillisecondsPerBlockChanged","parameters":[{"name":"old","type":"Integer"},{"name":"new","type":"Integer"}]},{"name":"WhitelistFeeChanged","parameters":[{"name":"contract","type":"Hash160"},{"name":"method","type":"String"},{"name":"argCount","type":"Integer"},{"name":"fee","type":"Any"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, - nativenames.Treasury: `{"id":-11,"hash":"0x156326f25b1b5d839a4d326aeaa75383c9563ac1","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1592866325},"manifest":{"name":"Treasury","abi":{"methods":[{"name":"onNEP11Payment","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenId","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":true},{"name":"onNEP17Payment","offset":7,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":true},{"name":"verify","offset":14,"parameters":[],"returntype":"Boolean","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-26","NEP-27","NEP-30"],"trusts":[],"extra":null},"updatecounter":0}`, + nativenames.Treasury: `{"id":-11,"hash":"0x156326f25b1b5d839a4d326aeaa75383c9563ac1","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1592866325},"manifest":{"name":"Treasury","abi":{"methods":[{"name":"onNEP11Payment","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenId","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":true},{"name":"onNEP17Payment","offset":7,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":true},{"name":"verify","offset":14,"parameters":[],"returntype":"Boolean","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-26","NEP-27","NEP-30"],"trusts":[],"extra":null},"updatecounter":0}`, + nativenames.Federation: `{"id":-15,"hash":"0x1f05b01d25f44897c7dd4019b0ed19a90b7078fd","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":341349534},"manifest":{"name":"Federation","abi":{"methods":[{"name":"getAsylumInfo","offset":0,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getFeePercent","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"getHomeChain","offset":14,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"getInterChainDebt","offset":21,"parameters":[{"name":"chainID","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"getNaturalizationInfo","offset":28,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"grantAsylum","offset":35,"parameters":[{"name":"owner","type":"Hash160"},{"name":"homeChainID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"hasAsylum","offset":42,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"isNaturalizedCitizen","offset":49,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"isVisitor","offset":56,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"naturalize","offset":63,"parameters":[{"name":"owner","type":"Hash160"},{"name":"originalHomeChain","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"registerVisitor","offset":70,"parameters":[{"name":"owner","type":"Hash160"},{"name":"homeChainID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"revokeAsylum","offset":77,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"setFeePercent","offset":84,"parameters":[{"name":"percent","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"settleDebt","offset":91,"parameters":[{"name":"chainID","type":"Integer"},{"name":"amount","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"unregisterVisitor","offset":98,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":false}],"events":[{"name":"VisitorRegistered","parameters":[{"name":"owner","type":"Hash160"},{"name":"homeChainID","type":"Integer"}]},{"name":"VisitorUnregistered","parameters":[{"name":"owner","type":"Hash160"}]},{"name":"FeePercentChanged","parameters":[{"name":"oldPercent","type":"Integer"},{"name":"newPercent","type":"Integer"}]},{"name":"DebtSettled","parameters":[{"name":"chainID","type":"Integer"},{"name":"amount","type":"Integer"},{"name":"remaining","type":"Integer"}]},{"name":"AsylumGranted","parameters":[{"name":"owner","type":"Hash160"},{"name":"homeChainID","type":"Integer"},{"name":"reason","type":"String"}]},{"name":"AsylumRevoked","parameters":[{"name":"owner","type":"Hash160"}]},{"name":"CitizenNaturalized","parameters":[{"name":"owner","type":"Hash160"},{"name":"originalHomeChain","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, } ) @@ -376,7 +378,7 @@ func TestManagement_NativeUpdate(t *testing.T) { for _, name := range nativenames.All { h := state.CreateNativeContractHash(name) cs := c.Chain.GetContractState(h) - if name == nativenames.Notary || name == nativenames.Treasury { + if name == nativenames.Notary || name == nativenames.Treasury || name == nativenames.Federation { require.Nil(t, cs, name) continue } else { @@ -393,7 +395,7 @@ func TestManagement_NativeUpdate(t *testing.T) { for _, name := range nativenames.All { h := state.CreateNativeContractHash(name) cs := c.Chain.GetContractState(h) - if name == nativenames.Notary || name == nativenames.Treasury { + if name == nativenames.Notary || name == nativenames.Treasury || name == nativenames.Federation { require.Nil(t, cs, name) continue } else { @@ -419,7 +421,7 @@ func TestManagement_NativeUpdate(t *testing.T) { for _, name := range nativenames.All { h := state.CreateNativeContractHash(name) cs := c.Chain.GetContractState(h) - if name == nativenames.Treasury { + if name == nativenames.Treasury || name == nativenames.Federation { require.Nil(t, cs, name) continue } else { @@ -504,7 +506,7 @@ func TestBlockchain_GetNatives(t *testing.T) { // Check genesis-based native contract states. natives := bc.GetNatives() - require.Equal(t, len(nativenames.All)-3, len(natives)) // Notary is deployed starting from D hardfork, Treasury and Federation - starting from Faun. + require.Equal(t, len(nativenames.All)-3, len(natives)) // Notary is deployed starting from Echidna hardfork, Treasury and Federation - starting from Faun. for _, cs := range natives { csFull := state.Contract{ ContractBase: cs.ContractBase, @@ -518,7 +520,7 @@ func TestBlockchain_GetNatives(t *testing.T) { // Check native state after Cockatrice. e.GenerateNewBlocks(t, cockatriceHeight) natives = bc.GetNatives() - require.Equal(t, len(nativenames.All)-2, len(natives)) // Notary is deployed starting from D hardfork. + require.Equal(t, len(nativenames.All)-3, len(natives)) // Notary is deployed at Echidna, Treasury and Federation at Faun. for _, cs := range natives { csFull := state.Contract{ ContractBase: cs.ContractBase, @@ -532,7 +534,7 @@ func TestBlockchain_GetNatives(t *testing.T) { // Check native state after Echidna. e.GenerateNewBlocks(t, echidnaHeight-cockatriceHeight) natives = bc.GetNatives() - require.Equal(t, len(nativenames.All)-1, len(natives)) + require.Equal(t, len(nativenames.All)-2, len(natives)) // Notary deployed at Echidna, missing Treasury and Federation (Faun). for _, cs := range natives { csFull := state.Contract{ ContractBase: cs.ContractBase, diff --git a/pkg/core/native/nativehashes/hashes.go b/pkg/core/native/nativehashes/hashes.go index dad051b..3f674b2 100644 --- a/pkg/core/native/nativehashes/hashes.go +++ b/pkg/core/native/nativehashes/hashes.go @@ -39,4 +39,6 @@ var ( 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} + // Lex is a hash of native Lex contract. + Lex = util.Uint160{0x2e, 0x3f, 0xb7, 0x5, 0x8, 0x17, 0xef, 0xb1, 0xc2, 0xbe, 0x68, 0xc4, 0xd4, 0xde, 0xc6, 0xf6, 0x2d, 0x92, 0x96, 0xe6} ) diff --git a/pkg/core/native/nativeids/ids.go b/pkg/core/native/nativeids/ids.go index 8aa8d73..d190f30 100644 --- a/pkg/core/native/nativeids/ids.go +++ b/pkg/core/native/nativeids/ids.go @@ -37,4 +37,6 @@ const ( VTS int32 = -14 // Federation is an ID of native Federation contract. Federation int32 = -15 + // Lex is an ID of native Lex contract. + Lex int32 = -16 ) diff --git a/pkg/core/native/nativenames/names.go b/pkg/core/native/nativenames/names.go index 9829c87..ae9882e 100644 --- a/pkg/core/native/nativenames/names.go +++ b/pkg/core/native/nativenames/names.go @@ -17,6 +17,7 @@ const ( RoleRegistry = "RoleRegistry" VTS = "VTS" Federation = "Federation" + Lex = "Lex" ) // All contains the list of all native contract names ordered by the contract ID. @@ -36,6 +37,7 @@ var All = []string{ RoleRegistry, VTS, Federation, + Lex, } // IsValid checks if the name is a valid native contract's name. @@ -54,5 +56,6 @@ func IsValid(name string) bool { name == Vita || name == RoleRegistry || name == VTS || - name == Federation + name == Federation || + name == Lex } diff --git a/pkg/core/state/lex.go b/pkg/core/state/lex.go new file mode 100644 index 0000000..d701c2a --- /dev/null +++ b/pkg/core/state/lex.go @@ -0,0 +1,410 @@ +package state + +import ( + "errors" + "fmt" + "math/big" + + "github.com/tutus-one/tutus-chain/pkg/util" + "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" +) + +// LawCategory represents the hierarchical category of a law. +type LawCategory uint8 + +const ( + // LawCategoryConstitutional represents immutable constitutional rights. + LawCategoryConstitutional LawCategory = 1 + // LawCategoryFederal represents national/federal laws. + LawCategoryFederal LawCategory = 2 + // LawCategoryRegional represents state/province laws. + LawCategoryRegional LawCategory = 3 + // LawCategoryLocal represents municipal/local laws. + LawCategoryLocal LawCategory = 4 + // LawCategoryAdministrative represents regulations and administrative rules. + LawCategoryAdministrative LawCategory = 5 +) + +// LawStatus represents the current status of a law. +type LawStatus uint8 + +const ( + // LawStatusDraft indicates a proposed law not yet voted. + LawStatusDraft LawStatus = 0 + // LawStatusActive indicates a law currently in force. + LawStatusActive LawStatus = 1 + // LawStatusSuspended indicates a temporarily suspended law. + LawStatusSuspended LawStatus = 2 + // LawStatusRepealed indicates a permanently removed law. + LawStatusRepealed LawStatus = 3 + // LawStatusSuperseded indicates a law replaced by a newer one. + LawStatusSuperseded LawStatus = 4 +) + +// EnforcementType specifies how a law is enforced. +type EnforcementType uint8 + +const ( + // EnforcementAutomatic means smart contracts block violations. + EnforcementAutomatic EnforcementType = 1 + // EnforcementLogging means violations emit events but allow transaction. + EnforcementLogging EnforcementType = 2 + // EnforcementAdvisory means non-binding guidance only. + EnforcementAdvisory EnforcementType = 3 +) + +// RestrictionType represents the type of rights restriction. +type RestrictionType uint8 + +const ( + // RestrictionSuspend temporarily suspends a right. + RestrictionSuspend RestrictionType = 1 + // RestrictionLimit limits a right (e.g., movement within area). + RestrictionLimit RestrictionType = 2 + // RestrictionCondition makes a right conditional (e.g., probation). + RestrictionCondition RestrictionType = 3 +) + +// ConstitutionalRight IDs - these are immutable and built into the protocol. +const ( + RightLife uint64 = 1 // Right to life + RightLiberty uint64 = 2 // Freedom from arbitrary detention + RightProperty uint64 = 3 // Right to own and transfer assets + RightEquality uint64 = 4 // Equal treatment under law + RightDueProcess uint64 = 5 // Fair legal proceedings + RightPrivacy uint64 = 6 // Privacy of personal data + RightExpression uint64 = 7 // Freedom of expression + RightAssembly uint64 = 8 // Freedom of assembly + RightMovement uint64 = 9 // Freedom of movement + RightEducation uint64 = 10 // Right to education + RightHealthcare uint64 = 11 // Right to healthcare + RightLabor uint64 = 12 // Right to fair labor + RightVote uint64 = 13 // Right to vote + RightAsylum uint64 = 14 // Right to seek asylum + + // MaxConstitutionalRight is the highest constitutional right ID. + MaxConstitutionalRight uint64 = 14 +) + +// Law represents a law in the registry. +type Law struct { + ID uint64 // Unique law identifier + Name string // Human-readable name + ContentHash util.Uint256 // Hash of full law text (stored off-chain) + Category LawCategory // Constitutional, Federal, Regional, Local + Jurisdiction uint32 // Jurisdiction ID (0 = global) + ParentID uint64 // Parent law in hierarchy (0 = root) + EffectiveAt uint32 // Block height when law becomes active + ExpiresAt uint32 // Block height when law expires (0 = perpetual) + EnactedAt uint32 // Block height of enactment + EnactedBy util.Uint160 // Authority that enacted + Status LawStatus // Active, Suspended, Repealed, Superseded + SupersededBy uint64 // ID of law that replaced this one + RequiresVita bool // Whether law applies only to Vita holders + Enforcement EnforcementType // How law is enforced +} + +// ToStackItem implements stackitem.Convertible interface. +func (l *Law) ToStackItem() (stackitem.Item, error) { + return stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(l.ID))), + stackitem.NewByteArray([]byte(l.Name)), + stackitem.NewByteArray(l.ContentHash[:]), + stackitem.NewBigInteger(big.NewInt(int64(l.Category))), + stackitem.NewBigInteger(big.NewInt(int64(l.Jurisdiction))), + stackitem.NewBigInteger(big.NewInt(int64(l.ParentID))), + stackitem.NewBigInteger(big.NewInt(int64(l.EffectiveAt))), + stackitem.NewBigInteger(big.NewInt(int64(l.ExpiresAt))), + stackitem.NewBigInteger(big.NewInt(int64(l.EnactedAt))), + stackitem.NewByteArray(l.EnactedBy.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(l.Status))), + stackitem.NewBigInteger(big.NewInt(int64(l.SupersededBy))), + stackitem.NewBool(l.RequiresVita), + stackitem.NewBigInteger(big.NewInt(int64(l.Enforcement))), + }), nil +} + +// FromStackItem implements stackitem.Convertible interface. +func (l *Law) FromStackItem(item stackitem.Item) error { + items, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not a struct") + } + if len(items) != 14 { + return fmt.Errorf("wrong number of elements: expected 14, got %d", len(items)) + } + + id, err := items[0].TryInteger() + if err != nil { + return fmt.Errorf("invalid ID: %w", err) + } + l.ID = id.Uint64() + + name, err := items[1].TryBytes() + if err != nil { + return fmt.Errorf("invalid Name: %w", err) + } + l.Name = string(name) + + contentHash, err := items[2].TryBytes() + if err != nil { + return fmt.Errorf("invalid ContentHash: %w", err) + } + if len(contentHash) == 32 { + copy(l.ContentHash[:], contentHash) + } + + category, err := items[3].TryInteger() + if err != nil { + return fmt.Errorf("invalid Category: %w", err) + } + l.Category = LawCategory(category.Uint64()) + + jurisdiction, err := items[4].TryInteger() + if err != nil { + return fmt.Errorf("invalid Jurisdiction: %w", err) + } + l.Jurisdiction = uint32(jurisdiction.Uint64()) + + parentID, err := items[5].TryInteger() + if err != nil { + return fmt.Errorf("invalid ParentID: %w", err) + } + l.ParentID = parentID.Uint64() + + effectiveAt, err := items[6].TryInteger() + if err != nil { + return fmt.Errorf("invalid EffectiveAt: %w", err) + } + l.EffectiveAt = uint32(effectiveAt.Uint64()) + + expiresAt, err := items[7].TryInteger() + if err != nil { + return fmt.Errorf("invalid ExpiresAt: %w", err) + } + l.ExpiresAt = uint32(expiresAt.Uint64()) + + enactedAt, err := items[8].TryInteger() + if err != nil { + return fmt.Errorf("invalid EnactedAt: %w", err) + } + l.EnactedAt = uint32(enactedAt.Uint64()) + + enactedBy, err := items[9].TryBytes() + if err != nil { + return fmt.Errorf("invalid EnactedBy: %w", err) + } + l.EnactedBy, err = util.Uint160DecodeBytesBE(enactedBy) + if err != nil { + return fmt.Errorf("invalid EnactedBy address: %w", err) + } + + status, err := items[10].TryInteger() + if err != nil { + return fmt.Errorf("invalid Status: %w", err) + } + l.Status = LawStatus(status.Uint64()) + + supersededBy, err := items[11].TryInteger() + if err != nil { + return fmt.Errorf("invalid SupersededBy: %w", err) + } + l.SupersededBy = supersededBy.Uint64() + + requiresVita, err := items[12].TryBool() + if err != nil { + return fmt.Errorf("invalid RequiresVita: %w", err) + } + l.RequiresVita = requiresVita + + enforcement, err := items[13].TryInteger() + if err != nil { + return fmt.Errorf("invalid Enforcement: %w", err) + } + l.Enforcement = EnforcementType(enforcement.Uint64()) + + return nil +} + +// Bytes serializes the Law to bytes. +func (l *Law) Bytes() []byte { + item, _ := l.ToStackItem() + data, _ := stackitem.Serialize(item) + return data +} + +// LawFromBytes deserializes a Law from bytes. +func LawFromBytes(data []byte) (*Law, error) { + item, err := stackitem.Deserialize(data) + if err != nil { + return nil, err + } + l := &Law{} + if err := l.FromStackItem(item); err != nil { + return nil, err + } + return l, nil +} + +// RightsRestriction represents a restriction on a constitutional right. +type RightsRestriction struct { + Subject util.Uint160 // Who is restricted + RightID uint64 // Which right is restricted + RestrictionType RestrictionType // Type of restriction + IssuedBy util.Uint160 // Authority that issued + IssuedAt uint32 // Block height when issued + ExpiresAt uint32 // When restriction ends (must be set) + Reason string // Legal basis + CaseID util.Uint256 // Associated legal case (required for due process) +} + +// ToStackItem implements stackitem.Convertible interface. +func (r *RightsRestriction) ToStackItem() (stackitem.Item, error) { + return stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray(r.Subject.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(r.RightID))), + stackitem.NewBigInteger(big.NewInt(int64(r.RestrictionType))), + stackitem.NewByteArray(r.IssuedBy.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(r.IssuedAt))), + stackitem.NewBigInteger(big.NewInt(int64(r.ExpiresAt))), + stackitem.NewByteArray([]byte(r.Reason)), + stackitem.NewByteArray(r.CaseID[:]), + }), nil +} + +// FromStackItem implements stackitem.Convertible interface. +func (r *RightsRestriction) FromStackItem(item stackitem.Item) error { + items, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not a struct") + } + if len(items) != 8 { + return fmt.Errorf("wrong number of elements: expected 8, got %d", len(items)) + } + + subject, err := items[0].TryBytes() + if err != nil { + return fmt.Errorf("invalid Subject: %w", err) + } + r.Subject, err = util.Uint160DecodeBytesBE(subject) + if err != nil { + return fmt.Errorf("invalid Subject address: %w", err) + } + + rightID, err := items[1].TryInteger() + if err != nil { + return fmt.Errorf("invalid RightID: %w", err) + } + r.RightID = rightID.Uint64() + + restrictionType, err := items[2].TryInteger() + if err != nil { + return fmt.Errorf("invalid RestrictionType: %w", err) + } + r.RestrictionType = RestrictionType(restrictionType.Uint64()) + + issuedBy, err := items[3].TryBytes() + if err != nil { + return fmt.Errorf("invalid IssuedBy: %w", err) + } + r.IssuedBy, err = util.Uint160DecodeBytesBE(issuedBy) + if err != nil { + return fmt.Errorf("invalid IssuedBy address: %w", err) + } + + issuedAt, err := items[4].TryInteger() + if err != nil { + return fmt.Errorf("invalid IssuedAt: %w", err) + } + r.IssuedAt = uint32(issuedAt.Uint64()) + + expiresAt, err := items[5].TryInteger() + if err != nil { + return fmt.Errorf("invalid ExpiresAt: %w", err) + } + r.ExpiresAt = uint32(expiresAt.Uint64()) + + reason, err := items[6].TryBytes() + if err != nil { + return fmt.Errorf("invalid Reason: %w", err) + } + r.Reason = string(reason) + + caseID, err := items[7].TryBytes() + if err != nil { + return fmt.Errorf("invalid CaseID: %w", err) + } + if len(caseID) == 32 { + copy(r.CaseID[:], caseID) + } + + return nil +} + +// Bytes serializes the RightsRestriction to bytes. +func (r *RightsRestriction) Bytes() []byte { + item, _ := r.ToStackItem() + data, _ := stackitem.Serialize(item) + return data +} + +// RightsRestrictionFromBytes deserializes a RightsRestriction from bytes. +func RightsRestrictionFromBytes(data []byte) (*RightsRestriction, error) { + item, err := stackitem.Deserialize(data) + if err != nil { + return nil, err + } + r := &RightsRestriction{} + if err := r.FromStackItem(item); err != nil { + return nil, err + } + return r, nil +} + +// IsExpired returns true if the restriction has expired. +func (r *RightsRestriction) IsExpired(currentBlock uint32) bool { + return r.ExpiresAt != 0 && r.ExpiresAt <= currentBlock +} + +// ConstitutionalRight represents an immutable constitutional right. +type ConstitutionalRight struct { + ID uint64 // Right ID (1-14) + Name string // Human-readable name + Description string // Description of the right + Enforcement EnforcementType // How right is enforced +} + +// GetConstitutionalRights returns all 14 constitutional rights. +// These are hardcoded and immutable. +func GetConstitutionalRights() []ConstitutionalRight { + return []ConstitutionalRight{ + {RightLife, "Life", "Protection from arbitrary death", EnforcementAutomatic}, + {RightLiberty, "Liberty", "Freedom from arbitrary detention", EnforcementAutomatic}, + {RightProperty, "Property", "Right to own and transfer assets", EnforcementAutomatic}, + {RightEquality, "Equality", "Equal treatment under law", EnforcementAutomatic}, + {RightDueProcess, "DueProcess", "Fair legal proceedings before restriction", EnforcementAutomatic}, + {RightPrivacy, "Privacy", "Privacy of personal data", EnforcementAutomatic}, + {RightExpression, "Expression", "Freedom of expression", EnforcementLogging}, + {RightAssembly, "Assembly", "Freedom of assembly", EnforcementLogging}, + {RightMovement, "Movement", "Freedom of movement", EnforcementAutomatic}, + {RightEducation, "Education", "Right to education", EnforcementLogging}, + {RightHealthcare, "Healthcare", "Right to healthcare", EnforcementLogging}, + {RightLabor, "Labor", "Right to fair labor conditions", EnforcementLogging}, + {RightVote, "Vote", "Right to participate in democracy", EnforcementAutomatic}, + {RightAsylum, "Asylum", "Right to seek protection from persecution", EnforcementAutomatic}, + } +} + +// GetConstitutionalRight returns a specific constitutional right by ID. +func GetConstitutionalRight(id uint64) *ConstitutionalRight { + if id < 1 || id > MaxConstitutionalRight { + return nil + } + rights := GetConstitutionalRights() + return &rights[id-1] +} + +// IsConstitutionalRight returns true if the right ID is a constitutional right. +func IsConstitutionalRight(id uint64) bool { + return id >= 1 && id <= MaxConstitutionalRight +}