mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-20 15:05:21 +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