985 lines
31 KiB
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
|
|
Annos IAnnos
|
|
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.Annos != nil && l.Annos.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
|
|
}
|