mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 15:25:18 +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
|
## Features
|
||||||
- (evmutil) [#1590] Add allow list param of sdk native denoms that can be transferred to evm
|
- (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]
|
## [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
|
- [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run
|
||||||
large-scale simulations remotely using aws-batch
|
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
|
[#1590]: https://github.com/Kava-Labs/kava/pull/1590
|
||||||
[#1568]: https://github.com/Kava-Labs/kava/pull/1568
|
[#1568]: https://github.com/Kava-Labs/kava/pull/1568
|
||||||
[#1567]: https://github.com/Kava-Labs/kava/pull/1567
|
[#1567]: https://github.com/Kava-Labs/kava/pull/1567
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
@ -64,6 +65,51 @@ func (k Keeper) DeployTestMintableERC20Contract(
|
|||||||
return types.NewInternalEVMAddress(contractAddr), nil
|
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
|
// 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.
|
// unchecked and should only be called after permission and enabled ERC20 checks.
|
||||||
func (k Keeper) MintERC20(
|
func (k Keeper) MintERC20(
|
||||||
|
@ -84,3 +84,64 @@ func (suite *ERC20TestSuite) TestERC20Mint() {
|
|||||||
suite.Require().True(ok, "balanceOf should respond with *big.Int")
|
suite.Require().True(ok, "balanceOf should respond with *big.Int")
|
||||||
suite.Require().Equal(big.NewInt(1234), balance)
|
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)
|
suite.EvmModuleAddr = suite.AccountKeeper.GetModuleAddress(evmtypes.ModuleName)
|
||||||
|
|
||||||
// test evm user keys that have no minting permissions
|
// test evm user keys that have no minting permissions
|
||||||
key1, err := ethsecp256k1.GenerateKey()
|
addr, privKey := suite.RandomAccount()
|
||||||
suite.Require().NoError(err)
|
suite.Key1 = privKey
|
||||||
suite.Key1 = key1
|
suite.Key1Addr = types.NewInternalEVMAddress(addr)
|
||||||
suite.Key1Addr = types.NewInternalEVMAddress(common.BytesToAddress(suite.Key1.PubKey().Address()))
|
_, suite.Key2 = suite.RandomAccount()
|
||||||
suite.Key2, err = ethsecp256k1.GenerateKey()
|
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(4)
|
_, addrs := app.GeneratePrivKeyAddressPairs(4)
|
||||||
suite.Addrs = addrs
|
suite.Addrs = addrs
|
||||||
@ -182,6 +180,13 @@ func (suite *Suite) Commit() {
|
|||||||
suite.Ctx = suite.App.NewContext(false, header)
|
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) {
|
func (suite *Suite) FundAccountWithKava(addr sdk.AccAddress, coins sdk.Coins) {
|
||||||
ukava := coins.AmountOf("ukava")
|
ukava := coins.AmountOf("ukava")
|
||||||
if ukava.IsPositive() {
|
if ukava.IsPositive() {
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
// Embed ERC20 JSON files
|
// Embed ERC20 JSON files
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
@ -32,6 +33,12 @@ var (
|
|||||||
|
|
||||||
// ERC20MintableBurnableAddress is the erc20 module address
|
// ERC20MintableBurnableAddress is the erc20 module address
|
||||||
ERC20MintableBurnableAddress common.Address
|
ERC20MintableBurnableAddress common.Address
|
||||||
|
|
||||||
|
//go:embed ethermint_json/ERC20KavaWrappedNativeCoin.json
|
||||||
|
ERC20KavaWrappedNativeCoinJSON []byte
|
||||||
|
|
||||||
|
// ERC20KavaWrappedNativeCoinContract is the compiled erc20 contract
|
||||||
|
ERC20KavaWrappedNativeCoinContract evmtypes.CompiledContract
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -39,10 +46,19 @@ func init() {
|
|||||||
|
|
||||||
err := json.Unmarshal(ERC20MintableBurnableJSON, &ERC20MintableBurnableContract)
|
err := json.Unmarshal(ERC20MintableBurnableJSON, &ERC20MintableBurnableContract)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(fmt.Sprintf("failed to unmarshal ERC20MintableBurnableJSON: %s. %s", err, string(ERC20MintableBurnableJSON)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ERC20MintableBurnableContract.Bin) == 0 {
|
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