Skip to content

Commit

Permalink
feat: add ASIGenesisUpgrade command
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathansumner committed Apr 11, 2024
1 parent 18ddc93 commit 75d7e2f
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 165 deletions.
262 changes: 262 additions & 0 deletions cmd/fetchd/cmd/ASIGenesisUpgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
package cmd

import (
"encoding/json"
"fmt"
"github.com/btcsuite/btcutil/bech32"
"github.com/cosmos/cosmos-sdk/codec"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/types"
"regexp"
"strings"
"sync"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
)

const (
flagNewDescription = "new-description"
Bech32Chars = "023456789acdefghjklmnpqrstuvwxyz"
AddrDataLength = 32
AddrChecksumLength = 6
AccAddressPrefix = ""
ValAddressPrefix = "valoper"
ConsAddressPrefix = "valcons"

NewBaseDenom = "asi"
NewDenom = "aasi"
NewAddrPrefix = "asi"
NewChainId = "asi-1"
NewDescription = "ASI Token"

OldBaseDenom = "fet"
OldDenom = "afet"
OldAddrPrefix = "fetch"
)

//" fetch char* [bech32 chars] limited by size " CHECKSUM?
//verify functions after!

// ASIGenesisUpgradeCmd returns replace-genesis-values cobra Command.
func ASIGenesisUpgradeCmd(defaultNodeHome string) *cobra.Command {
cmd := &cobra.Command{
Use: "asi-genesis-upgrade",
Short: "This command carries out a full upgrade of the genesis file to the new ASI chain parameters.",
Long: `The following command will upgrade the current genesis file to the new ASI chain parameters. The following changes will be made:
- Chain ID will be updated to "asi-1"
- The native coin denom will be updated to "asi"
- The address prefix will be updated to "asi"
- The old fetch addresses will be updated to the new asi addresses`,

Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
cdc := clientCtx.Codec

serverCtx := server.GetServerContextFromCmd(cmd)
config := serverCtx.Config

config.SetRoot(clientCtx.HomeDir)

genFile := config.GenesisFile()

appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile)
if err != nil {
return fmt.Errorf("failed to unmarshal genesis state: %w", err)
}

// replace chain-id
ASIGenesisUpgradeReplaceChainID(genDoc)

// set denom metadata in bank module
err = ASIGenesisUpgradeReplaceDenomMetadata(cdc, &appState)
if err != nil {
return fmt.Errorf("failed to replace denom metadata: %w", err)
}

appStateJSON, err := json.Marshal(appState)
if err != nil {
return fmt.Errorf("failed to marshal application genesis state: %w", err)
}

appStateStr := string(appStateJSON)

// replace denom across the genesis file
ASIGenesisUpgradeReplaceDenom(&appStateStr)

// replace addresses across the genesis file
ASIGenesisUpgradeReplaceAddresses(&appStateStr)

genDoc.AppState = []byte(appStateStr)
return genutil.ExportGenesisFile(genDoc, genFile)
},
}

cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
cmd.Flags().String(flagNewDescription, "", "The new description for the native coin in the genesis file")
flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func ASIGenesisUpgradeReplaceDenomMetadata(cdc codec.Codec, appState *map[string]json.RawMessage) error {
bankGenState := banktypes.GetGenesisStateFromAppState(cdc, *appState)

denomRegex := getRegex(OldDenom, NewDenom)
upperDenomRegex := getRegex(strings.ToUpper(OldBaseDenom), strings.ToUpper(NewBaseDenom))
exponentDenomRegex := getPartialRegexLeft(OldBaseDenom, NewBaseDenom)

for _, metadata := range bankGenState.DenomMetadata {
replaceString(&metadata.Base, []*regexPair{denomRegex})
if metadata.Name == OldBaseDenom {
metadata.Description = NewDescription
}
replaceString(&metadata.Display, []*regexPair{upperDenomRegex})
replaceString(&metadata.Name, []*regexPair{upperDenomRegex})
replaceString(&metadata.Symbol, []*regexPair{upperDenomRegex})
for _, unit := range metadata.DenomUnits {
replaceString(&unit.Denom, []*regexPair{upperDenomRegex})
replaceString(&unit.Denom, []*regexPair{exponentDenomRegex})
}

}

bankGenStateBytes, err := cdc.MarshalJSON(bankGenState)
if err != nil {
return fmt.Errorf("failed to marshal auth genesis state: %w", err)
}

(*appState)[banktypes.ModuleName] = bankGenStateBytes
return nil
}

