package native import ( "encoding/binary" "errors" "math/big" "github.com/tutus-one/tutus-chain/pkg/core/dao" "github.com/tutus-one/tutus-chain/pkg/core/storage" "github.com/tutus-one/tutus-chain/pkg/util" "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" ) // LOW-003: Configuration Governance // Centralized configuration registry for managing system-wide parameters. // All hardcoded values should be moved here for transparent governance. // ConfigCategory represents a category of configuration values. type ConfigCategory uint8 const ( // ConfigCategorySystem covers core system parameters ConfigCategorySystem ConfigCategory = iota // ConfigCategoryIdentity covers Vita/identity parameters ConfigCategoryIdentity // ConfigCategoryEconomic covers VTS/Tribute/economic parameters ConfigCategoryEconomic // ConfigCategoryGovernance covers voting/committee parameters ConfigCategoryGovernance // ConfigCategoryHealth covers Salus/healthcare parameters ConfigCategoryHealth // ConfigCategoryEducation covers Scire/education parameters ConfigCategoryEducation // ConfigCategoryLegal covers Lex/legal parameters ConfigCategoryLegal // ConfigCategorySecurity covers security-related parameters ConfigCategorySecurity ) // ConfigEntry represents a single configuration value. type ConfigEntry struct { Key string // Unique key (category:name format) Category ConfigCategory // Category for grouping ValueType ConfigValueType CurrentValue []byte // Current value (serialized) DefaultValue []byte // Default value (for reference) MinValue []byte // Minimum allowed value (if applicable) MaxValue []byte // Maximum allowed value (if applicable) Description string // Human-readable description RequiresVote bool // Whether changes require governance vote LastModified uint32 // Block height of last modification ModifiedBy util.Uint160 // Who made the last modification } // ConfigValueType represents the type of a configuration value. type ConfigValueType uint8 const ( ConfigTypeUint64 ConfigValueType = iota ConfigTypeInt64 ConfigTypeBool ConfigTypeString ConfigTypeHash160 ConfigTypeHash256 ConfigTypeBytes ) // ConfigChangeProposal represents a pending configuration change. type ConfigChangeProposal struct { ProposalID uint64 Key string NewValue []byte Proposer util.Uint160 ProposedAt uint32 ExpiresAt uint32 Approvals []util.Uint160 RequiredVotes uint32 Status ConfigProposalStatus } // ConfigProposalStatus represents the status of a config change proposal. type ConfigProposalStatus uint8 const ( ConfigProposalPending ConfigProposalStatus = iota ConfigProposalApproved ConfigProposalRejected ConfigProposalExpired ConfigProposalExecuted ) // Storage prefixes for configuration registry. const ( configRegPrefixEntry byte = 0xC0 // key -> ConfigEntry configRegPrefixByCategory byte = 0xC1 // category + key -> exists configRegPrefixProposal byte = 0xC2 // proposalID -> ConfigChangeProposal configRegPrefixProposalCtr byte = 0xCF // -> next proposalID ) // Configuration errors. var ( ErrConfigNotFound = errors.New("configuration key not found") ErrConfigInvalidValue = errors.New("invalid configuration value") ErrConfigOutOfRange = errors.New("configuration value out of allowed range") ErrConfigRequiresVote = errors.New("configuration change requires governance vote") ErrConfigProposalExists = errors.New("pending proposal already exists for this key") ) // ConfigRegistry manages system-wide configuration. type ConfigRegistry struct { contractID int32 } // NewConfigRegistry creates a new configuration registry. func NewConfigRegistry(contractID int32) *ConfigRegistry { return &ConfigRegistry{contractID: contractID} } // GetEntry retrieves a configuration entry by key. func (cr *ConfigRegistry) GetEntry(d *dao.Simple, key string) *ConfigEntry { storageKey := cr.makeEntryKey(key) si := d.GetStorageItem(cr.contractID, storageKey) if si == nil { return nil } return cr.deserializeEntry(si) } // SetEntry stores a configuration entry. func (cr *ConfigRegistry) SetEntry(d *dao.Simple, entry *ConfigEntry) { storageKey := cr.makeEntryKey(entry.Key) data := cr.serializeEntry(entry) d.PutStorageItem(cr.contractID, storageKey, data) // Also index by category catKey := cr.makeCategoryKey(entry.Category, entry.Key) d.PutStorageItem(cr.contractID, catKey, []byte{1}) } // GetUint64 retrieves a uint64 configuration value. func (cr *ConfigRegistry) GetUint64(d *dao.Simple, key string) (uint64, error) { entry := cr.GetEntry(d, key) if entry == nil { return 0, ErrConfigNotFound } if entry.ValueType != ConfigTypeUint64 || len(entry.CurrentValue) < 8 { return 0, ErrConfigInvalidValue } return binary.BigEndian.Uint64(entry.CurrentValue), nil } // GetUint64OrDefault retrieves a uint64 value or returns default if not found. func (cr *ConfigRegistry) GetUint64OrDefault(d *dao.Simple, key string, defaultVal uint64) uint64 { val, err := cr.GetUint64(d, key) if err != nil { return defaultVal } return val } // SetUint64 sets a uint64 configuration value. func (cr *ConfigRegistry) SetUint64(d *dao.Simple, key string, value uint64, modifier util.Uint160, blockHeight uint32) error { entry := cr.GetEntry(d, key) if entry == nil { return ErrConfigNotFound } if entry.ValueType != ConfigTypeUint64 { return ErrConfigInvalidValue } // Validate range if len(entry.MinValue) >= 8 && len(entry.MaxValue) >= 8 { minVal := binary.BigEndian.Uint64(entry.MinValue) maxVal := binary.BigEndian.Uint64(entry.MaxValue) if value < minVal || value > maxVal { return ErrConfigOutOfRange } } entry.CurrentValue = make([]byte, 8) binary.BigEndian.PutUint64(entry.CurrentValue, value) entry.LastModified = blockHeight entry.ModifiedBy = modifier cr.SetEntry(d, entry) return nil } // GetBool retrieves a boolean configuration value. func (cr *ConfigRegistry) GetBool(d *dao.Simple, key string) (bool, error) { entry := cr.GetEntry(d, key) if entry == nil { return false, ErrConfigNotFound } if entry.ValueType != ConfigTypeBool || len(entry.CurrentValue) < 1 { return false, ErrConfigInvalidValue } return entry.CurrentValue[0] == 1, nil } // GetBoolOrDefault retrieves a bool value or returns default if not found. func (cr *ConfigRegistry) GetBoolOrDefault(d *dao.Simple, key string, defaultVal bool) bool { val, err := cr.GetBool(d, key) if err != nil { return defaultVal } return val } // GetEntriesByCategory retrieves all entries in a category. func (cr *ConfigRegistry) GetEntriesByCategory(d *dao.Simple, category ConfigCategory) []*ConfigEntry { var entries []*ConfigEntry prefix := []byte{configRegPrefixByCategory, byte(category)} d.Seek(cr.contractID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool { if len(k) > 1 { key := string(k[1:]) // Skip category byte entry := cr.GetEntry(d, key) if entry != nil { entries = append(entries, entry) } } return true }) return entries } // RegisterConfig registers a new configuration entry with defaults. func (cr *ConfigRegistry) RegisterConfig(d *dao.Simple, key string, category ConfigCategory, valueType ConfigValueType, defaultValue []byte, minValue, maxValue []byte, description string, requiresVote bool) { entry := &ConfigEntry{ Key: key, Category: category, ValueType: valueType, CurrentValue: defaultValue, DefaultValue: defaultValue, MinValue: minValue, MaxValue: maxValue, Description: description, RequiresVote: requiresVote, } cr.SetEntry(d, entry) } // ToStackItem converts ConfigEntry to stack item for RPC. func (e *ConfigEntry) ToStackItem() stackitem.Item { return stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray([]byte(e.Key)), stackitem.NewBigInteger(big.NewInt(int64(e.Category))), stackitem.NewBigInteger(big.NewInt(int64(e.ValueType))), stackitem.NewByteArray(e.CurrentValue), stackitem.NewByteArray(e.DefaultValue), stackitem.NewByteArray([]byte(e.Description)), stackitem.NewBool(e.RequiresVote), stackitem.NewBigInteger(big.NewInt(int64(e.LastModified))), }) } // Helper methods for storage keys. func (cr *ConfigRegistry) makeEntryKey(key string) []byte { result := make([]byte, 1+len(key)) result[0] = configRegPrefixEntry copy(result[1:], key) return result } func (cr *ConfigRegistry) makeCategoryKey(category ConfigCategory, key string) []byte { result := make([]byte, 2+len(key)) result[0] = configRegPrefixByCategory result[1] = byte(category) copy(result[2:], key) return result } // Serialization helpers. func (cr *ConfigRegistry) serializeEntry(e *ConfigEntry) []byte { keyBytes := []byte(e.Key) descBytes := []byte(e.Description) size := 4 + len(keyBytes) + // key length + key 1 + // category 1 + // value type 4 + len(e.CurrentValue) + // current value length + value 4 + len(e.DefaultValue) + // default value length + value 4 + len(e.MinValue) + // min value length + value 4 + len(e.MaxValue) + // max value length + value 4 + len(descBytes) + // description length + desc 1 + // requires vote 4 + // last modified 20 // modified by data := make([]byte, size) offset := 0 // Key binary.BigEndian.PutUint32(data[offset:], uint32(len(keyBytes))) offset += 4 copy(data[offset:], keyBytes) offset += len(keyBytes) // Category data[offset] = byte(e.Category) offset++ // Value type data[offset] = byte(e.ValueType) offset++ // Current value binary.BigEndian.PutUint32(data[offset:], uint32(len(e.CurrentValue))) offset += 4 copy(data[offset:], e.CurrentValue) offset += len(e.CurrentValue) // Default value binary.BigEndian.PutUint32(data[offset:], uint32(len(e.DefaultValue))) offset += 4 copy(data[offset:], e.DefaultValue) offset += len(e.DefaultValue) // Min value binary.BigEndian.PutUint32(data[offset:], uint32(len(e.MinValue))) offset += 4 copy(data[offset:], e.MinValue) offset += len(e.MinValue) // Max value binary.BigEndian.PutUint32(data[offset:], uint32(len(e.MaxValue))) offset += 4 copy(data[offset:], e.MaxValue) offset += len(e.MaxValue) // Description binary.BigEndian.PutUint32(data[offset:], uint32(len(descBytes))) offset += 4 copy(data[offset:], descBytes) offset += len(descBytes) // Requires vote if e.RequiresVote { data[offset] = 1 } offset++ // Last modified binary.BigEndian.PutUint32(data[offset:], e.LastModified) offset += 4 // Modified by copy(data[offset:], e.ModifiedBy.BytesBE()) return data } func (cr *ConfigRegistry) deserializeEntry(data []byte) *ConfigEntry { if len(data) < 10 { return nil } e := &ConfigEntry{} offset := 0 // Key keyLen := binary.BigEndian.Uint32(data[offset:]) offset += 4 if offset+int(keyLen) > len(data) { return nil } e.Key = string(data[offset : offset+int(keyLen)]) offset += int(keyLen) // Category e.Category = ConfigCategory(data[offset]) offset++ // Value type e.ValueType = ConfigValueType(data[offset]) offset++ // Current value if offset+4 > len(data) { return nil } valLen := binary.BigEndian.Uint32(data[offset:]) offset += 4 if offset+int(valLen) > len(data) { return nil } e.CurrentValue = make([]byte, valLen) copy(e.CurrentValue, data[offset:offset+int(valLen)]) offset += int(valLen) // Default value if offset+4 > len(data) { return nil } defLen := binary.BigEndian.Uint32(data[offset:]) offset += 4 if offset+int(defLen) > len(data) { return nil } e.DefaultValue = make([]byte, defLen) copy(e.DefaultValue, data[offset:offset+int(defLen)]) offset += int(defLen) // Min value if offset+4 > len(data) { return nil } minLen := binary.BigEndian.Uint32(data[offset:]) offset += 4 if offset+int(minLen) > len(data) { return nil } e.MinValue = make([]byte, minLen) copy(e.MinValue, data[offset:offset+int(minLen)]) offset += int(minLen) // Max value if offset+4 > len(data) { return nil } maxLen := binary.BigEndian.Uint32(data[offset:]) offset += 4 if offset+int(maxLen) > len(data) { return nil } e.MaxValue = make([]byte, maxLen) copy(e.MaxValue, data[offset:offset+int(maxLen)]) offset += int(maxLen) // Description if offset+4 > len(data) { return nil } descLen := binary.BigEndian.Uint32(data[offset:]) offset += 4 if offset+int(descLen) > len(data) { return nil } e.Description = string(data[offset : offset+int(descLen)]) offset += int(descLen) // Requires vote if offset >= len(data) { return nil } e.RequiresVote = data[offset] == 1 offset++ // Last modified if offset+4 > len(data) { return nil } e.LastModified = binary.BigEndian.Uint32(data[offset:]) offset += 4 // Modified by if offset+20 > len(data) { return nil } e.ModifiedBy, _ = util.Uint160DecodeBytesBE(data[offset : offset+20]) return e } // StandardConfigKeys defines standard configuration keys used across contracts. var StandardConfigKeys = struct { // System MaxQueryLimit string DefaultPageSize string BlockInterval string // Security RecoveryDelay string RequiredApprovals string RateLimitBlocks string ProofExpiryBlocks string // Economic TributeRateMild string TributeRateSevere string AsylumQuotaPerYear string // Governance VotingAge string ProposalQuorum string ProposalThreshold string }{ MaxQueryLimit: "system:max_query_limit", DefaultPageSize: "system:default_page_size", BlockInterval: "system:block_interval", RecoveryDelay: "security:recovery_delay", RequiredApprovals: "security:required_approvals", RateLimitBlocks: "security:rate_limit_blocks", ProofExpiryBlocks: "security:proof_expiry_blocks", TributeRateMild: "economic:tribute_rate_mild", TributeRateSevere: "economic:tribute_rate_severe", AsylumQuotaPerYear: "economic:asylum_quota_per_year", VotingAge: "governance:voting_age", ProposalQuorum: "governance:proposal_quorum", ProposalThreshold: "governance:proposal_threshold", }