Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

parameter_registry.gno

36.99 Kb · 1270 lines
   1package governance
   2
   3import (
   4	"strings"
   5
   6	prbac "gno.land/p/gnoswap/rbac/v1"
   7	ufmt "gno.land/p/nt/ufmt/v0"
   8
   9	cm "gno.land/r/gnoswap/common"
  10	cp "gno.land/r/gnoswap/community_pool/v1"
  11	en "gno.land/r/gnoswap/emission"
  12	"gno.land/r/gnoswap/rbac/v1"
  13
  14	"gno.land/r/gnoswap/gov/governance"
  15	gs "gno.land/r/gnoswap/gov/staker"
  16	lp "gno.land/r/gnoswap/launchpad"
  17	pl "gno.land/r/gnoswap/pool"
  18	pos "gno.land/r/gnoswap/position"
  19	pf "gno.land/r/gnoswap/protocol_fee"
  20	rr "gno.land/r/gnoswap/router"
  21	sr "gno.land/r/gnoswap/staker"
  22
  23	"gno.land/r/gnoswap/halt/v1"
  24)
  25
  26// globalParameterRegistry is initialized once and reused for all validation and execution.
  27// This provides consistent validation at proposal creation time and execution time.
  28var globalParameterRegistry *ParameterRegistry
  29
  30func init() {
  31	globalParameterRegistry = CreateParameterHandlers()
  32}
  33
  34// Package paths
  35const (
  36	GNS_TOKEN_KEY       = "gno.land/r/gnoswap/gns.GNS"
  37	HALT_PATH           = "gno.land/r/gnoswap/halt/v1"
  38	RBAC_PATH           = "gno.land/r/gnoswap/rbac/v1"
  39	ACCESS_PATH         = "gno.land/r/gnoswap/access/v1"
  40	EMISSION_PATH       = "gno.land/r/gnoswap/emission"
  41	COMMON_PATH         = "gno.land/r/gnoswap/common"
  42	POOL_PATH           = "gno.land/r/gnoswap/pool"
  43	POSITION_PATH       = "gno.land/r/gnoswap/position"
  44	ROUTER_PATH         = "gno.land/r/gnoswap/router"
  45	STAKER_PATH         = "gno.land/r/gnoswap/staker"
  46	LAUNCHPAD_PATH      = "gno.land/r/gnoswap/launchpad"
  47	PROTOCOL_FEE_PATH   = "gno.land/r/gnoswap/protocol_fee"
  48	COMMUNITY_POOL_PATH = "gno.land/r/gnoswap/community_pool/v1"
  49	GOV_GOVERNANCE_PATH = "gno.land/r/gnoswap/gov/governance"
  50	GOV_STAKER_PATH     = "gno.land/r/gnoswap/gov/staker"
  51)
  52
  53// ParameterHandler interface defines the contract for parameter execution handlers.
  54// Each handler is responsible for executing specific parameter changes in the system.
  55type ParameterHandler interface {
  56	// Execute processes the parameters and applies the changes to the system.
  57	// The `_ int, rlm realm` discriminator pair forwards the governance proxy's
  58	// realm value into the handler so any cross-realm calls inside the closure
  59	// run under the proxy's identity (the only address with caller-allowlist
  60	// permission against the targeted /r/ realms).
  61	//
  62	// Parameters:
  63	//   - _: integer discriminator required by the crossing entrypoint; callers use 0
  64	//   - rlm: current governance proxy realm context forwarded to the handler
  65	//   - params: serialized parameter values to validate and apply in handler order
  66	//
  67	// Returns:
  68	//   - error: nil when the parameter change succeeds, or an execution/validation error
  69	Execute(_ int, rlm realm, params []string) error
  70}
  71
  72// ParameterHandlerOptions contains the configuration and execution logic for a parameter handler.
  73// This struct encapsulates all information needed to identify and execute a parameter change.
  74//
  75// NOTE: handlerFunc uses `rlm realm` (rather than `cur realm`) as the realm
  76// parameter name. The v2 preprocessor reserves the `cur` name for the first
  77// realm-type parameter of top-level crossing function declarations and
  78// `t.Run` closures only; using it as the realm-parameter name on a
  79// multi-parameter struct-field function value trips a parser check
  80// ("only the first realm type argument of a crossing function may have name
  81// `cur`"). Naming it `rlm` keeps the lowering identical without the syntax
  82// constraint.
  83type ParameterHandlerOptions struct {
  84	pkgPath            string                                   // Package path of the target contract
  85	function           string                                   // Function name to be called
  86	paramCount         int                                      // Expected number of parameters
  87	handlerFunc        func(_ int, rlm realm, _ []string) error // Function that executes the parameter change
  88	paramValidators    []paramValidator                         // Optional per-parameter validators for proposal-time checks
  89	compositeValidator compositeValidator                       // Optional cross-parameter validator for static business rules
  90}
  91
  92// paramValidator validates a single parameter value and returns an error on failure.
  93type paramValidator func(string) error
  94
  95// compositeValidator validates relationships between a handler's parameters.
  96// It must only enforce deterministic, state-independent rules so that a
  97// proposal is not rejected merely because on-chain state changes while voting.
  98type compositeValidator func([]string) error
  99
 100// NewParameterHandlerOptions creates a new parameter handler with the specified configuration.
 101//
 102// Parameters:
 103//   - pkgPath: package path of the target contract
 104//   - function: function name to be called
 105//   - paramCount: expected number of parameters
 106//   - handlerFunc: callback receiving the discriminator, propagated realm, and serialized parameters to execute the change
 107//   - paramValidators: optional validators for each parameter (must match paramCount if provided)
 108//
 109// Returns:
 110//   - ParameterHandler: configured parameter handler interface
 111func NewParameterHandlerOptions(
 112	pkgPath,
 113	function string,
 114	paramCount int,
 115	handlerFunc func(_ int, rlm realm, _ []string) error,
 116	paramValidators ...paramValidator,
 117) ParameterHandler {
 118	if len(paramValidators) > 0 && len(paramValidators) != paramCount {
 119		panic(ufmt.Sprintf(
 120			"invalid validator count for %s:%s: expected %d, got %d",
 121			pkgPath, function, paramCount, len(paramValidators),
 122		))
 123	}
 124
 125	return &ParameterHandlerOptions{
 126		pkgPath:         pkgPath,
 127		function:        function,
 128		paramCount:      paramCount,
 129		handlerFunc:     handlerFunc,
 130		paramValidators: paramValidators,
 131	}
 132}
 133
 134// HandlerKey generates a unique key for this handler based on package path and function name.
 135//
 136// Returns:
 137//   - string: unique identifier for the handler
 138func (h *ParameterHandlerOptions) HandlerKey() string {
 139	return makeHandlerKey(h.pkgPath, h.function)
 140}
 141
 142// Execute validates parameter count and executes the handler function.
 143// This method ensures the correct number of parameters are provided before execution.
 144//
 145// Parameters:
 146//   - _: integer discriminator required by the crossing entrypoint; callers use 0
 147//   - rlm: governance proxy realm threaded into the wrapped handler
 148//   - params: serialized parameter values to pass to the handler
 149//
 150// Returns:
 151//   - error: parameter-count error or the error returned by the wrapped handler
 152func (h *ParameterHandlerOptions) Execute(_ int, rlm realm, params []string) error {
 153	if len(params) != h.paramCount {
 154		return ufmt.Errorf("expected %d parameters, got %d", h.paramCount, len(params))
 155	}
 156
 157	return h.handlerFunc(0, rlm, params)
 158}
 159
 160// ValidateParams checks the parameter count and runs configured individual and composite validators without executing the handler.
 161//
 162// Parameters:
 163//   - params: serialized parameter values to validate in handler order
 164//
 165// Returns:
 166//   - error: nil when all configured checks pass, or the first count/type/business-rule validation error
 167func (h *ParameterHandlerOptions) ValidateParams(params []string) error {
 168	if len(params) != h.paramCount {
 169		return ufmt.Errorf(
 170			"expected %d parameters for %s, got %d",
 171			h.paramCount, h.HandlerKey(), len(params),
 172		)
 173	}
 174
 175	if len(h.paramValidators) > 0 {
 176		if len(h.paramValidators) != h.paramCount {
 177			return ufmt.Errorf(
 178				"validator count mismatch for %s: expected %d validator(s), got %d",
 179				h.HandlerKey(), h.paramCount, len(h.paramValidators),
 180			)
 181		}
 182
 183		for i, validator := range h.paramValidators {
 184			if validator == nil {
 185				continue
 186			}
 187
 188			if err := validator(params[i]); err != nil {
 189				return ufmt.Errorf("param[%d]: %v", i, err)
 190			}
 191		}
 192	}
 193
 194	if h.compositeValidator != nil {
 195		if err := h.compositeValidator(params); err != nil {
 196			return err
 197		}
 198	}
 199
 200	return nil
 201}
 202
 203// ParameterRegistry manages the collection of parameter handlers for governance execution.
 204// This registry allows proposals to execute parameter changes across different system contracts.
 205type ParameterRegistry struct {
 206	handlers map[string]ParameterHandlerOptions // Map storing handler configurations keyed by package:function
 207}
 208
 209// Register adds a new parameter handler to the registry.
 210// Each handler is identified by a unique combination of package path and function name.
 211//
 212// Parameters:
 213//   - handler: parameter handler configuration to register
 214func (r *ParameterRegistry) Register(handler ParameterHandlerOptions) {
 215	r.handlers[handler.HandlerKey()] = handler
 216}
 217
 218// Handler retrieves a parameter handler by its package:function key.
 219// This method is used during proposal execution to find the appropriate handler.
 220//
 221// Parameters:
 222//   - key: handler key returned by HandlerKey, in "pkgPath:function" format
 223//
 224// Returns:
 225//   - ParameterHandler: the matching parameter handler
 226//   - error: error if the key has no registered handler
 227func (r *ParameterRegistry) Handler(key string) (ParameterHandler, error) {
 228	// Retrieve handler from registry
 229	handler, exists := r.handlers[key]
 230	if !exists {
 231		return nil, ufmt.Errorf("handler not found for %s", key)
 232	}
 233
 234	return &handler, nil
 235}
 236
 237// NewParameterRegistry creates a new empty parameter registry.
 238//
 239// Returns:
 240//   - *ParameterRegistry: new registry instance
 241func NewParameterRegistry() *ParameterRegistry {
 242	return &ParameterRegistry{handlers: make(map[string]ParameterHandlerOptions)}
 243}
 244
 245// makeHandlerKey creates a unique identifier for a handler based on package path and function.
 246//
 247// Parameters:
 248//   - pkgPath: package path of the target contract
 249//   - function: function name to be called
 250//
 251// Returns:
 252//   - string: unique key in format "pkgPath:function"
 253func makeHandlerKey(pkgPath, function string) string {
 254	return pkgPath + ":" + function
 255}
 256
 257// CreateParameterHandlers initializes and configures all supported parameter handlers.
 258// This function defines all the parameter changes that can be executed through governance proposals.
 259// It covers configuration changes for various system components including pools, staking, fees, etc.
 260//
 261// Returns:
 262//   - *ParameterRegistry: fully configured registry with all supported handlers
 263func CreateParameterHandlers() *ParameterRegistry {
 264	registry := NewParameterRegistry()
 265
 266	// Define all handler configurations for different system components.
 267	//
 268	// Each closure receives `(_ int, rlm realm, params []string)` and threads
 269	// `rlm` into the targeted /r/ realm call. The closure is invoked from
 270	// ParameterHandlerOptions.Execute which forwards the governance proxy's
 271	// realm value, so cross-realm calls land on the targeted realm with the
 272	// governance proxy as the immediate caller.
 273	handlers := []*ParameterHandlerOptions{
 274		// Community pool token transfers
 275		{
 276			pkgPath:    COMMUNITY_POOL_PATH,
 277			function:   "TransferToken",
 278			paramCount: 3,
 279			paramValidators: []paramValidator{
 280				stringValidator,  // pkgPath
 281				addressValidator, // to
 282				positiveInt64Validator("amount"),
 283			},
 284			handlerFunc: func(_ int, rlm realm, params []string) error {
 285				// Transfer tokens from community pool to specified address
 286				cp.TransferToken(
 287					cross(rlm),
 288					params[0],          // pkgPath
 289					address(params[1]), // to
 290					parseNumber(params[2], kindInt64).(int64), // amount
 291				)
 292
 293				return nil
 294			},
 295		},
 296		// Emission distribution configuration
 297		{
 298			pkgPath:    EMISSION_PATH,
 299			function:   "SetDistributionStartTime",
 300			paramCount: 1,
 301			paramValidators: []paramValidator{
 302				numberValidator(kindInt64), // start time
 303			},
 304			handlerFunc: func(_ int, rlm realm, params []string) error {
 305				// Set distribution start time
 306				en.SetDistributionStartTime(cross(rlm), parseInt64(params[0]))
 307
 308				return nil
 309			},
 310		},
 311		{
 312			pkgPath:    EMISSION_PATH,
 313			function:   "ChangeDistributionPct",
 314			paramCount: 4,
 315			paramValidators: []paramValidator{
 316				numberValidator(kindInt64), // liquidityStakerPct
 317				numberValidator(kindInt64), // devOpsPct
 318				numberValidator(kindInt64), // communityPoolPct
 319				numberValidator(kindInt64), // govStakerPct
 320			},
 321			compositeValidator: distributionPctCompositeValidator,
 322			handlerFunc: func(_ int, rlm realm, params []string) error {
 323				liquidityStakerPct := parseNumber(params[0], kindInt64).(int64)
 324				devOpsPct := parseNumber(params[1], kindInt64).(int64)
 325				communityPoolPct := parseNumber(params[2], kindInt64).(int64)
 326				govStakerPct := parseNumber(params[3], kindInt64).(int64)
 327
 328				en.ChangeDistributionPct(
 329					cross(rlm),
 330					liquidityStakerPct,
 331					devOpsPct,
 332					communityPoolPct,
 333					govStakerPct,
 334				)
 335
 336				return nil
 337			},
 338		},
 339		// Governance configuration changes
 340		{
 341			pkgPath:    GOV_GOVERNANCE_PATH,
 342			function:   "Reconfigure",
 343			paramCount: 7,
 344			paramValidators: []paramValidator{
 345				numberValidator(kindInt64), // votingStartDelay
 346				numberValidator(kindInt64), // votingPeriod
 347				numberValidator(kindInt64), // votingWeightSmoothingDuration
 348				numberValidator(kindInt64), // quorum
 349				numberValidator(kindInt64), // proposalCreationThreshold
 350				numberValidator(kindInt64), // executionDelay
 351				numberValidator(kindInt64), // executionWindow
 352			},
 353			handlerFunc: func(_ int, rlm realm, params []string) error {
 354				// Parse governance configuration parameters
 355				votingStartDelay := parseInt64(params[0])
 356				votingPeriod := parseInt64(params[1])
 357				votingWeightSmoothingDuration := parseInt64(params[2])
 358				quorum := parseInt64(params[3])
 359				proposalCreationThreshold := parseInt64(params[4])
 360				executionDelay := parseInt64(params[5])
 361				executionWindow := parseInt64(params[6])
 362
 363				// Reconfigure governance parameters through governance process
 364				governance.Reconfigure(
 365					cross(rlm),
 366					votingStartDelay,
 367					votingPeriod,
 368					votingWeightSmoothingDuration,
 369					quorum,
 370					proposalCreationThreshold,
 371					executionDelay,
 372					executionWindow,
 373				)
 374
 375				return nil
 376			},
 377		},
 378
 379		// Pool protocol fee configuration
 380		{
 381			pkgPath:    POOL_PATH,
 382			function:   "CollectProtocol",
 383			paramCount: 6,
 384			paramValidators: []paramValidator{
 385				stringValidator,  // token0Path
 386				stringValidator,  // token1Path
 387				uint64Validator,  // fee
 388				addressValidator, // recipient
 389				nonNegativeInt64Validator("amount0Requested"),
 390				nonNegativeInt64Validator("amount1Requested"),
 391			},
 392			handlerFunc: func(_ int, rlm realm, params []string) error {
 393				pl.CollectProtocol(
 394					cross(rlm),
 395					params[0],                      // token0Path
 396					params[1],                      // token1Path
 397					uint32(parseUint64(params[2])), // fee
 398					address(params[3]),             // recipient
 399					params[4],                      // amount0Requested
 400					params[5],                      // amount1Requested
 401				)
 402
 403				return nil
 404			},
 405		},
 406		{
 407			pkgPath:    POOL_PATH,
 408			function:   "SetFeeProtocol",
 409			paramCount: 2,
 410			paramValidators: []paramValidator{
 411				uint8RangeValidator("feeProtocol0"),
 412				uint8RangeValidator("feeProtocol1"),
 413			},
 414			handlerFunc: func(_ int, rlm realm, params []string) error {
 415				// Parse and validate fee protocol values
 416				feeProtocol0 := parseInt64(params[0])
 417				feeProtocol1 := parseInt64(params[1])
 418
 419				// Validate fee protocol values are within uint8 range
 420				if feeProtocol0 > 255 {
 421					panic(ufmt.Sprintf("feeProtocol0 out of range: %d", feeProtocol0))
 422				}
 423
 424				if feeProtocol1 > 255 {
 425					panic(ufmt.Sprintf("feeProtocol1 out of range: %d", feeProtocol1))
 426				}
 427
 428				// Set protocol fee percentages
 429				pl.SetFeeProtocol(
 430					cross(rlm),
 431					uint8(feeProtocol0), // feeProtocol0
 432					uint8(feeProtocol1), // feeProtocol1
 433				)
 434
 435				return nil
 436			},
 437		},
 438		// Pool creation fee
 439		{
 440			pkgPath:    POOL_PATH,
 441			function:   "SetPoolCreationFee",
 442			paramCount: 1,
 443			paramValidators: []paramValidator{
 444				nonNegativeInt64Validator("fee"),
 445			},
 446			handlerFunc: func(_ int, rlm realm, params []string) error {
 447				// Set fee required to create new pools
 448				pl.SetPoolCreationFee(cross(rlm), parseInt64(params[0])) // fee
 449				return nil
 450			},
 451		},
 452		// Pool withdrawal fee
 453		{
 454			pkgPath:    POOL_PATH,
 455			function:   "SetWithdrawalFee",
 456			paramCount: 1,
 457			paramValidators: []paramValidator{
 458				uint64RangeValidator("fee", 1000),
 459			},
 460			handlerFunc: func(_ int, rlm realm, params []string) error {
 461				// Set fee for withdrawing from pools
 462				pl.SetWithdrawalFee(cross(rlm), parseUint64(params[0])) // fee
 463				return nil
 464			},
 465		},
 466
 467		// Protocol fee distribution
 468		{
 469			pkgPath:    PROTOCOL_FEE_PATH,
 470			function:   "SetDevOpsPct",
 471			paramCount: 1,
 472			paramValidators: []paramValidator{
 473				int64RangeValidator("pct", 0, 10000),
 474			},
 475			handlerFunc: func(_ int, rlm realm, params []string) error {
 476				// Set percentage of protocol fees going to development operations
 477				pf.SetDevOpsPct(cross(rlm), parseInt64(params[0])) // pct
 478				return nil
 479			},
 480		},
 481
 482		// Router swap fee
 483		{
 484			pkgPath:    ROUTER_PATH,
 485			function:   "SetSwapFee",
 486			paramCount: 1,
 487			paramValidators: []paramValidator{
 488				uint64RangeValidator("fee", 1000),
 489			},
 490			handlerFunc: func(_ int, rlm realm, params []string) error {
 491				// Set fee charged for token swaps
 492				rr.SetSwapFee(cross(rlm), parseUint64(params[0])) // fee
 493				return nil
 494			},
 495		},
 496
 497		// Staker configuration handlers
 498		{
 499			pkgPath:    STAKER_PATH,
 500			function:   "SetDepositGnsAmount",
 501			paramCount: 1,
 502			paramValidators: []paramValidator{
 503				nonNegativeInt64Validator("amount"),
 504			},
 505			handlerFunc: func(_ int, rlm realm, params []string) error {
 506				// Set minimum GNS amount required for staking deposits
 507				sr.SetDepositGnsAmount(cross(rlm), parseInt64(params[0])) // amount
 508				return nil
 509			},
 510		},
 511		{
 512			pkgPath:    STAKER_PATH,
 513			function:   "SetMinimumRewardAmount",
 514			paramCount: 1,
 515			paramValidators: []paramValidator{
 516				nonNegativeInt64Validator("amount"),
 517			},
 518			handlerFunc: func(_ int, rlm realm, params []string) error {
 519				// Set minimum GNS amount required for staking deposits
 520				sr.SetMinimumRewardAmount(cross(rlm), parseInt64(params[0])) // amount
 521				return nil
 522			},
 523		},
 524		{
 525			pkgPath:    STAKER_PATH,
 526			function:   "SetTokenMinimumRewardAmount",
 527			paramCount: 1,
 528			paramValidators: []paramValidator{
 529				tokenMinimumRewardAmountValidator,
 530			},
 531			handlerFunc: func(_ int, rlm realm, params []string) error {
 532				// Set minimum GNS amount required for staking deposits
 533				// params[0] is a string in the format "tokenPath:amount"
 534				sr.SetTokenMinimumRewardAmount(cross(rlm), params[0]) // amount
 535				return nil
 536			},
 537		},
 538		{
 539			pkgPath:    STAKER_PATH,
 540			function:   "SetPoolTier",
 541			paramCount: 2,
 542			paramValidators: []paramValidator{
 543				stringValidator, // pool
 544				uint64RangeValidator("tier", sr.AllTierCount-1),
 545			},
 546			handlerFunc: func(_ int, rlm realm, params []string) error {
 547				// Assign tier level to a specific pool
 548				sr.SetPoolTier(
 549					cross(rlm),
 550					params[0],              // pool
 551					parseUint64(params[1]), // tier
 552				)
 553				return nil
 554			},
 555		},
 556		{
 557			pkgPath:    STAKER_PATH,
 558			function:   "ChangePoolTier",
 559			paramCount: 2,
 560			paramValidators: []paramValidator{
 561				stringValidator, // pool
 562				uint64RangeValidator("tier", sr.AllTierCount-1),
 563			},
 564			handlerFunc: func(_ int, rlm realm, params []string) error {
 565				// Change existing pool's tier level
 566				sr.ChangePoolTier(
 567					cross(rlm),
 568					params[0],              // pool
 569					parseUint64(params[1]), // tier
 570				)
 571				return nil
 572			},
 573		},
 574		{
 575			pkgPath:    STAKER_PATH,
 576			function:   "RemovePoolTier",
 577			paramCount: 1,
 578			paramValidators: []paramValidator{
 579				stringValidator, // pool
 580			},
 581			handlerFunc: func(_ int, rlm realm, params []string) error {
 582				// Remove tier assignment from a pool
 583				sr.RemovePoolTier(cross(rlm), params[0]) // pool
 584				return nil
 585			},
 586		},
 587		{
 588			pkgPath:    STAKER_PATH,
 589			function:   "SetUnStakingFee",
 590			paramCount: 1,
 591			paramValidators: []paramValidator{
 592				uint64RangeValidator("fee", 1000),
 593			},
 594			handlerFunc: func(_ int, rlm realm, params []string) error {
 595				// Set fee charged for unstaking operations
 596				fee := parseUint64(params[0])
 597				sr.SetUnStakingFee(cross(rlm), fee)
 598				return nil
 599			},
 600		},
 601		{
 602			pkgPath:    STAKER_PATH,
 603			function:   "SetWarmUp",
 604			paramCount: 2,
 605			paramValidators: []paramValidator{
 606				numberValidator(kindInt64), // percent
 607				numberValidator(kindInt64), // block
 608			},
 609			handlerFunc: func(_ int, rlm realm, params []string) error {
 610				// Set warm-up period configuration for staking
 611				percent := parseInt64(params[0])
 612				block := parseNumber(params[1], kindInt64).(int64)
 613				sr.SetWarmUp(cross(rlm), percent, block)
 614
 615				return nil
 616			},
 617		},
 618
 619		// System halt controls
 620		{
 621			pkgPath:    HALT_PATH,
 622			function:   "SetHaltLevel",
 623			paramCount: 1,
 624			paramValidators: []paramValidator{
 625				haltLevelValidator,
 626			},
 627			handlerFunc: func(_ int, rlm realm, params []string) error {
 628				// Set system-wide halt status
 629				halt.SetHaltLevel(cross(rlm), halt.HaltLevel(params[0])) // true = halt, false = no halt
 630
 631				return nil
 632			},
 633		},
 634		{
 635			pkgPath:    HALT_PATH,
 636			function:   "SetOperationStatus",
 637			paramCount: 2,
 638			paramValidators: []paramValidator{
 639				haltOperationTypeValidator,
 640				boolValidator, // allowed
 641			},
 642			handlerFunc: func(_ int, rlm realm, params []string) error {
 643				// Enable or disable specific operation types
 644				opType := halt.OpType(params[0])
 645				allowed := parseBool(params[1])
 646
 647				halt.SetOperationStatus(cross(rlm), opType, allowed)
 648
 649				return nil
 650			},
 651		},
 652
 653		// RBAC configuration
 654		{
 655			pkgPath:    RBAC_PATH,
 656			function:   "RegisterRole",
 657			paramCount: 2,
 658			paramValidators: []paramValidator{
 659				roleNameValidator,
 660				addressValidator, // roleAddress
 661			},
 662			handlerFunc: func(_ int, rlm realm, params []string) error {
 663				roleName := params[0]
 664				roleAddress := address(params[1])
 665
 666				// Register a new role
 667				rbac.RegisterRole(cross(rlm), roleName, roleAddress)
 668
 669				return nil
 670			},
 671		},
 672		{
 673			pkgPath:    RBAC_PATH,
 674			function:   "UpdateRoleAddress",
 675			paramCount: 2,
 676			paramValidators: []paramValidator{
 677				updatableRoleNameValidator,
 678				addressValidator, // roleAddress
 679			},
 680			handlerFunc: func(_ int, rlm realm, params []string) error {
 681				roleName := params[0]
 682				roleAddress := address(params[1])
 683
 684				// Update role address
 685				rbac.UpdateRoleAddress(cross(rlm), roleName, roleAddress)
 686
 687				return nil
 688			},
 689		},
 690		{
 691			pkgPath:    RBAC_PATH,
 692			function:   "RemoveRole",
 693			paramCount: 1,
 694			paramValidators: []paramValidator{
 695				removableRoleNameValidator,
 696			},
 697			handlerFunc: func(_ int, rlm realm, params []string) error {
 698				roleName := params[0]
 699
 700				// Remove role
 701				rbac.RemoveRole(cross(rlm), roleName)
 702
 703				return nil
 704			},
 705		},
 706
 707		// Protocol fee - SetGovStakerPct
 708		{
 709			pkgPath:    PROTOCOL_FEE_PATH,
 710			function:   "SetGovStakerPct",
 711			paramCount: 1,
 712			paramValidators: []paramValidator{
 713				int64RangeValidator("pct", 0, 10000),
 714			},
 715			handlerFunc: func(_ int, rlm realm, params []string) error {
 716				// Set percentage of protocol fees going to governance stakers
 717				pf.SetGovStakerPct(cross(rlm), parseInt64(params[0])) // pct
 718				return nil
 719			},
 720		},
 721
 722		// Staker - Token list management
 723		{
 724			pkgPath:    STAKER_PATH,
 725			function:   "AddToken",
 726			paramCount: 1,
 727			paramValidators: []paramValidator{
 728				stringValidator, // tokenPath
 729			},
 730			handlerFunc: func(_ int, rlm realm, params []string) error {
 731				// Add token to allowed token list for external incentives
 732				sr.AddToken(cross(rlm), params[0]) // tokenPath
 733				return nil
 734			},
 735		},
 736		{
 737			pkgPath:    STAKER_PATH,
 738			function:   "RemoveToken",
 739			paramCount: 1,
 740			paramValidators: []paramValidator{
 741				stringValidator, // tokenPath
 742			},
 743			handlerFunc: func(_ int, rlm realm, params []string) error {
 744				// Remove token from allowed token list
 745				sr.RemoveToken(cross(rlm), params[0]) // tokenPath
 746				return nil
 747			},
 748		},
 749		{
 750			pkgPath:    STAKER_PATH,
 751			function:   "SetDeniedRewardToken",
 752			paramCount: 2,
 753			paramValidators: []paramValidator{
 754				stringValidator, // tokenPath
 755				boolValidator,   // denied
 756			},
 757			handlerFunc: func(_ int, rlm realm, params []string) error {
 758				// Deny or re-allow a token as an external incentive reward token
 759				sr.SetDeniedRewardToken(cross(rlm), params[0], parseBool(params[1]))
 760				return nil
 761			},
 762		},
 763
 764		// Staker - External incentive recovery
 765		{
 766			pkgPath:    STAKER_PATH,
 767			function:   "CancelExternalIncentive",
 768			paramCount: 2,
 769			paramValidators: []paramValidator{
 770				stringValidator, // targetPoolPath
 771				stringValidator, // incentiveId
 772			},
 773			handlerFunc: func(_ int, rlm realm, params []string) error {
 774				// Cancel a not-yet-started external incentive; the reward tokens
 775				// and the GNS deposit are refunded to the incentive creator
 776				sr.CancelExternalIncentive(
 777					cross(rlm),
 778					params[0], // targetPoolPath
 779					params[1], // incentiveId
 780				)
 781				return nil
 782			},
 783		},
 784
 785		// Launchpad - Project management
 786		{
 787			pkgPath:    LAUNCHPAD_PATH,
 788			function:   "CreateProject",
 789			paramCount: 10,
 790			paramValidators: []paramValidator{
 791				stringValidator,            // name
 792				stringValidator,            // tokenPath
 793				addressValidator,           // recipient
 794				numberValidator(kindInt64), // depositAmount
 795				stringValidator,            // conditionTokens
 796				stringValidator,            // conditionAmounts
 797				numberValidator(kindInt64), // tier30Ratio
 798				numberValidator(kindInt64), // tier90Ratio
 799				numberValidator(kindInt64), // tier180Ratio
 800				numberValidator(kindInt64), // startTime
 801			},
 802			handlerFunc: func(_ int, rlm realm, params []string) error {
 803				// Create a new launchpad project
 804				lp.CreateProject(
 805					cross(rlm),
 806					params[0],          // name
 807					params[1],          // tokenPath
 808					address(params[2]), // recipient
 809					parseNumber(params[3], kindInt64).(int64), // depositAmount
 810					params[4], // conditionTokens
 811					params[5], // conditionAmounts
 812					parseNumber(params[6], kindInt64).(int64), // tier30Ratio
 813					parseNumber(params[7], kindInt64).(int64), // tier90Ratio
 814					parseNumber(params[8], kindInt64).(int64), // tier180Ratio
 815					parseNumber(params[9], kindInt64).(int64), // startTime
 816				)
 817				return nil
 818			},
 819		},
 820		// Upgrade handlers for various domains
 821		{
 822			pkgPath:    POOL_PATH,
 823			function:   "UpgradeImpl",
 824			paramCount: 1,
 825			paramValidators: []paramValidator{
 826				stringValidator, // packagePath
 827			},
 828			handlerFunc: func(_ int, rlm realm, params []string) error {
 829				// Upgrade pool implementation
 830				pl.UpgradeImpl(cross(rlm), params[0]) // packagePath
 831				return nil
 832			},
 833		},
 834		{
 835			pkgPath:    POSITION_PATH,
 836			function:   "UpgradeImpl",
 837			paramCount: 1,
 838			paramValidators: []paramValidator{
 839				stringValidator, // packagePath
 840			},
 841			handlerFunc: func(_ int, rlm realm, params []string) error {
 842				// Upgrade position implementation
 843				pos.UpgradeImpl(cross(rlm), params[0]) // packagePath
 844				return nil
 845			},
 846		},
 847		{
 848			pkgPath:    STAKER_PATH,
 849			function:   "UpgradeImpl",
 850			paramCount: 1,
 851			paramValidators: []paramValidator{
 852				stringValidator, // packagePath
 853			},
 854			handlerFunc: func(_ int, rlm realm, params []string) error {
 855				// Upgrade staker implementation
 856				sr.UpgradeImpl(cross(rlm), params[0]) // packagePath
 857				return nil
 858			},
 859		},
 860		{
 861			pkgPath:    LAUNCHPAD_PATH,
 862			function:   "UpgradeImpl",
 863			paramCount: 1,
 864			paramValidators: []paramValidator{
 865				stringValidator, // packagePath
 866			},
 867			handlerFunc: func(_ int, rlm realm, params []string) error {
 868				// Upgrade launchpad implementation
 869				lp.UpgradeImpl(cross(rlm), params[0]) // packagePath
 870				return nil
 871			},
 872		},
 873		{
 874			pkgPath:    GOV_GOVERNANCE_PATH,
 875			function:   "UpgradeImpl",
 876			paramCount: 1,
 877			paramValidators: []paramValidator{
 878				stringValidator, // targetPackagePath
 879			},
 880			handlerFunc: func(_ int, rlm realm, params []string) error {
 881				// Upgrade governance implementation
 882				governance.UpgradeImpl(cross(rlm), params[0]) // packagePath
 883				return nil
 884			},
 885		},
 886		{
 887			pkgPath:    GOV_STAKER_PATH,
 888			function:   "UpgradeImpl",
 889			paramCount: 1,
 890			paramValidators: []paramValidator{
 891				stringValidator, // packagePath
 892			},
 893			handlerFunc: func(_ int, rlm realm, params []string) error {
 894				// Upgrade gov staker implementation
 895				gs.UpgradeImpl(cross(rlm), params[0]) // packagePath
 896				return nil
 897			},
 898		},
 899		{
 900			pkgPath:    ROUTER_PATH,
 901			function:   "UpgradeImpl",
 902			paramCount: 1,
 903			paramValidators: []paramValidator{
 904				stringValidator, // packagePath
 905			},
 906			handlerFunc: func(_ int, rlm realm, params []string) error {
 907				// Upgrade router implementation
 908				rr.UpgradeImpl(cross(rlm), params[0]) // packagePath
 909				return nil
 910			},
 911		},
 912		{
 913			pkgPath:    PROTOCOL_FEE_PATH,
 914			function:   "UpgradeImpl",
 915			paramCount: 1,
 916			paramValidators: []paramValidator{
 917				stringValidator, // packagePath
 918			},
 919			handlerFunc: func(_ int, rlm realm, params []string) error {
 920				// Upgrade protocol fee implementation
 921				pf.UpgradeImpl(cross(rlm), params[0]) // packagePath
 922				return nil
 923			},
 924		},
 925		{
 926			pkgPath:    COMMON_PATH,
 927			function:   "UpgradeImpl",
 928			paramCount: 1,
 929			paramValidators: []paramValidator{
 930				stringValidator, // packagePath
 931			},
 932			handlerFunc: func(_ int, rlm realm, params []string) error {
 933				// Upgrade common (token resolution) implementation
 934				cm.UpgradeImpl(cross(rlm), params[0]) // packagePath
 935				return nil
 936			},
 937		},
 938	}
 939
 940	// Register all configured handlers in the registry
 941	registerHandlers(registry, handlers)
 942
 943	return registry
 944}
 945
 946// registerHandlers batch registers all configured handlers into the registry.
 947// This helper function processes the handler configuration array and adds each handler to the registry.
 948//
 949// Parameters:
 950//   - registry: the parameter registry to add handlers to
 951//   - handlerOptions: slice of handler configurations to register
 952func registerHandlers(registry *ParameterRegistry, handlerOptions []*ParameterHandlerOptions) {
 953	for _, handlerOption := range handlerOptions {
 954		registry.Register(*handlerOption)
 955	}
 956}
 957
 958// runValidator executes a validator function and converts panics to errors.
 959func runValidator(fn func()) (err error) {
 960	defer func() {
 961		if r := recover(); r != nil {
 962			if e, ok := r.(error); ok {
 963				err = e
 964				return
 965			}
 966			err = ufmt.Errorf("%v", r)
 967		}
 968	}()
 969
 970	fn()
 971	return nil
 972}
 973
 974// Basic reusable validators for proposal-time type checking.
 975var (
 976	stringValidator = func(s string) error {
 977		return nil
 978	}
 979	boolValidator = func(s string) error {
 980		return runValidator(func() {
 981			parseBool(s)
 982		})
 983	}
 984	uint64Validator = func(s string) error {
 985		return runValidator(func() {
 986			parseUint64(s)
 987		})
 988	}
 989	addressValidator = func(s string) error {
 990		return runValidator(func() {
 991			addr := address(s)
 992			if !addr.IsValid() {
 993				panic(ufmt.Sprintf("invalid address: %s", addr))
 994			}
 995		})
 996	}
 997)
 998
 999func numberValidator(kind numberKind) paramValidator {
1000	return func(s string) error {
1001		return runValidator(func() {
1002			parseNumber(s, kind)
1003		})
1004	}
1005}
1006
1007func uint8RangeValidator(name string) paramValidator {
1008	return func(s string) error {
1009		return runValidator(func() {
1010			value := parseInt64(s)
1011			if value < 0 || value > 255 {
1012				panic(ufmt.Sprintf("%s out of range: %d", name, value))
1013			}
1014		})
1015	}
1016}
1017
1018func roleNameValidator(s string) error {
1019	return runValidator(func() {
1020		roleName := strings.TrimSpace(s)
1021		if roleName == "" {
1022			panic(ufmt.Sprintf("role name is empty: %q", s))
1023		}
1024	})
1025}
1026
1027func updatableRoleNameValidator(s string) error {
1028	return runValidator(func() {
1029		roleName := strings.TrimSpace(s)
1030		if err := roleNameValidator(roleName); err != nil {
1031			panic(err)
1032		}
1033
1034		if roleName == prbac.ROLE_ADMIN.String() {
1035			panic(ufmt.Sprintf("admin role cannot be updated: %s", roleName))
1036		}
1037	})
1038}
1039
1040func removableRoleNameValidator(s string) error {
1041	return runValidator(func() {
1042		roleName := strings.TrimSpace(s)
1043		if err := roleNameValidator(roleName); err != nil {
1044			panic(err)
1045		}
1046
1047		if prbac.IsSystemRole(roleName) {
1048			panic(ufmt.Sprintf("system role cannot be removed: %s", roleName))
1049		}
1050	})
1051}
1052
1053func nonNegativeInt64Validator(name string) paramValidator {
1054	return func(s string) error {
1055		return runValidator(func() {
1056			value := parseInt64(s)
1057			if value < 0 {
1058				panic(ufmt.Sprintf("%s must be non-negative: %d", name, value))
1059			}
1060		})
1061	}
1062}
1063
1064func positiveInt64Validator(name string) paramValidator {
1065	return func(s string) error {
1066		return runValidator(func() {
1067			value := parseInt64(s)
1068			if value <= 0 {
1069				panic(ufmt.Sprintf("%s must be positive: %d", name, value))
1070			}
1071		})
1072	}
1073}
1074
1075func int64RangeValidator(name string, min, max int64) paramValidator {
1076	return func(s string) error {
1077		return runValidator(func() {
1078			value := parseInt64(s)
1079			if value < min || value > max {
1080				panic(ufmt.Sprintf("%s must be in range %d to %d: %d", name, min, max, value))
1081			}
1082		})
1083	}
1084}
1085
1086func uint64RangeValidator(name string, max uint64) paramValidator {
1087	return func(s string) error {
1088		return runValidator(func() {
1089			value := parseUint64(s)
1090			if value > max {
1091				panic(ufmt.Sprintf("%s must be in range 0 to %d: %d", name, max, value))
1092			}
1093		})
1094	}
1095}
1096
1097func haltLevelValidator(s string) error {
1098	return runValidator(func() {
1099		if !halt.HaltLevel(s).IsValid() {
1100			panic(ufmt.Sprintf("invalid halt level: %s", s))
1101		}
1102	})
1103}
1104
1105func haltOperationTypeValidator(s string) error {
1106	return runValidator(func() {
1107		if !halt.OpType(s).IsValid() {
1108			panic(ufmt.Sprintf("invalid operation type: %s", s))
1109		}
1110	})
1111}
1112
1113func tokenMinimumRewardAmountValidator(s string) error {
1114	return runValidator(func() {
1115		parts := strings.SplitN(s, ":", 2)
1116		if len(parts) != 2 || parts[0] == "" {
1117			panic("token minimum reward amount must be tokenPath:amount")
1118		}
1119		amount := parseInt64(parts[1])
1120		if amount < 0 {
1121			panic(ufmt.Sprintf("minimum reward amount must be non-negative: %d", amount))
1122		}
1123	})
1124}
1125
1126func distributionPctCompositeValidator(params []string) error {
1127	return runValidator(func() {
1128		sum := int64(0)
1129		for i, param := range params {
1130			pct := parseInt64(param)
1131			if pct < 0 || pct > 10000 {
1132				panic(ufmt.Sprintf("percentage %d must be in range 0 to 10000: %d", i+1, pct))
1133			}
1134			sum += pct
1135		}
1136		if sum != 10000 {
1137			panic(ufmt.Sprintf("sum of percentages must be 10000, got %d", sum))
1138		}
1139	})
1140}
1141
1142func splitExecutionsRaw(executions string) []string {
1143	if executions == "" {
1144		return []string{}
1145	}
1146
1147	return strings.Split(executions, messageSeparator)
1148}
1149
1150func parseExecutionMessage(msg string) (pkgPath string, function string, params []string, partCount int) {
1151	parts := strings.Split(msg, parameterSeparator)
1152	partCount = len(parts)
1153	if partCount != 3 {
1154		return "", "", nil, partCount
1155	}
1156
1157	pkgPath = parts[0]
1158	function = parts[1]
1159	if parts[2] == "" {
1160		return pkgPath, function, []string{}, partCount
1161	}
1162
1163	return pkgPath, function, strings.Split(parts[2], ","), partCount
1164}
1165
1166// validateExecutions validates that all executions in a parameter change proposal
1167// correspond to registered handlers in the global parameter registry.
1168// This function performs comprehensive validation including:
1169// - Basic format validation (count, structure)
1170// - Handler existence verification in the registry
1171// - Parameter count validation against handler expectations
1172//
1173// Parameters:
1174//   - numToExecute: number of parameter changes to execute
1175//   - msgs: pre-split slice of execution messages, where each message
1176//     is formatted as <pkgPath>*EXE*<function>*EXE*<params>
1177//
1178// Returns:
1179//   - error: validation error if any execution is invalid
1180func validateExecutions(numToExecute int64, msgs []string) error {
1181	// Validate execution count is positive
1182	if numToExecute <= 0 {
1183		return makeErrorWithDetails(
1184			errInvalidInput,
1185			"numToExecute is less than or equal to 0",
1186		)
1187	}
1188
1189	// Check if executions are empty
1190	if len(msgs) == 0 {
1191		return makeErrorWithDetails(
1192			errInvalidInput,
1193			"executions is empty",
1194		)
1195	}
1196
1197	// Validate execution count doesn't exceed maximum
1198	if numToExecute > maxNumberOfExecution {
1199		return makeErrorWithDetails(
1200			errInvalidInput,
1201			ufmt.Sprintf("numToExecute is greater than %d", maxNumberOfExecution),
1202		)
1203	}
1204
1205	msgCount := len(msgs)
1206	// Validate execution count matches message count
1207	if msgCount != int(numToExecute) {
1208		return makeErrorWithDetails(
1209			errInvalidInput,
1210			ufmt.Sprintf("executions count (%d) does not match numToExecute (%d)", len(msgs), numToExecute),
1211		)
1212	}
1213
1214	// Parse and validate each execution message
1215	for i, execution := range msgs {
1216		pkgPath, function, params, partCount := parseExecutionMessage(execution)
1217		if partCount != 3 {
1218			// Provide more helpful error message based on what's wrong
1219			detail := ufmt.Sprintf("execution[%d]: expected 3 parts (pkgPath, function, params), got %d", i, partCount)
1220			return makeErrorWithDetails(
1221				errInvalidMessageFormat,
1222				detail,
1223			)
1224		}
1225
1226		// Validate package path and function are not empty
1227		if pkgPath == "" {
1228			return makeErrorWithDetails(
1229				errInvalidInput,
1230				ufmt.Sprintf("execution[%d]: package path is empty", i),
1231			)
1232		}
1233
1234		if function == "" {
1235			return makeErrorWithDetails(
1236				errInvalidInput,
1237				ufmt.Sprintf("execution[%d]: function name is empty", i),
1238			)
1239		}
1240
1241		// Check if handler exists in registry
1242		key := makeHandlerKey(pkgPath, function)
1243		handler, err := globalParameterRegistry.Handler(key)
1244		if err != nil {
1245			return makeErrorWithDetails(
1246				errInvalidExecution,
1247				ufmt.Sprintf("execution[%d]: %s (key: %s)", i, err.Error(), key),
1248			)
1249		}
1250
1251		// Get expected parameter count from handler
1252		handlerOpts, ok := handler.(*ParameterHandlerOptions)
1253		if !ok {
1254			return makeErrorWithDetails(
1255				errInvalidExecution,
1256				ufmt.Sprintf("execution[%d]: failed to get handler options", i),
1257			)
1258		}
1259
1260		// Validate parameter types/format ahead of proposal creation
1261		if err := handlerOpts.ValidateParams(params); err != nil {
1262			return makeErrorWithDetails(
1263				errInvalidInput,
1264				ufmt.Sprintf("execution[%d]: %v", i, err),
1265			)
1266		}
1267	}
1268
1269	return nil
1270}