package native import ( "encoding/binary" "errors" "fmt" "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) } // RatifyAmendmentInternal creates a new law from a passed Eligere proposal. // This is called by the Eligere contract when a law amendment proposal passes. // Returns the new law ID. func (l *Lex) RatifyAmendmentInternal(ic *interop.Context, proposalID uint64, contentHash util.Uint256, category state.LawCategory, jurisdiction uint32) uint64 { // Category validation if category < state.LawCategoryFederal || category > state.LawCategoryAdministrative { panic(ErrInvalidLawCategory) } // Cannot create constitutional laws via amendment - they are immutable if category == state.LawCategoryConstitutional { panic(ErrCannotModifyConst) } // Create law from ratified proposal lawID := l.getLawCounter(ic.DAO) name := fmt.Sprintf("Amendment_%d", proposalID) law := &state.Law{ ID: lawID, Name: name, ContentHash: contentHash, Category: category, Jurisdiction: jurisdiction, ParentID: 0, EffectiveAt: ic.Block.Index, ExpiresAt: 0, // Perpetual by default EnactedAt: ic.Block.Index, EnactedBy: ic.VM.GetCallingScriptHash(), Status: state.LawStatusActive, SupersededBy: 0, RequiresVita: true, Enforcement: state.EnforcementAutomatic, } 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 lawID }