github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/wasm/client/cli/genesis_msg.go (about)

     1  package cli
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"crypto/sha256"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/flags"
    12  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys"
    13  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    14  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
    15  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth"
    16  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/genutil"
    17  	genutiltypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/genutil/types"
    18  	tmtypes "github.com/fibonacci-chain/fbc/libs/tendermint/types"
    19  	"github.com/fibonacci-chain/fbc/x/wasm/client/utils"
    20  	"github.com/fibonacci-chain/fbc/x/wasm/keeper"
    21  	"github.com/fibonacci-chain/fbc/x/wasm/types"
    22  	"github.com/spf13/cobra"
    23  )
    24  
    25  // GenesisReader reads genesis data. Extension point for custom genesis state readers.
    26  type GenesisReader interface {
    27  	ReadWasmGenesis(cmd *cobra.Command) (*GenesisData, error)
    28  }
    29  
    30  // GenesisMutator extension point to modify the wasm module genesis state.
    31  // This gives flexibility to customize the data structure in the genesis file a bit.
    32  type GenesisMutator interface {
    33  	// AlterWasmModuleState loads the genesis from the default or set home dir,
    34  	// unmarshalls the wasm module section into the object representation
    35  	// calls the callback function to modify it
    36  	// and marshals the modified state back into the genesis file
    37  	AlterWasmModuleState(cmd *cobra.Command, callback func(state *types.GenesisState, appState map[string]json.RawMessage) error) error
    38  }
    39  
    40  // GenesisStoreCodeCmd cli command to add a `MsgStoreCode` to the wasm section of the genesis
    41  // that is executed on block 0.
    42  func GenesisStoreCodeCmd(defaultNodeHome string, genesisMutator GenesisMutator) *cobra.Command {
    43  	cmd := &cobra.Command{
    44  		Use:   "store [wasm file] --run-as [owner_address_or_key_name]\",",
    45  		Short: "Upload a wasm binary",
    46  		Args:  cobra.ExactArgs(1),
    47  		RunE: func(cmd *cobra.Command, args []string) error {
    48  			senderAddr, err := getActorAddress(cmd)
    49  			if err != nil {
    50  				return err
    51  			}
    52  
    53  			msg, err := parseStoreCodeArgs(args[0], senderAddr, cmd.Flags())
    54  			if err != nil {
    55  				return err
    56  			}
    57  			if err = msg.ValidateBasic(); err != nil {
    58  				return err
    59  			}
    60  
    61  			return genesisMutator.AlterWasmModuleState(cmd, func(state *types.GenesisState, _ map[string]json.RawMessage) error {
    62  				state.GenMsgs = append(state.GenMsgs, types.GenesisState_GenMsgs{
    63  					Sum: &types.GenesisState_GenMsgs_StoreCode{StoreCode: &msg},
    64  				})
    65  				return nil
    66  			})
    67  		},
    68  	}
    69  	cmd.Flags().String(flagRunAs, "", "The address that is stored as code creator")
    70  	cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional")
    71  	cmd.Flags().String(flagInstantiateNobody, "", "Nobody except the governance process can instantiate a contract from the code, optional")
    72  	cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional")
    73  
    74  	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
    75  	cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
    76  	flags.AddQueryFlagsToCmd(cmd)
    77  	return cmd
    78  }
    79  
    80  // GenesisInstantiateContractCmd cli command to add a `MsgInstantiateContract` to the wasm section of the genesis
    81  // that is executed on block 0.
    82  func GenesisInstantiateContractCmd(defaultNodeHome string, genesisMutator GenesisMutator) *cobra.Command {
    83  	cmd := &cobra.Command{
    84  		Use:   "instantiate-contract [code_id_int64] [json_encoded_init_args] --label [text] --run-as [address] --admin [address,optional] --amount [coins,optional]",
    85  		Short: "Instantiate a wasm contract",
    86  		Args:  cobra.ExactArgs(2),
    87  		RunE: func(cmd *cobra.Command, args []string) error {
    88  			senderAddr, err := getActorAddress(cmd)
    89  			if err != nil {
    90  				return err
    91  			}
    92  
    93  			msg, err := parseInstantiateArgs(args[0], args[1], senderAddr, cmd.Flags())
    94  			if err != nil {
    95  				return err
    96  			}
    97  			if err = msg.ValidateBasic(); err != nil {
    98  				return err
    99  			}
   100  
   101  			return genesisMutator.AlterWasmModuleState(cmd, func(state *types.GenesisState, appState map[string]json.RawMessage) error {
   102  				// simple sanity check that sender has some balance although it may be consumed by appState previous message already
   103  				switch ok, err := hasAccountBalance(cmd, appState, senderAddr, sdk.CoinAdaptersToCoins(msg.Funds)); {
   104  				case err != nil:
   105  					return err
   106  				case !ok:
   107  					return errors.New("sender has not enough account balance")
   108  				}
   109  
   110  				//  does code id exists?
   111  				codeInfos, err := GetAllCodes(state)
   112  				if err != nil {
   113  					return err
   114  				}
   115  				var codeInfo *CodeMeta
   116  				for i := range codeInfos {
   117  					if codeInfos[i].CodeID == msg.CodeID {
   118  						codeInfo = &codeInfos[i]
   119  						break
   120  					}
   121  				}
   122  				if codeInfo == nil {
   123  					return fmt.Errorf("unknown code id: %d", msg.CodeID)
   124  				}
   125  				// permissions correct?
   126  				if !codeInfo.Info.InstantiateConfig.Allowed(senderAddr) {
   127  					return fmt.Errorf("permissions were not granted for %s", senderAddr)
   128  				}
   129  				state.GenMsgs = append(state.GenMsgs, types.GenesisState_GenMsgs{
   130  					Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &msg},
   131  				})
   132  				return nil
   133  			})
   134  		},
   135  	}
   136  	cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation")
   137  	cmd.Flags().String(flagLabel, "", "A human-readable name for this contract in lists")
   138  	cmd.Flags().String(flagAdmin, "", "Address of an admin")
   139  	cmd.Flags().Bool(flagNoAdmin, false, "You must set this explicitly if you don't want an admin")
   140  	cmd.Flags().String(flagRunAs, "", "The address that pays the init funds. It is the creator of the contract.")
   141  
   142  	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
   143  	cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
   144  	flags.AddQueryFlagsToCmd(cmd)
   145  	return cmd
   146  }
   147  
   148  // GenesisExecuteContractCmd cli command to add a `MsgExecuteContract` to the wasm section of the genesis
   149  // that is executed on block 0.
   150  func GenesisExecuteContractCmd(defaultNodeHome string, genesisMutator GenesisMutator) *cobra.Command {
   151  	cmd := &cobra.Command{
   152  		Use:   "execute [contract_addr_bech32] [json_encoded_send_args] --run-as [address] --amount [coins,optional]",
   153  		Short: "Execute a command on a wasm contract",
   154  		Args:  cobra.ExactArgs(2),
   155  		RunE: func(cmd *cobra.Command, args []string) error {
   156  			senderAddr, err := getActorAddress(cmd)
   157  			if err != nil {
   158  				return err
   159  			}
   160  
   161  			msg, err := parseExecuteArgs(args[0], args[1], senderAddr, cmd.Flags())
   162  			if err != nil {
   163  				return err
   164  			}
   165  			if err = msg.ValidateBasic(); err != nil {
   166  				return err
   167  			}
   168  
   169  			return genesisMutator.AlterWasmModuleState(cmd, func(state *types.GenesisState, appState map[string]json.RawMessage) error {
   170  				// simple sanity check that sender has some balance although it may be consumed by appState previous message already
   171  				switch ok, err := hasAccountBalance(cmd, appState, senderAddr, sdk.CoinAdaptersToCoins(msg.Funds)); {
   172  				case err != nil:
   173  					return err
   174  				case !ok:
   175  					return errors.New("sender has not enough account balance")
   176  				}
   177  
   178  				// - does contract address exists?
   179  				if !hasContract(state, msg.Contract) {
   180  					return fmt.Errorf("unknown contract: %state", msg.Contract)
   181  				}
   182  				state.GenMsgs = append(state.GenMsgs, types.GenesisState_GenMsgs{
   183  					Sum: &types.GenesisState_GenMsgs_ExecuteContract{ExecuteContract: &msg},
   184  				})
   185  				return nil
   186  			})
   187  		},
   188  	}
   189  	cmd.Flags().String(flagAmount, "", "Coins to send to the contract along with command")
   190  	cmd.Flags().String(flagRunAs, "", "The address that pays the funds.")
   191  
   192  	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
   193  	cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
   194  	flags.AddQueryFlagsToCmd(cmd)
   195  	return cmd
   196  }
   197  
   198  // GenesisListCodesCmd cli command to list all codes stored in the genesis wasm.code section
   199  // as well as from messages that are queued in the wasm.genMsgs section.
   200  func GenesisListCodesCmd(defaultNodeHome string, genReader GenesisReader) *cobra.Command {
   201  	cmd := &cobra.Command{
   202  		Use:   "list-codes ",
   203  		Short: "Lists all codes from genesis code dump and queued messages",
   204  		Args:  cobra.ExactArgs(0),
   205  		RunE: func(cmd *cobra.Command, args []string) error {
   206  			g, err := genReader.ReadWasmGenesis(cmd)
   207  			if err != nil {
   208  				return err
   209  			}
   210  			all, err := GetAllCodes(g.WasmModuleState)
   211  			if err != nil {
   212  				return err
   213  			}
   214  			return printJSONOutput(cmd, all)
   215  		},
   216  	}
   217  	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
   218  	flags.AddQueryFlagsToCmd(cmd)
   219  	return cmd
   220  }
   221  
   222  // GenesisListContractsCmd cli command to list all contracts stored in the genesis wasm.contract section
   223  // as well as from messages that are queued in the wasm.genMsgs section.
   224  func GenesisListContractsCmd(defaultNodeHome string, genReader GenesisReader) *cobra.Command {
   225  	cmd := &cobra.Command{
   226  		Use:   "list-contracts ",
   227  		Short: "Lists all contracts from genesis contract dump and queued messages",
   228  		Args:  cobra.ExactArgs(0),
   229  		RunE: func(cmd *cobra.Command, args []string) error {
   230  			g, err := genReader.ReadWasmGenesis(cmd)
   231  			if err != nil {
   232  				return err
   233  			}
   234  			state := g.WasmModuleState
   235  			all := GetAllContracts(state)
   236  			return printJSONOutput(cmd, all)
   237  		},
   238  	}
   239  	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
   240  	flags.AddQueryFlagsToCmd(cmd)
   241  	return cmd
   242  }
   243  
   244  // clientCtx marshaller works only with proto or bytes so we marshal the output ourself
   245  func printJSONOutput(cmd *cobra.Command, obj interface{}) error {
   246  	clientCtx := utils.GetClientContextFromCmd(cmd)
   247  	bz, err := json.MarshalIndent(obj, "", " ")
   248  	if err != nil {
   249  		return err
   250  	}
   251  	return clientCtx.PrintOutput(string(bz))
   252  }
   253  
   254  type CodeMeta struct {
   255  	CodeID uint64         `json:"code_id"`
   256  	Info   types.CodeInfo `json:"info"`
   257  }
   258  
   259  func GetAllCodes(state *types.GenesisState) ([]CodeMeta, error) {
   260  	all := make([]CodeMeta, len(state.Codes))
   261  	for i, c := range state.Codes {
   262  		all[i] = CodeMeta{
   263  			CodeID: c.CodeID,
   264  			Info:   c.CodeInfo,
   265  		}
   266  	}
   267  	// add inflight
   268  	seq := codeSeqValue(state)
   269  	for _, m := range state.GenMsgs {
   270  		if msg := m.GetStoreCode(); msg != nil {
   271  			var accessConfig types.AccessConfig
   272  			if msg.InstantiatePermission != nil {
   273  				accessConfig = *msg.InstantiatePermission
   274  			} else {
   275  				// default
   276  				creator, err := sdk.AccAddressFromBech32(msg.Sender)
   277  				if err != nil {
   278  					return nil, fmt.Errorf("sender: %s", err)
   279  				}
   280  				accessConfig = state.Params.InstantiateDefaultPermission.With(creator)
   281  			}
   282  			hash := sha256.Sum256(msg.WASMByteCode)
   283  			all = append(all, CodeMeta{
   284  				CodeID: seq,
   285  				Info: types.CodeInfo{
   286  					CodeHash:          hash[:],
   287  					Creator:           msg.Sender,
   288  					InstantiateConfig: accessConfig,
   289  				},
   290  			})
   291  			seq++
   292  		}
   293  	}
   294  	return all, nil
   295  }
   296  
   297  type ContractMeta struct {
   298  	ContractAddress string             `json:"contract_address"`
   299  	Info            types.ContractInfo `json:"info"`
   300  }
   301  
   302  func GetAllContracts(state *types.GenesisState) []ContractMeta {
   303  	all := make([]ContractMeta, len(state.Contracts))
   304  	for i, c := range state.Contracts {
   305  		all[i] = ContractMeta{
   306  			ContractAddress: c.ContractAddress,
   307  			Info:            c.ContractInfo,
   308  		}
   309  	}
   310  	// add inflight
   311  	seq := contractSeqValue(state)
   312  	for _, m := range state.GenMsgs {
   313  		if msg := m.GetInstantiateContract(); msg != nil {
   314  			all = append(all, ContractMeta{
   315  				ContractAddress: keeper.BuildContractAddress(msg.CodeID, seq).String(),
   316  				Info: types.ContractInfo{
   317  					CodeID:  msg.CodeID,
   318  					Creator: msg.Sender,
   319  					Admin:   msg.Admin,
   320  					Label:   msg.Label,
   321  				},
   322  			})
   323  			seq++
   324  		}
   325  	}
   326  	return all
   327  }
   328  
   329  func hasAccountBalance(cmd *cobra.Command, appState map[string]json.RawMessage, sender sdk.AccAddress, coins sdk.Coins) (bool, error) {
   330  	// no coins needed, no account needed
   331  	if coins.IsZero() {
   332  		return true, nil
   333  	}
   334  	clientCtx, err := utils.GetClientQueryContext(cmd)
   335  	if err != nil {
   336  		return false, err
   337  	}
   338  	cdc := clientCtx.Codec
   339  	var genBalIterator auth.GenesisAccountIterator
   340  	err = genutil.ValidateAccountInGenesis(appState, genBalIterator, sender, coins, cdc)
   341  	if err != nil {
   342  		return false, err
   343  	}
   344  	return true, nil
   345  }
   346  
   347  func hasContract(state *types.GenesisState, contractAddr string) bool {
   348  	for _, c := range state.Contracts {
   349  		if c.ContractAddress == contractAddr {
   350  			return true
   351  		}
   352  	}
   353  	seq := contractSeqValue(state)
   354  	for _, m := range state.GenMsgs {
   355  		if msg := m.GetInstantiateContract(); msg != nil {
   356  			if keeper.BuildContractAddress(msg.CodeID, seq).String() == contractAddr {
   357  				return true
   358  			}
   359  			seq++
   360  		}
   361  	}
   362  	return false
   363  }
   364  
   365  // GenesisData contains raw and unmarshalled data from the genesis file
   366  type GenesisData struct {
   367  	GenesisFile     string
   368  	GenDoc          *tmtypes.GenesisDoc
   369  	AppState        map[string]json.RawMessage
   370  	WasmModuleState *types.GenesisState
   371  }
   372  
   373  func NewGenesisData(genesisFile string, genDoc *tmtypes.GenesisDoc, appState map[string]json.RawMessage, wasmModuleState *types.GenesisState) *GenesisData {
   374  	return &GenesisData{GenesisFile: genesisFile, GenDoc: genDoc, AppState: appState, WasmModuleState: wasmModuleState}
   375  }
   376  
   377  type DefaultGenesisReader struct{}
   378  
   379  func (d DefaultGenesisReader) ReadWasmGenesis(cmd *cobra.Command) (*GenesisData, error) {
   380  	clientCtx := utils.GetClientContextFromCmd(cmd)
   381  	serverCtx := utils.GetServerContextFromCmd(cmd)
   382  	config := serverCtx.Config
   383  	config.SetRoot(clientCtx.HomeDir)
   384  
   385  	genFile := config.GenesisFile()
   386  	appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(clientCtx.Codec, genFile)
   387  	if err != nil {
   388  		return nil, fmt.Errorf("failed to unmarshal genesis state: %w", err)
   389  	}
   390  	var wasmGenesisState types.GenesisState
   391  	if appState[types.ModuleName] != nil {
   392  		clientCtx := utils.GetClientContextFromCmd(cmd)
   393  		clientCtx.CodecProy.GetProtocMarshal().MustUnmarshalJSON(appState[types.ModuleName], &wasmGenesisState)
   394  	}
   395  
   396  	return NewGenesisData(
   397  		genFile,
   398  		genDoc,
   399  		appState,
   400  		&wasmGenesisState,
   401  	), nil
   402  }
   403  
   404  var (
   405  	_ GenesisReader  = DefaultGenesisIO{}
   406  	_ GenesisMutator = DefaultGenesisIO{}
   407  )
   408  
   409  // DefaultGenesisIO implements both interfaces to read and modify the genesis state for this module.
   410  // This implementation uses the default data structure that is used by the module.go genesis import/ export.
   411  type DefaultGenesisIO struct {
   412  	DefaultGenesisReader
   413  }
   414  
   415  // NewDefaultGenesisIO constructor to create a new instance
   416  func NewDefaultGenesisIO() *DefaultGenesisIO {
   417  	return &DefaultGenesisIO{DefaultGenesisReader: DefaultGenesisReader{}}
   418  }
   419  
   420  // AlterWasmModuleState loads the genesis from the default or set home dir,
   421  // unmarshalls the wasm module section into the object representation
   422  // calls the callback function to modify it
   423  // and marshals the modified state back into the genesis file
   424  func (x DefaultGenesisIO) AlterWasmModuleState(cmd *cobra.Command, callback func(state *types.GenesisState, appState map[string]json.RawMessage) error) error {
   425  	g, err := x.ReadWasmGenesis(cmd)
   426  	if err != nil {
   427  		return err
   428  	}
   429  	if err := callback(g.WasmModuleState, g.AppState); err != nil {
   430  		return err
   431  	}
   432  	// and store update
   433  	if err := g.WasmModuleState.ValidateBasic(); err != nil {
   434  		return err
   435  	}
   436  	clientCtx := utils.GetClientContextFromCmd(cmd)
   437  	wasmGenStateBz, err := clientCtx.CodecProy.GetProtocMarshal().MarshalJSON(g.WasmModuleState)
   438  	if err != nil {
   439  		return sdkerrors.Wrap(err, "marshal wasm genesis state")
   440  	}
   441  
   442  	g.AppState[types.ModuleName] = wasmGenStateBz
   443  	appStateJSON, err := json.Marshal(g.AppState)
   444  	if err != nil {
   445  		return sdkerrors.Wrap(err, "marshal application genesis state")
   446  	}
   447  
   448  	g.GenDoc.AppState = appStateJSON
   449  	return genutil.ExportGenesisFile(g.GenDoc, g.GenesisFile)
   450  }
   451  
   452  // contractSeqValue reads the contract sequence from the genesis or
   453  // returns default start value used in the keeper
   454  func contractSeqValue(state *types.GenesisState) uint64 {
   455  	var seq uint64 = 1
   456  	for _, s := range state.Sequences {
   457  		if bytes.Equal(s.IDKey, types.KeyLastInstanceID) {
   458  			seq = s.Value
   459  			break
   460  		}
   461  	}
   462  	return seq
   463  }
   464  
   465  // codeSeqValue reads the code sequence from the genesis or
   466  // returns default start value used in the keeper
   467  func codeSeqValue(state *types.GenesisState) uint64 {
   468  	var seq uint64 = 1
   469  	for _, s := range state.Sequences {
   470  		if bytes.Equal(s.IDKey, types.KeyLastCodeID) {
   471  			seq = s.Value
   472  			break
   473  		}
   474  	}
   475  	return seq
   476  }
   477  
   478  // getActorAddress returns the account address for the `--run-as` flag.
   479  // The flag value can either be an address already or a key name where the
   480  // address is read from the keyring instead.
   481  func getActorAddress(cmd *cobra.Command) (sdk.AccAddress, error) {
   482  	actorArg, err := cmd.Flags().GetString(flagRunAs)
   483  	if err != nil {
   484  		return nil, fmt.Errorf("run-as: %s", err.Error())
   485  	}
   486  	if len(actorArg) == 0 {
   487  		return nil, errors.New("run-as address is required")
   488  	}
   489  
   490  	actorAddr, err := sdk.AccAddressFromBech32(actorArg)
   491  	if err == nil {
   492  		return actorAddr, nil
   493  	}
   494  	inBuf := bufio.NewReader(cmd.InOrStdin())
   495  	keyringBackend, err := cmd.Flags().GetString(flags.FlagKeyringBackend)
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  
   500  	homeDir := utils.GetClientContextFromCmd(cmd).HomeDir
   501  	// attempt to lookup address from Keybase if no address was provided
   502  	kb, err := keys.NewKeyring(sdk.KeyringServiceName(), keyringBackend, homeDir, inBuf)
   503  	if err != nil {
   504  		return nil, err
   505  	}
   506  
   507  	info, err := kb.Get(actorArg)
   508  	if err != nil {
   509  		return nil, fmt.Errorf("failed to get address from Keybase: %w", err)
   510  	}
   511  	return info.GetAddress(), nil
   512  }