github.com/Finschia/finschia-sdk@v0.49.1/x/params/types/subspace.go (about)

     1  package types
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  
     7  	"github.com/Finschia/finschia-sdk/codec"
     8  	"github.com/Finschia/finschia-sdk/store/prefix"
     9  	storetypes "github.com/Finschia/finschia-sdk/store/types"
    10  	sdk "github.com/Finschia/finschia-sdk/types"
    11  )
    12  
    13  const (
    14  	// StoreKey is the string store key for the param store
    15  	StoreKey = "params"
    16  
    17  	// TStoreKey is the string store key for the param transient store
    18  	TStoreKey = "transient_params"
    19  )
    20  
    21  // Individual parameter store for each keeper
    22  type Subspace struct {
    23  	cdc         codec.BinaryCodec
    24  	legacyAmino *codec.LegacyAmino
    25  	key         sdk.StoreKey // []byte -> []byte, stores parameter
    26  	name        []byte
    27  	table       KeyTable
    28  }
    29  
    30  // NewSubspace constructs a store with namestore
    31  func NewSubspace(cdc codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey storetypes.StoreKey, name string) Subspace {
    32  	return Subspace{
    33  		cdc:         cdc,
    34  		legacyAmino: legacyAmino,
    35  		key:         key,
    36  		name:        []byte(name),
    37  		table:       NewKeyTable(),
    38  	}
    39  }
    40  
    41  // HasKeyTable returns if the Subspace has a KeyTable registered.
    42  func (s Subspace) HasKeyTable() bool {
    43  	return len(s.table.m) > 0
    44  }
    45  
    46  // WithKeyTable initializes KeyTable and returns modified Subspace
    47  func (s Subspace) WithKeyTable(table KeyTable) Subspace {
    48  	if table.m == nil {
    49  		panic("SetKeyTable() called with nil KeyTable")
    50  	}
    51  	if len(s.table.m) != 0 {
    52  		panic("SetKeyTable() called on already initialized Subspace")
    53  	}
    54  
    55  	for k, v := range table.m {
    56  		s.table.m[k] = v
    57  	}
    58  
    59  	// Allocate additional capacity for Subspace.name
    60  	// So we don't have to allocate extra space each time appending to the key
    61  	name := s.name
    62  	s.name = make([]byte, len(name), len(name)+table.maxKeyLength())
    63  	copy(s.name, name)
    64  
    65  	return s
    66  }
    67  
    68  // Returns a KVStore identical with ctx.KVStore(s.key).Prefix()
    69  func (s Subspace) kvStore(ctx sdk.Context) sdk.KVStore {
    70  	// this function can be called concurrently so we should not call append on s.name directly
    71  	name := make([]byte, len(s.name))
    72  	copy(name, s.name)
    73  	return prefix.NewStore(ctx.MultiStore().GetKVStore(s.key), append(name, '/'))
    74  }
    75  
    76  // Validate attempts to validate a parameter value by its key. If the key is not
    77  // registered or if the validation of the value fails, an error is returned.
    78  func (s Subspace) Validate(ctx sdk.Context, key []byte, value interface{}) error {
    79  	attr, ok := s.table.m[string(key)]
    80  	if !ok {
    81  		return fmt.Errorf("parameter %s not registered", string(key))
    82  	}
    83  
    84  	if err := attr.vfn(value); err != nil {
    85  		return fmt.Errorf("invalid parameter value: %w", err)
    86  	}
    87  
    88  	return nil
    89  }
    90  
    91  // Get queries for a parameter by key from the Subspace's KVStore and sets the
    92  // value to the provided pointer. If the value does not exist, it will panic.
    93  func (s Subspace) Get(ctx sdk.Context, key []byte, ptr interface{}) {
    94  	s.checkType(key, ptr)
    95  
    96  	store := s.kvStore(ctx)
    97  	bz := store.Get(key)
    98  
    99  	if err := s.legacyAmino.UnmarshalJSON(bz, ptr); err != nil {
   100  		panic(err)
   101  	}
   102  }
   103  
   104  // GetIfExists queries for a parameter by key from the Subspace's KVStore and
   105  // sets the value to the provided pointer. If the value does not exist, it will
   106  // perform a no-op.
   107  func (s Subspace) GetIfExists(ctx sdk.Context, key []byte, ptr interface{}) {
   108  	store := s.kvStore(ctx)
   109  	bz := store.Get(key)
   110  	if bz == nil {
   111  		return
   112  	}
   113  
   114  	s.checkType(key, ptr)
   115  
   116  	if err := s.legacyAmino.UnmarshalJSON(bz, ptr); err != nil {
   117  		panic(err)
   118  	}
   119  }
   120  
   121  // GetRaw queries for the raw values bytes for a parameter by key.
   122  func (s Subspace) GetRaw(ctx sdk.Context, key []byte) []byte {
   123  	store := s.kvStore(ctx)
   124  	return store.Get(key)
   125  }
   126  
   127  // Has returns if a parameter key exists or not in the Subspace's KVStore.
   128  func (s Subspace) Has(ctx sdk.Context, key []byte) bool {
   129  	store := s.kvStore(ctx)
   130  	return store.Has(key)
   131  }
   132  
   133  // checkType verifies that the provided key and value are comptable and registered.
   134  func (s Subspace) checkType(key []byte, value interface{}) {
   135  	attr, ok := s.table.m[string(key)]
   136  	if !ok {
   137  		panic(fmt.Sprintf("parameter %s not registered", string(key)))
   138  	}
   139  
   140  	ty := attr.ty
   141  	pty := reflect.TypeOf(value)
   142  	if pty.Kind() == reflect.Ptr {
   143  		pty = pty.Elem()
   144  	}
   145  
   146  	if pty != ty {
   147  		panic("type mismatch with registered table")
   148  	}
   149  }
   150  
   151  // Set stores a value for given a parameter key assuming the parameter type has
   152  // been registered. It will panic if the parameter type has not been registered
   153  // or if the value cannot be encoded. A change record is also set in the Subspace's
   154  // transient KVStore to mark the parameter as modified.
   155  func (s Subspace) Set(ctx sdk.Context, key []byte, value interface{}) {
   156  	s.checkType(key, value)
   157  	store := s.kvStore(ctx)
   158  
   159  	bz, err := s.legacyAmino.MarshalJSON(value)
   160  	if err != nil {
   161  		panic(err)
   162  	}
   163  
   164  	store.Set(key, bz)
   165  }
   166  
   167  // Update stores an updated raw value for a given parameter key assuming the
   168  // parameter type has been registered. It will panic if the parameter type has
   169  // not been registered or if the value cannot be encoded. An error is returned
   170  // if the raw value is not compatible with the registered type for the parameter
   171  // key or if the new value is invalid as determined by the registered type's
   172  // validation function.
   173  func (s Subspace) Update(ctx sdk.Context, key, value []byte) error {
   174  	attr, ok := s.table.m[string(key)]
   175  	if !ok {
   176  		panic(fmt.Sprintf("parameter %s not registered", string(key)))
   177  	}
   178  
   179  	ty := attr.ty
   180  	dest := reflect.New(ty).Interface()
   181  	s.GetIfExists(ctx, key, dest)
   182  
   183  	if err := s.legacyAmino.UnmarshalJSON(value, dest); err != nil {
   184  		return err
   185  	}
   186  
   187  	// destValue contains the dereferenced value of dest so validation function do
   188  	// not have to operate on pointers.
   189  	destValue := reflect.Indirect(reflect.ValueOf(dest)).Interface()
   190  	if err := s.Validate(ctx, key, destValue); err != nil {
   191  		return err
   192  	}
   193  
   194  	s.Set(ctx, key, dest)
   195  	return nil
   196  }
   197  
   198  // GetParamSet iterates through each ParamSetPair where for each pair, it will
   199  // retrieve the value and set it to the corresponding value pointer provided
   200  // in the ParamSetPair by calling Subspace#Get.
   201  func (s Subspace) GetParamSet(ctx sdk.Context, ps ParamSet) {
   202  	for _, pair := range ps.ParamSetPairs() {
   203  		s.Get(ctx, pair.Key, pair.Value)
   204  	}
   205  }
   206  
   207  // GetParamSetIfExists iterates through each ParamSetPair where for each pair, it will
   208  // retrieve the value and set it to the corresponding value pointer provided
   209  // in the ParamSetPair by calling Subspace#GetIfExists.
   210  func (s Subspace) GetParamSetIfExists(ctx sdk.Context, ps ParamSet) {
   211  	for _, pair := range ps.ParamSetPairs() {
   212  		s.GetIfExists(ctx, pair.Key, pair.Value)
   213  	}
   214  }
   215  
   216  // SetParamSet iterates through each ParamSetPair and sets the value with the
   217  // corresponding parameter key in the Subspace's KVStore.
   218  func (s Subspace) SetParamSet(ctx sdk.Context, ps ParamSet) {
   219  	for _, pair := range ps.ParamSetPairs() {
   220  		// pair.Field is a pointer to the field, so indirecting the ptr.
   221  		// go-amino automatically handles it but just for sure,
   222  		// since SetStruct is meant to be used in InitGenesis
   223  		// so this method will not be called frequently
   224  		v := reflect.Indirect(reflect.ValueOf(pair.Value)).Interface()
   225  
   226  		if err := pair.ValidatorFn(v); err != nil {
   227  			panic(fmt.Sprintf("value from ParamSetPair is invalid: %s", err))
   228  		}
   229  		s.Set(ctx, pair.Key, v)
   230  	}
   231  }
   232  
   233  // Name returns the name of the Subspace.
   234  func (s Subspace) Name() string {
   235  	return string(s.name)
   236  }
   237  
   238  // Wrapper of Subspace, provides immutable functions only
   239  type ReadOnlySubspace struct {
   240  	s Subspace
   241  }
   242  
   243  // Get delegates a read-only Get call to the Subspace.
   244  func (ros ReadOnlySubspace) Get(ctx sdk.Context, key []byte, ptr interface{}) {
   245  	ros.s.Get(ctx, key, ptr)
   246  }
   247  
   248  // GetRaw delegates a read-only GetRaw call to the Subspace.
   249  func (ros ReadOnlySubspace) GetRaw(ctx sdk.Context, key []byte) []byte {
   250  	return ros.s.GetRaw(ctx, key)
   251  }
   252  
   253  // Has delegates a read-only Has call to the Subspace.
   254  func (ros ReadOnlySubspace) Has(ctx sdk.Context, key []byte) bool {
   255  	return ros.s.Has(ctx, key)
   256  }
   257  
   258  // Name delegates a read-only Name call to the Subspace.
   259  func (ros ReadOnlySubspace) Name() string {
   260  	return ros.s.Name()
   261  }