Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
2,585 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright Tharsis Labs Ltd.(Evmos) | ||
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) | ||
|
||
package v20 | ||
|
||
const ( | ||
// UpgradeName is the shared upgrade plan name for mainnet | ||
UpgradeName = "v20.0.0" | ||
// UpgradeInfo defines the binaries that will be used for the upgrade | ||
UpgradeInfo = `'{"binaries":{"darwin/amd64":"https://github.com/evmos/evmos/releases/download/v20.0.0/evmos_20.0.0_Darwin_arm64.tar.gz","darwin/x86_64":"https://github.com/evmos/evmos/releases/download/v20.0.0/evmos_20.0.0_Darwin_x86_64.tar.gz","linux/arm64":"https://github.com/evmos/evmos/releases/download/v20.0.0/evmos_20.0.0_Linux_arm64.tar.gz","linux/amd64":"https://github.com/evmos/evmos/releases/download/v20.0.0/evmos_20.0.0_Linux_amd64.tar.gz","windows/x86_64":"https://github.com/evmos/evmos/releases/download/v20.0.0/evmos_20.0.0_Windows_x86_64.zip"}}'` | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
// Copyright Tharsis Labs Ltd.(Evmos) | ||
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) | ||
|
||
package v20 | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"math/big" | ||
"os" | ||
"slices" | ||
"time" | ||
|
||
"github.com/cometbft/cometbft/libs/log" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" | ||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
fixes "github.com/evmos/evmos/v18/app/upgrades/v20/fixes" | ||
erc20keeper "github.com/evmos/evmos/v18/x/erc20/keeper" | ||
erc20types "github.com/evmos/evmos/v18/x/erc20/types" | ||
evmkeeper "github.com/evmos/evmos/v18/x/evm/keeper" | ||
) | ||
|
||
// storeKey contains the slot in which the balance is stored in the evm. | ||
var storeKey []byte = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} | ||
var storeKeyWevmos []byte = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3} | ||
|
||
type parseTokenPairs = []common.Address | ||
|
||
// BalanceResult contains the data needed to perform the balance conversion | ||
type BalanceResult struct { | ||
address sdk.AccAddress | ||
balanceBytes []byte | ||
id int | ||
} | ||
|
||
// ExportResult holds the data | ||
// to be exported to a json file | ||
type ExportResult struct { | ||
Address string | ||
Balance string | ||
Erc20 string | ||
} | ||
|
||
// executeConversion receives the whole set of adress with erc20 balances | ||
// it sends the equivalent coin from the escrow address into the holder address | ||
// it doesnt need to burn the erc20 balance, because the evm storage will be deleted later | ||
func executeConversion( | ||
ctx sdk.Context, | ||
results []BalanceResult, | ||
bankKeeper bankkeeper.Keeper, | ||
erc20Keeper erc20keeper.Keeper, | ||
wrappedEvmosAddr common.Address, | ||
nativeTokenPairs []erc20types.TokenPair, | ||
fromSnapshot bool, | ||
) error { | ||
wevmosAccount := sdk.AccAddress(wrappedEvmosAddr.Bytes()) | ||
// Go trough every address with an erc20 balance | ||
for _, result := range results { | ||
if fromSnapshot && erc20Keeper.HasSTRv2Address(ctx, result.address) { | ||
continue | ||
} | ||
|
||
tokenPair := nativeTokenPairs[result.id] | ||
|
||
// The conversion is different for Evmos/WEVMOS and IBC-coins | ||
// Convert balance Bytes into Big Int | ||
balance := new(big.Int).SetBytes(result.balanceBytes) | ||
if balance.Sign() <= 0 { | ||
continue | ||
} | ||
// Create the coin | ||
coins := sdk.Coins{sdk.Coin{Denom: tokenPair.Denom, Amount: sdk.NewIntFromBigInt(balance)}} | ||
|
||
// If its Wevmos | ||
if tokenPair.Erc20Address == wrappedEvmosAddr.Hex() { | ||
// Withdraw the balance from the contract | ||
// Unescrow coins and send to holder account | ||
err := bankKeeper.SendCoinsFromAccountToModule(ctx, wevmosAccount, erc20types.ModuleName, coins) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
err := bankKeeper.SendCoinsFromModuleToAccount(ctx, erc20types.ModuleName, result.address, coins) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// ConvertERC20Coins generates the list of address-erc20 balance that need to be migrated | ||
// It takes some steps to generate this list (parallel) | ||
// - Divide all the accounts into smaller batches | ||
// - Have parallel workers query the db for erc20 balance | ||
// - Consolidate all the balances on the same array | ||
// | ||
// Once the list is generated, it does three things (serialized) | ||
// - Save the result into a file | ||
// - Actually move all the balances from erc20 to bank | ||
// - Check that all the balances has been moved. | ||
func ConvertERC20Coins( | ||
ctx sdk.Context, | ||
logger log.Logger, | ||
accountKeeper authkeeper.AccountKeeper, | ||
bankKeeper bankkeeper.Keeper, | ||
erc20Keeper erc20keeper.Keeper, | ||
evmKeeper evmkeeper.Keeper, | ||
wrappedAddr common.Address, | ||
nativeTokenPairs []erc20types.TokenPair, | ||
) error { | ||
timeBegin := time.Now() // control the time of the execution | ||
|
||
// simplify the list of erc20 token pairs to handle less data | ||
tokenPairs := make(parseTokenPairs, len(nativeTokenPairs)) | ||
for i := range nativeTokenPairs { | ||
tokenPairs[i] = nativeTokenPairs[i].GetERC20Contract() | ||
} | ||
|
||
modifiedBalancesAccounts := erc20Keeper.GetAllSTRV2Address(ctx) | ||
var modifiedBalancesWallets = make([]string, len(modifiedBalancesAccounts)) | ||
for i, addr := range modifiedBalancesAccounts { | ||
modifiedBalancesWallets[i] = addr.String() | ||
} | ||
|
||
// Need to filter only uniques accounts | ||
missingWallets := fixes.GetMissingWalletsFromAuthModule(ctx, accountKeeper) | ||
combinedWallets := append(missingWallets, modifiedBalancesWallets...) | ||
slices.Sort(combinedWallets) | ||
combinedWallets = slices.Compact(combinedWallets) | ||
|
||
// Convert to accounts | ||
allAccounts := make([]sdk.AccAddress, len(combinedWallets)) | ||
for i, wallet := range combinedWallets { | ||
allAccounts[i] = sdk.MustAccAddressFromBech32(wallet) | ||
} | ||
|
||
wevmosId := 0 | ||
tokenPairStores := make([]sdk.KVStore, len(tokenPairs)) | ||
for i, pair := range tokenPairs { | ||
tokenPairStores[i] = evmKeeper.GetStoreDummy(ctx, pair) | ||
if wrappedAddr.Hex() == pair.Hex() { | ||
wevmosId = i | ||
} | ||
} | ||
|
||
resultsCol := []BalanceResult{} | ||
for _, account := range allAccounts { | ||
concatBytes := append(common.LeftPadBytes(account.Bytes(), 32), storeKey...) | ||
key := crypto.Keccak256Hash(concatBytes) | ||
|
||
concatBytesWevmos := append(common.LeftPadBytes(account.Bytes(), 32), storeKeyWevmos...) | ||
keyWevmos := crypto.Keccak256Hash(concatBytesWevmos) | ||
var value []byte | ||
for tokenId, store := range tokenPairStores { | ||
if tokenId == wevmosId { | ||
value = store.Get(keyWevmos.Bytes()) | ||
if len(value) == 0 { | ||
continue | ||
} | ||
} else { | ||
value = store.Get(key.Bytes()) | ||
if len(value) == 0 { | ||
continue | ||
} | ||
} | ||
|
||
resultsCol = append(resultsCol, BalanceResult{address: account, balanceBytes: value, id: tokenId}) | ||
} | ||
} | ||
|
||
err := executeConversion(ctx, resultsCol, bankKeeper, erc20Keeper, wrappedAddr, nativeTokenPairs, false) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
userHomeDir, err := os.UserHomeDir() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// Store in file | ||
// file, _ := json.MarshalIndent(jsonExport, "", " ") | ||
file, err := os.ReadFile(fmt.Sprint(userHomeDir, "/results-full.json")) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
var readResults []ExportResult | ||
err = json.Unmarshal(file, &readResults) | ||
fmt.Println("Finalized results: ", len(readResults)) | ||
if err != nil { | ||
panic("Failed to unmarshal") | ||
} | ||
|
||
erc20Map := make(map[string]int) | ||
for i, erc20 := range nativeTokenPairs { | ||
erc20Map[erc20.Erc20Address] = i | ||
} | ||
|
||
// Generate the json to store in the file | ||
var finalizedResults []BalanceResult = make([]BalanceResult, len(readResults)) | ||
for i, result := range readResults { | ||
b, _ := new(big.Int).SetString(result.Balance, 10) | ||
finalizedResults[i] = BalanceResult{ | ||
address: sdk.MustAccAddressFromBech32(result.Address), | ||
balanceBytes: b.Bytes(), | ||
id: erc20Map[result.Erc20], | ||
} | ||
} | ||
// execute the actual conversion. | ||
err = executeConversion(ctx, finalizedResults, bankKeeper, erc20Keeper, wrappedAddr, nativeTokenPairs, true) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// NOTE: if there are tokens left in the ERC-20 module account | ||
// we return an error because this implies that the migration of native | ||
// coins to ERC-20 tokens was not fully completed. | ||
erc20ModuleAccountAddress := authtypes.NewModuleAddress(erc20types.ModuleName) | ||
balances := bankKeeper.GetAllBalances(ctx, erc20ModuleAccountAddress) | ||
if !balances.IsZero() { | ||
return fmt.Errorf("there are still tokens in the erc-20 module account: %s", balances.String()) | ||
} | ||
duration := time.Since(timeBegin) | ||
logger.Info(fmt.Sprintf("STR v2 migration took %s\n", duration)) | ||
return nil | ||
} | ||
|
||
// getNativeTokenPairs returns the token pairs that are registered for native Cosmos coins. | ||
func getNativeTokenPairs( | ||
ctx sdk.Context, | ||
erc20Keeper erc20keeper.Keeper, | ||
) []erc20types.TokenPair { | ||
var nativeTokenPairs []erc20types.TokenPair | ||
|
||
erc20Keeper.IterateTokenPairs(ctx, func(tokenPair erc20types.TokenPair) bool { | ||
// NOTE: here we check if the token pair contains an IBC coin. For now, we only want to convert those. | ||
if !tokenPair.IsNativeCoin() { | ||
return false | ||
} | ||
|
||
nativeTokenPairs = append(nativeTokenPairs, tokenPair) | ||
return false | ||
}) | ||
|
||
return nativeTokenPairs | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright Tharsis Labs Ltd.(Evmos) | ||
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) | ||
|
||
package v20 | ||
|
||
import ( | ||
errorsmod "cosmossdk.io/errors" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/ethereum/go-ethereum/common" | ||
erc20keeper "github.com/evmos/evmos/v18/x/erc20/keeper" | ||
"github.com/evmos/evmos/v18/x/erc20/types" | ||
evmkeeper "github.com/evmos/evmos/v18/x/evm/keeper" | ||
) | ||
|
||
// RegisterERC20Extensions registers the ERC20 precompiles with the EVM. | ||
func RegisterERC20Extensions( | ||
ctx sdk.Context, | ||
erc20Keeper erc20keeper.Keeper, | ||
evmKeeper *evmkeeper.Keeper, | ||
) error { | ||
precompiles := make([]common.Address, 0) | ||
evmParams := evmKeeper.GetParams(ctx) | ||
|
||
var err error | ||
erc20Keeper.IterateTokenPairs(ctx, func(tokenPair types.TokenPair) bool { | ||
address := tokenPair.GetERC20Contract() | ||
|
||
// skip registration if token is native or if it has already been registered | ||
// NOTE: this should handle failure during the selfdestruct | ||
if !tokenPair.IsNativeCoin() || | ||
evmKeeper.IsAvailableDynamicPrecompile(&evmParams, address) { | ||
return false | ||
} | ||
|
||
// try to self-destruct the old ERC20 contract | ||
// NOTE(@fedekunze): From now on, the contract address will map to a precompile instead | ||
// of the ERC20MinterBurner contract. We try to force a self-destruction to remove the unnecessary | ||
// code and storage from the state machine. | ||
// In any case, the precompiles are handled in the EVM | ||
// before the regular contracts so not removing them doesn't create any issues in the implementation. | ||
err = evmKeeper.DeleteAccount(ctx, address) | ||
if err != nil { | ||
err = errorsmod.Wrapf(err, "failed to selfdestruct account %s", address) | ||
return true | ||
} | ||
|
||
precompiles = append(precompiles, address) | ||
return false | ||
}) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
// add the ERC20s to the EVM active and available precompiles | ||
return evmKeeper.EnableDynamicPrecompiles(ctx, precompiles...) | ||
} |
Oops, something went wrong.