mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 09:57:27 +00:00 
			
		
		
		
	feat(evmutil): add ERC20KavaWrappedNativeCoinContract (#1591)
* feat(evmutil): add ERC20KavaWrappedNativeCoinContract * adds the contract ABI & bytecode for an Ownable erc20 with the following: * customizable decimals on deploy -> requires overriding decimals() view * mint() exposed for the contract owner which will be the evmutil module * burn() exposed for the contract owner which will be the evmutil module * sets up keeper to deploy above token based on details from an AllowedNativeCoinERC20Token * tests basic queries and permissions of deployed contract * update changelog * improve error messages & comments for erc20 deploy
This commit is contained in:
		
							parent
							
								
									6da31bd662
								
							
						
					
					
						commit
						278f7854dc
					
				@ -38,6 +38,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
- (evmutil) [#1590] Add allow list param of sdk native denoms that can be transferred to evm
 | 
			
		||||
- (evmutil) [#1591] Configure module to support deploying ERC20KavaWrappedNativeCoin contracts
 | 
			
		||||
 | 
			
		||||
## [v0.23.0]
 | 
			
		||||
 | 
			
		||||
@ -240,6 +241,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md).
 | 
			
		||||
- [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run
 | 
			
		||||
  large-scale simulations remotely using aws-batch
 | 
			
		||||
 | 
			
		||||
[#1591]: https://github.com/Kava-Labs/kava/pull/1591
 | 
			
		||||
[#1590]: https://github.com/Kava-Labs/kava/pull/1590
 | 
			
		||||
[#1568]: https://github.com/Kava-Labs/kava/pull/1568
 | 
			
		||||
[#1567]: https://github.com/Kava-Labs/kava/pull/1567
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
package keeper
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math/big"
 | 
			
		||||
 | 
			
		||||
@ -64,6 +65,51 @@ func (k Keeper) DeployTestMintableERC20Contract(
 | 
			
		||||
	return types.NewInternalEVMAddress(contractAddr), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeployKavaWrappedNativeCoinERC20Contract validates token details and then deploys an ERC20
 | 
			
		||||
// contract with the token metadata.
 | 
			
		||||
// This method does NOT check if a token for the provided SdkDenom has already been deployed.
 | 
			
		||||
func (k Keeper) DeployKavaWrappedNativeCoinERC20Contract(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	token types.AllowedNativeCoinERC20Token,
 | 
			
		||||
) (types.InternalEVMAddress, error) {
 | 
			
		||||
	if err := token.Validate(); err != nil {
 | 
			
		||||
		return types.InternalEVMAddress{}, errorsmod.Wrapf(err, "failed to deploy erc20 for sdk denom %s", token.SdkDenom)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packedAbi, err := types.ERC20KavaWrappedNativeCoinContract.ABI.Pack(
 | 
			
		||||
		"", // Empty string for contract constructor
 | 
			
		||||
		token.Name,
 | 
			
		||||
		token.Symbol,
 | 
			
		||||
		uint8(token.Decimals), // cast to uint8 is safe because of Validate()
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return types.InternalEVMAddress{}, errorsmod.Wrapf(err, "failed to pack token with details %+v", token)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data := make([]byte, len(types.ERC20KavaWrappedNativeCoinContract.Bin)+len(packedAbi))
 | 
			
		||||
	copy(
 | 
			
		||||
		data[:len(types.ERC20KavaWrappedNativeCoinContract.Bin)],
 | 
			
		||||
		types.ERC20KavaWrappedNativeCoinContract.Bin,
 | 
			
		||||
	)
 | 
			
		||||
	copy(
 | 
			
		||||
		data[len(types.ERC20KavaWrappedNativeCoinContract.Bin):],
 | 
			
		||||
		packedAbi,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	nonce, err := k.accountKeeper.GetSequence(ctx, types.ModuleEVMAddress.Bytes())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return types.InternalEVMAddress{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contractAddr := crypto.CreateAddress(types.ModuleEVMAddress, nonce)
 | 
			
		||||
	_, err = k.CallEVMWithData(ctx, types.ModuleEVMAddress, nil, data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return types.InternalEVMAddress{}, fmt.Errorf("failed to deploy ERC20 %s (nonce=%d, data=%s): %s", token.Name, nonce, hex.EncodeToString(data), err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return types.NewInternalEVMAddress(contractAddr), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MintERC20 mints the given amount of an ERC20 token to an address. This is
 | 
			
		||||
// unchecked and should only be called after permission and enabled ERC20 checks.
 | 
			
		||||
func (k Keeper) MintERC20(
 | 
			
		||||
 | 
			
		||||
@ -84,3 +84,64 @@ func (suite *ERC20TestSuite) TestERC20Mint() {
 | 
			
		||||
	suite.Require().True(ok, "balanceOf should respond with *big.Int")
 | 
			
		||||
	suite.Require().Equal(big.NewInt(1234), balance)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *ERC20TestSuite) TestDeployKavaWrappedNativeCoinERC20Contract() {
 | 
			
		||||
	suite.Run("fails to deploy invalid contract", func() {
 | 
			
		||||
		// empty other fields means this token is invalid.
 | 
			
		||||
		invalidToken := types.AllowedNativeCoinERC20Token{SdkDenom: "nope"}
 | 
			
		||||
		_, err := suite.Keeper.DeployKavaWrappedNativeCoinERC20Contract(suite.Ctx, invalidToken)
 | 
			
		||||
		suite.ErrorContains(err, "token's name cannot be empty")
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	suite.Run("deploys contract with expected metadata & permissions", func() {
 | 
			
		||||
		caller, privKey := suite.RandomAccount()
 | 
			
		||||
 | 
			
		||||
		token := types.NewAllowedNativeCoinERC20Token("hard", "EVM HARD", "HARD", 6)
 | 
			
		||||
		addr, err := suite.Keeper.DeployKavaWrappedNativeCoinERC20Contract(suite.Ctx, token)
 | 
			
		||||
		suite.NoError(err)
 | 
			
		||||
		suite.NotNil(addr)
 | 
			
		||||
 | 
			
		||||
		callContract := func(method string, args ...interface{}) ([]interface{}, error) {
 | 
			
		||||
			return suite.QueryContract(
 | 
			
		||||
				types.ERC20KavaWrappedNativeCoinContract.ABI,
 | 
			
		||||
				caller,
 | 
			
		||||
				privKey,
 | 
			
		||||
				addr,
 | 
			
		||||
				method,
 | 
			
		||||
				args...,
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// owner must be the evmutil module account
 | 
			
		||||
		data, err := callContract("owner")
 | 
			
		||||
		suite.NoError(err)
 | 
			
		||||
		suite.Len(data, 1)
 | 
			
		||||
		suite.Equal(types.ModuleEVMAddress, data[0].(common.Address))
 | 
			
		||||
 | 
			
		||||
		// get name
 | 
			
		||||
		data, err = callContract("name")
 | 
			
		||||
		suite.NoError(err)
 | 
			
		||||
		suite.Len(data, 1)
 | 
			
		||||
		suite.Equal(token.Name, data[0].(string))
 | 
			
		||||
 | 
			
		||||
		// get symbol
 | 
			
		||||
		data, err = callContract("symbol")
 | 
			
		||||
		suite.NoError(err)
 | 
			
		||||
		suite.Len(data, 1)
 | 
			
		||||
		suite.Equal(token.Symbol, data[0].(string))
 | 
			
		||||
 | 
			
		||||
		// get decimals
 | 
			
		||||
		data, err = callContract("decimals")
 | 
			
		||||
		suite.NoError(err)
 | 
			
		||||
		suite.Len(data, 1)
 | 
			
		||||
		suite.Equal(token.Decimals, uint32(data[0].(uint8)))
 | 
			
		||||
 | 
			
		||||
		// should not be able to call mint
 | 
			
		||||
		_, err = callContract("mint", caller, big.NewInt(1))
 | 
			
		||||
		suite.ErrorContains(err, "Ownable: caller is not the owner")
 | 
			
		||||
 | 
			
		||||
		// should not be able to call burn
 | 
			
		||||
		_, err = callContract("burn", caller, big.NewInt(1))
 | 
			
		||||
		suite.ErrorContains(err, "Ownable: caller is not the owner")
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -72,12 +72,10 @@ func (suite *Suite) SetupTest() {
 | 
			
		||||
	suite.EvmModuleAddr = suite.AccountKeeper.GetModuleAddress(evmtypes.ModuleName)
 | 
			
		||||
 | 
			
		||||
	// test evm user keys that have no minting permissions
 | 
			
		||||
	key1, err := ethsecp256k1.GenerateKey()
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	suite.Key1 = key1
 | 
			
		||||
	suite.Key1Addr = types.NewInternalEVMAddress(common.BytesToAddress(suite.Key1.PubKey().Address()))
 | 
			
		||||
	suite.Key2, err = ethsecp256k1.GenerateKey()
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	addr, privKey := suite.RandomAccount()
 | 
			
		||||
	suite.Key1 = privKey
 | 
			
		||||
	suite.Key1Addr = types.NewInternalEVMAddress(addr)
 | 
			
		||||
	_, suite.Key2 = suite.RandomAccount()
 | 
			
		||||
 | 
			
		||||
	_, addrs := app.GeneratePrivKeyAddressPairs(4)
 | 
			
		||||
	suite.Addrs = addrs
 | 
			
		||||
@ -182,6 +180,13 @@ func (suite *Suite) Commit() {
 | 
			
		||||
	suite.Ctx = suite.App.NewContext(false, header)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *Suite) RandomAccount() (common.Address, *ethsecp256k1.PrivKey) {
 | 
			
		||||
	privKey, err := ethsecp256k1.GenerateKey()
 | 
			
		||||
	suite.NoError(err)
 | 
			
		||||
	addr := common.BytesToAddress(privKey.PubKey().Address())
 | 
			
		||||
	return addr, privKey
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *Suite) FundAccountWithKava(addr sdk.AccAddress, coins sdk.Coins) {
 | 
			
		||||
	ukava := coins.AmountOf("ukava")
 | 
			
		||||
	if ukava.IsPositive() {
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ import (
 | 
			
		||||
	// Embed ERC20 JSON files
 | 
			
		||||
	_ "embed"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/ethereum/go-ethereum/common"
 | 
			
		||||
	evmtypes "github.com/evmos/ethermint/x/evm/types"
 | 
			
		||||
@ -32,6 +33,12 @@ var (
 | 
			
		||||
 | 
			
		||||
	// ERC20MintableBurnableAddress is the erc20 module address
 | 
			
		||||
	ERC20MintableBurnableAddress common.Address
 | 
			
		||||
 | 
			
		||||
	//go:embed ethermint_json/ERC20KavaWrappedNativeCoin.json
 | 
			
		||||
	ERC20KavaWrappedNativeCoinJSON []byte
 | 
			
		||||
 | 
			
		||||
	// ERC20KavaWrappedNativeCoinContract is the compiled erc20 contract
 | 
			
		||||
	ERC20KavaWrappedNativeCoinContract evmtypes.CompiledContract
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
@ -39,10 +46,19 @@ func init() {
 | 
			
		||||
 | 
			
		||||
	err := json.Unmarshal(ERC20MintableBurnableJSON, &ERC20MintableBurnableContract)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
		panic(fmt.Sprintf("failed to unmarshal ERC20MintableBurnableJSON: %s. %s", err, string(ERC20MintableBurnableJSON)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(ERC20MintableBurnableContract.Bin) == 0 {
 | 
			
		||||
		panic("load contract failed")
 | 
			
		||||
		panic("loading ERC20MintableBurnable contract failed")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = json.Unmarshal(ERC20KavaWrappedNativeCoinJSON, &ERC20KavaWrappedNativeCoinContract)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Sprintf("failed to unmarshal ERC20KavaWrappedNativeCoinJSON: %s. %s", err, string(ERC20KavaWrappedNativeCoinJSON)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(ERC20KavaWrappedNativeCoinContract.Bin) == 0 {
 | 
			
		||||
		panic("loading ERC20KavaWrappedNativeCoin contract failed")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user