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

985 lines
31 KiB
Go

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
Tutus ITutus
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.Tutus != nil && l.Tutus.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
}