github.com/cosmos/cosmos-sdk@v0.50.10/x/genutil/genaccounts.go (about)

     1  package genutil
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"cosmossdk.io/core/address"
     9  
    10  	"github.com/cosmos/cosmos-sdk/codec"
    11  	sdk "github.com/cosmos/cosmos-sdk/types"
    12  	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
    13  	authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
    14  	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
    15  	genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
    16  )
    17  
    18  // AddGenesisAccount adds a genesis account to the genesis state.
    19  // Where `cdc` is client codec, `genesisFileUrl` is the path/url of current genesis file,
    20  // `accAddr` is the address to be added to the genesis state, `amountStr` is the list of initial coins
    21  // to be added for the account, `appendAcct` updates the account if already exists.
    22  // `vestingStart, vestingEnd and vestingAmtStr` respectively are the schedule start time, end time (unix epoch)
    23  // `moduleNameā€œ is the module name for which the account is being created
    24  // and coins to be appended to the account already in the genesis.json file.
    25  func AddGenesisAccount(
    26  	cdc codec.Codec,
    27  	accAddr sdk.AccAddress,
    28  	appendAcct bool,
    29  	genesisFileURL, amountStr, vestingAmtStr string,
    30  	vestingStart, vestingEnd int64,
    31  	moduleName string,
    32  ) error {
    33  	coins, err := sdk.ParseCoinsNormalized(amountStr)
    34  	if err != nil {
    35  		return fmt.Errorf("failed to parse coins: %w", err)
    36  	}
    37  
    38  	vestingAmt, err := sdk.ParseCoinsNormalized(vestingAmtStr)
    39  	if err != nil {
    40  		return fmt.Errorf("failed to parse vesting amount: %w", err)
    41  	}
    42  
    43  	// create concrete account type based on input parameters
    44  	var genAccount authtypes.GenesisAccount
    45  
    46  	balances := banktypes.Balance{Address: accAddr.String(), Coins: coins.Sort()}
    47  	baseAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0)
    48  
    49  	if !vestingAmt.IsZero() {
    50  		baseVestingAccount, err := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd)
    51  		if err != nil {
    52  			return fmt.Errorf("failed to create base vesting account: %w", err)
    53  		}
    54  
    55  		if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) ||
    56  			baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) {
    57  			return errors.New("vesting amount cannot be greater than total amount")
    58  		}
    59  
    60  		switch {
    61  		case vestingStart != 0 && vestingEnd != 0:
    62  			genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart)
    63  
    64  		case vestingEnd != 0:
    65  			genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount)
    66  
    67  		default:
    68  			return errors.New("invalid vesting parameters; must supply start and end time or end time")
    69  		}
    70  	} else if moduleName != "" {
    71  		genAccount = authtypes.NewEmptyModuleAccount(moduleName, authtypes.Burner, authtypes.Minter)
    72  	} else {
    73  		genAccount = baseAccount
    74  	}
    75  
    76  	if err := genAccount.Validate(); err != nil {
    77  		return fmt.Errorf("failed to validate new genesis account: %w", err)
    78  	}
    79  
    80  	appState, appGenesis, err := genutiltypes.GenesisStateFromGenFile(genesisFileURL)
    81  	if err != nil {
    82  		return fmt.Errorf("failed to unmarshal genesis state: %w", err)
    83  	}
    84  
    85  	authGenState := authtypes.GetGenesisStateFromAppState(cdc, appState)
    86  
    87  	accs, err := authtypes.UnpackAccounts(authGenState.Accounts)
    88  	if err != nil {
    89  		return fmt.Errorf("failed to get accounts from any: %w", err)
    90  	}
    91  
    92  	bankGenState := banktypes.GetGenesisStateFromAppState(cdc, appState)
    93  	if accs.Contains(accAddr) {
    94  		if !appendAcct {
    95  			return fmt.Errorf(" Account %s already exists\nUse `append` flag to append account at existing address", accAddr)
    96  		}
    97  
    98  		genesisB := banktypes.GetGenesisStateFromAppState(cdc, appState)
    99  		for idx, acc := range genesisB.Balances {
   100  			if acc.Address != accAddr.String() {
   101  				continue
   102  			}
   103  
   104  			updatedCoins := acc.Coins.Add(coins...)
   105  			bankGenState.Balances[idx] = banktypes.Balance{Address: accAddr.String(), Coins: updatedCoins.Sort()}
   106  			break
   107  		}
   108  	} else {
   109  		// Add the new account to the set of genesis accounts and sanitize the accounts afterwards.
   110  		accs = append(accs, genAccount)
   111  		accs = authtypes.SanitizeGenesisAccounts(accs)
   112  
   113  		genAccs, err := authtypes.PackAccounts(accs)
   114  		if err != nil {
   115  			return fmt.Errorf("failed to convert accounts into any's: %w", err)
   116  		}
   117  		authGenState.Accounts = genAccs
   118  
   119  		authGenStateBz, err := cdc.MarshalJSON(&authGenState)
   120  		if err != nil {
   121  			return fmt.Errorf("failed to marshal auth genesis state: %w", err)
   122  		}
   123  		appState[authtypes.ModuleName] = authGenStateBz
   124  
   125  		bankGenState.Balances = append(bankGenState.Balances, balances)
   126  	}
   127  
   128  	bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances)
   129  
   130  	bankGenState.Supply = bankGenState.Supply.Add(balances.Coins...)
   131  
   132  	bankGenStateBz, err := cdc.MarshalJSON(bankGenState)
   133  	if err != nil {
   134  		return fmt.Errorf("failed to marshal bank genesis state: %w", err)
   135  	}
   136  	appState[banktypes.ModuleName] = bankGenStateBz
   137  
   138  	appStateJSON, err := json.Marshal(appState)
   139  	if err != nil {
   140  		return fmt.Errorf("failed to marshal application genesis state: %w", err)
   141  	}
   142  
   143  	appGenesis.AppState = appStateJSON
   144  	return ExportGenesisFile(appGenesis, genesisFileURL)
   145  }
   146  
   147  type GenesisAccount struct {
   148  	// Base
   149  	Address string    `json:"address"`
   150  	Coins   sdk.Coins `json:"coins"`
   151  
   152  	// Vesting
   153  	VestingAmt   sdk.Coins `json:"vesting_amt,omitempty"`
   154  	VestingStart int64     `json:"vesting_start,omitempty"`
   155  	VestingEnd   int64     `json:"vesting_end,omitempty"`
   156  
   157  	// Module
   158  	ModuleName string `json:"module_name,omitempty"`
   159  }
   160  
   161  // AddGenesisAccounts adds genesis accounts to the genesis state.
   162  // Where `cdc` is the client codec, `accounts` are the genesis accounts to add,
   163  // `appendAcct` updates the account if already exists, and `genesisFileURL` is the path/url of the current genesis file.
   164  func AddGenesisAccounts(
   165  	cdc codec.Codec,
   166  	ac address.Codec,
   167  	accounts []GenesisAccount,
   168  	appendAcct bool,
   169  	genesisFileURL string,
   170  ) error {
   171  	appState, appGenesis, err := genutiltypes.GenesisStateFromGenFile(genesisFileURL)
   172  	if err != nil {
   173  		return fmt.Errorf("failed to unmarshal genesis state: %w", err)
   174  	}
   175  
   176  	authGenState := authtypes.GetGenesisStateFromAppState(cdc, appState)
   177  	bankGenState := banktypes.GetGenesisStateFromAppState(cdc, appState)
   178  
   179  	accs, err := authtypes.UnpackAccounts(authGenState.Accounts)
   180  	if err != nil {
   181  		return fmt.Errorf("failed to get accounts from any: %w", err)
   182  	}
   183  
   184  	newSupplyCoinsCache := sdk.NewCoins()
   185  	balanceCache := make(map[string]banktypes.Balance)
   186  	for _, acc := range accs {
   187  		for _, balance := range bankGenState.GetBalances() {
   188  			if balance.Address == acc.GetAddress().String() {
   189  				balanceCache[acc.GetAddress().String()] = balance
   190  			}
   191  		}
   192  	}
   193  
   194  	for _, acc := range accounts {
   195  		addr := acc.Address
   196  		coins := acc.Coins
   197  
   198  		accAddr, err := ac.StringToBytes(addr)
   199  		if err != nil {
   200  			return fmt.Errorf("failed to parse account address %s: %w", addr, err)
   201  		}
   202  
   203  		// create concrete account type based on input parameters
   204  		var genAccount authtypes.GenesisAccount
   205  
   206  		balances := banktypes.Balance{Address: addr, Coins: coins.Sort()}
   207  		baseAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0)
   208  
   209  		vestingAmt := acc.VestingAmt
   210  		if !vestingAmt.IsZero() {
   211  			vestingStart := acc.VestingStart
   212  			vestingEnd := acc.VestingEnd
   213  
   214  			baseVestingAccount, err := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd)
   215  			if err != nil {
   216  				return fmt.Errorf("failed to create base vesting account: %w", err)
   217  			}
   218  
   219  			if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) ||
   220  				baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) {
   221  				return errors.New("vesting amount cannot be greater than total amount")
   222  			}
   223  
   224  			switch {
   225  			case vestingStart != 0 && vestingEnd != 0:
   226  				genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart)
   227  
   228  			case vestingEnd != 0:
   229  				genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount)
   230  
   231  			default:
   232  				return errors.New("invalid vesting parameters; must supply start and end time or end time")
   233  			}
   234  		} else if acc.ModuleName != "" {
   235  			genAccount = authtypes.NewEmptyModuleAccount(acc.ModuleName, authtypes.Burner, authtypes.Minter)
   236  		} else {
   237  			genAccount = baseAccount
   238  		}
   239  
   240  		if err := genAccount.Validate(); err != nil {
   241  			return fmt.Errorf("failed to validate new genesis account: %w", err)
   242  		}
   243  
   244  		if _, ok := balanceCache[addr]; ok {
   245  			if !appendAcct {
   246  				return fmt.Errorf(" Account %s already exists\nUse `append` flag to append account at existing address", accAddr)
   247  			}
   248  
   249  			for idx, acc := range bankGenState.Balances {
   250  				if acc.Address != addr {
   251  					continue
   252  				}
   253  
   254  				updatedCoins := acc.Coins.Add(coins...)
   255  				bankGenState.Balances[idx] = banktypes.Balance{Address: addr, Coins: updatedCoins.Sort()}
   256  				break
   257  			}
   258  		} else {
   259  			accs = append(accs, genAccount)
   260  			bankGenState.Balances = append(bankGenState.Balances, balances)
   261  		}
   262  
   263  		newSupplyCoinsCache = newSupplyCoinsCache.Add(coins...)
   264  	}
   265  
   266  	accs = authtypes.SanitizeGenesisAccounts(accs)
   267  
   268  	authGenState.Accounts, err = authtypes.PackAccounts(accs)
   269  	if err != nil {
   270  		return fmt.Errorf("failed to convert accounts into any's: %w", err)
   271  	}
   272  
   273  	appState[authtypes.ModuleName], err = cdc.MarshalJSON(&authGenState)
   274  	if err != nil {
   275  		return fmt.Errorf("failed to marshal auth genesis state: %w", err)
   276  	}
   277  
   278  	bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances)
   279  	bankGenState.Supply = bankGenState.Supply.Add(newSupplyCoinsCache...)
   280  
   281  	appState[banktypes.ModuleName], err = cdc.MarshalJSON(bankGenState)
   282  	if err != nil {
   283  		return fmt.Errorf("failed to marshal bank genesis state: %w", err)
   284  	}
   285  
   286  	appStateJSON, err := json.Marshal(appState)
   287  	if err != nil {
   288  		return fmt.Errorf("failed to marshal application genesis state: %w", err)
   289  	}
   290  
   291  	appGenesis.AppState = appStateJSON
   292  	return ExportGenesisFile(appGenesis, genesisFileURL)
   293  }