func ASIGenesisUpgradeReplaceChainID(genesisData *types.GenesisDoc) {
genesisData.ChainID = NewChainId
}

func ASIGenesisUpgradeReplaceDenom(jsonString *string) {
for _, target := range []string{"denom", "bond_denom", "mint_denom", "base_denom", "base"} {
re := regexp.MustCompile(fmt.Sprintf(`("%s"\s*:\s*)"%s"`, target, OldDenom))
if re.MatchString(*jsonString) {
*jsonString = re.ReplaceAllString(*jsonString, fmt.Sprintf(`${1}"%s"`, NewDenom))
}
}
}

func ASIGenesisUpgradeReplaceAddresses(jsonString *string) {
replaceAddresses(AccAddressPrefix, jsonString)
replaceAddresses(ValAddressPrefix, jsonString)
replaceAddresses(ConsAddressPrefix, jsonString)
}

type AddressInfo struct {
Original string
Modified string
}

func replaceAddresses(addressTypePrefix string, jsonString *string) {
re := regexp.MustCompile(fmt.Sprintf(`"%s%s1([%s]{%d})"`, OldAddrPrefix, addressTypePrefix, Bech32Chars, AddrDataLength+AddrChecksumLength))
matches := re.FindAllString(*jsonString, -1)

addressChan := make(chan AddressInfo, len(matches))

var wg sync.WaitGroup
wg.Add(len(matches))

for _, match := range matches {
go func(match string) {
defer wg.Done()

matchedAddr := strings.ReplaceAll(match, `"`, "")
_, decodedAddrData, err := bech32.Decode(matchedAddr)
if err != nil {
panic(err)
}

newAddress, err := bech32.Encode(NewAddrPrefix+addressTypePrefix, decodedAddrData)
if err != nil {
panic(err)
}

err = cosmostypes.VerifyAddressFormat(decodedAddrData)
if err != nil {
panic(err)
}

switch addressTypePrefix {
case AccAddressPrefix:
_, err = cosmostypes.AccAddressFromBech32(newAddress)
case ValAddressPrefix:
_, err = cosmostypes.ValAddressFromBech32(newAddress)
case ConsAddressPrefix:
_, err = cosmostypes.ConsAddressFromBech32(newAddress)
default:
panic("invalid address type prefix")
}
if err != nil {
panic(err)
}

addressChan <- AddressInfo{Original: matchedAddr, Modified: newAddress}
}(match)
}

go func() {
wg.Wait()
close(addressChan)
}()

modifiedAddresses := make(map[string]string)
for addressInfo := range addressChan {
modifiedAddresses[addressInfo.Original] = addressInfo.Modified
}

//TODO: This is not feasible time-wise - we need to come up with a quicker method
for originalAddr, modifiedAddr := range modifiedAddresses {
*jsonString = strings.ReplaceAll(*jsonString, originalAddr, fmt.Sprintf(`"%s"`, modifiedAddr))
}
}

func ASIGenesisUpgradeWithdrawIBCChannelsBalances() {

}

func ASIGenesisUpgradeWithdrawReconciliationBalances() {

}

func getRegex(oldValue string, newValue string) *regexPair {
return &regexPair{
pattern: fmt.Sprintf(`^%s$`, oldValue),
replacement: fmt.Sprintf(`%s`, newValue),
}
}

func getPartialRegexLeft(oldValue string, newValue string) *regexPair {
return &regexPair{
pattern: fmt.Sprintf(`(.*?)%s"`, oldValue),
replacement: fmt.Sprintf(`${1}%s"`, newValue),
}
}

func replaceString(s *string, replacements []*regexPair) {
for _, pair := range replacements {
re := regexp.MustCompile(pair.pattern)
if re.MatchString(*s) {
*s = re.ReplaceAllString(*s, pair.replacement)
}
}
}

type regexPair struct {
pattern string
replacement string
}
Loading

0 comments on commit 75d7e2f

Please sign in to comment.