mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
feat(evmutil): implement MsgConvertCosmosCoinFromERC20 (#1609)
* first pass at convert cosmos coin -> evm msg * test ConvertCosmosCoinFromERC20 method * test message server for MsgConvertCosmosCoinFromERC20 * update spec to include MsgConvertCosmosCoinFromERC20 * update changelog * add CLI command for convert-cosmos-coin-from-erc20 * add test of removed/re-enable denom for convert
This commit is contained in:
parent
d988330d7f
commit
f4b8bf8f07
@ -43,6 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
|||||||
- (evmutil) [#1603] Add MsgConvertCosmosCoinToERC20 for converting an sdk.Coin to an ERC20 in the EVM
|
- (evmutil) [#1603] Add MsgConvertCosmosCoinToERC20 for converting an sdk.Coin to an ERC20 in the EVM
|
||||||
- (evmutil) [#1604] Emit events for MsgConvertCosmosCoinToERC20: `message` & `convert_cosmos_coin_to_erc20`
|
- (evmutil) [#1604] Emit events for MsgConvertCosmosCoinToERC20: `message` & `convert_cosmos_coin_to_erc20`
|
||||||
- (evmutil) [#1605] Add query for deployed ERC20 contracts representing Cosmos coins in the EVM
|
- (evmutil) [#1605] Add query for deployed ERC20 contracts representing Cosmos coins in the EVM
|
||||||
|
- (evmutil) [#1609] Add MsgConvertCosmosCoinFromERC20 for converting the ERC20 back to an sdk.Coin
|
||||||
|
|
||||||
### Client Breaking
|
### Client Breaking
|
||||||
- (evmutil) [#1603] Renamed error `ErrConversionNotEnabled` to `ErrEVMConversionNotEnabled`
|
- (evmutil) [#1603] Renamed error `ErrConversionNotEnabled` to `ErrEVMConversionNotEnabled`
|
||||||
@ -251,6 +252,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
|
||||||
|
|
||||||
|
[#1609]: https://github.com/Kava-Labs/kava/pull/1609
|
||||||
[#1605]: https://github.com/Kava-Labs/kava/pull/1605
|
[#1605]: https://github.com/Kava-Labs/kava/pull/1605
|
||||||
[#1604]: https://github.com/Kava-Labs/kava/pull/1604
|
[#1604]: https://github.com/Kava-Labs/kava/pull/1604
|
||||||
[#1603]: https://github.com/Kava-Labs/kava/pull/1603
|
[#1603]: https://github.com/Kava-Labs/kava/pull/1603
|
||||||
|
@ -3914,7 +3914,7 @@ MsgConvertCoinToERC20Response defines the response value from Msg/ConvertCoinToE
|
|||||||
<a name="kava.evmutil.v1beta1.MsgConvertCosmosCoinFromERC20"></a>
|
<a name="kava.evmutil.v1beta1.MsgConvertCosmosCoinFromERC20"></a>
|
||||||
|
|
||||||
### MsgConvertCosmosCoinFromERC20
|
### MsgConvertCosmosCoinFromERC20
|
||||||
ConvertCosmosCoinFromERC20 defines a conversion from ERC20 to cosmos coins for cosmos-native assets.
|
MsgConvertCosmosCoinFromERC20 defines a conversion from ERC20 to cosmos coins for cosmos-native assets.
|
||||||
|
|
||||||
|
|
||||||
| Field | Type | Label | Description |
|
| Field | Type | Label | Description |
|
||||||
@ -3941,7 +3941,7 @@ MsgConvertCosmosCoinFromERC20Response defines the response value from Msg/MsgCon
|
|||||||
<a name="kava.evmutil.v1beta1.MsgConvertCosmosCoinToERC20"></a>
|
<a name="kava.evmutil.v1beta1.MsgConvertCosmosCoinToERC20"></a>
|
||||||
|
|
||||||
### MsgConvertCosmosCoinToERC20
|
### MsgConvertCosmosCoinToERC20
|
||||||
ConvertCosmosCoinToERC20 defines a conversion from cosmos sdk.Coin to ERC20 for cosmos-native assets.
|
MsgConvertCosmosCoinToERC20 defines a conversion from cosmos sdk.Coin to ERC20 for cosmos-native assets.
|
||||||
|
|
||||||
|
|
||||||
| Field | Type | Label | Description |
|
| Field | Type | Label | Description |
|
||||||
|
@ -57,7 +57,7 @@ message MsgConvertERC20ToCoin {
|
|||||||
// Msg/MsgConvertERC20ToCoin.
|
// Msg/MsgConvertERC20ToCoin.
|
||||||
message MsgConvertERC20ToCoinResponse {}
|
message MsgConvertERC20ToCoinResponse {}
|
||||||
|
|
||||||
// ConvertCosmosCoinToERC20 defines a conversion from cosmos sdk.Coin to ERC20 for cosmos-native assets.
|
// MsgConvertCosmosCoinToERC20 defines a conversion from cosmos sdk.Coin to ERC20 for cosmos-native assets.
|
||||||
message MsgConvertCosmosCoinToERC20 {
|
message MsgConvertCosmosCoinToERC20 {
|
||||||
// Kava bech32 address initiating the conversion.
|
// Kava bech32 address initiating the conversion.
|
||||||
string initiator = 1;
|
string initiator = 1;
|
||||||
@ -70,7 +70,7 @@ message MsgConvertCosmosCoinToERC20 {
|
|||||||
// MsgConvertCosmosCoinToERC20Response defines the response value from Msg/MsgConvertCosmosCoinToERC20.
|
// MsgConvertCosmosCoinToERC20Response defines the response value from Msg/MsgConvertCosmosCoinToERC20.
|
||||||
message MsgConvertCosmosCoinToERC20Response {}
|
message MsgConvertCosmosCoinToERC20Response {}
|
||||||
|
|
||||||
// ConvertCosmosCoinFromERC20 defines a conversion from ERC20 to cosmos coins for cosmos-native assets.
|
// MsgConvertCosmosCoinFromERC20 defines a conversion from ERC20 to cosmos coins for cosmos-native assets.
|
||||||
message MsgConvertCosmosCoinFromERC20 {
|
message MsgConvertCosmosCoinFromERC20 {
|
||||||
// EVM hex address initiating the conversion.
|
// EVM hex address initiating the conversion.
|
||||||
string initiator = 1;
|
string initiator = 1;
|
||||||
|
@ -31,6 +31,7 @@ func GetTxCmd() *cobra.Command {
|
|||||||
getCmdMsgConvertCoinToERC20(),
|
getCmdMsgConvertCoinToERC20(),
|
||||||
getCmdConvertERC20ToCoin(),
|
getCmdConvertERC20ToCoin(),
|
||||||
getCmdMsgConvertCosmosCoinToERC20(),
|
getCmdMsgConvertCosmosCoinToERC20(),
|
||||||
|
getCmdMsgConvertCosmosCoinFromERC20(),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cmd := range cmds {
|
for _, cmd := range cmds {
|
||||||
@ -159,3 +160,42 @@ func getCmdMsgConvertCosmosCoinToERC20() *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCmdMsgConvertCosmosCoinFromERC20() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "convert-cosmos-coin-from-erc20 [receiver_kava_address] [amount] [flags]",
|
||||||
|
Short: "converts asset native to Cosmos Co-chain back from an ERC20 on the EVM co-chain",
|
||||||
|
Example: fmt.Sprintf(
|
||||||
|
`Convert ERC20 representation of 500 ATOM back to a Cosmos coin, sending to kava1q0dkky0505r555etn6u2nz4h4kjcg5y8dg863a:
|
||||||
|
%s tx %s convert-cosmos-coin-from-erc20 kava1q0dkky0505r555etn6u2nz4h4kjcg5y8dg863a 500000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --from <key> --gas 2000000`,
|
||||||
|
version.AppName, types.ModuleName,
|
||||||
|
),
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientTxContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver, err := sdk.AccAddressFromBech32(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("receiver '%s' is an invalid kava address", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, err := sdk.ParseCoinNormalized(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := clientCtx.GetFromAddress()
|
||||||
|
initiator := common.BytesToAddress(signer.Bytes())
|
||||||
|
|
||||||
|
msg := types.NewMsgConvertCosmosCoinFromERC20(initiator.String(), receiver.String(), amount)
|
||||||
|
if err := msg.ValidateBasic(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
errorsmod "cosmossdk.io/errors"
|
errorsmod "cosmossdk.io/errors"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/evmutil/types"
|
"github.com/kava-labs/kava/x/evmutil/types"
|
||||||
)
|
)
|
||||||
@ -54,3 +57,51 @@ func (k *Keeper) ConvertCosmosCoinToERC20(
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConvertCosmosCoinFromERC20 burns the ERC20 wrapper of the cosmos coin and
|
||||||
|
// sends the underlying sdk coin form the module account to the receiver.
|
||||||
|
func (k *Keeper) ConvertCosmosCoinFromERC20(
|
||||||
|
ctx sdk.Context,
|
||||||
|
initiator types.InternalEVMAddress,
|
||||||
|
receiver sdk.AccAddress,
|
||||||
|
coin sdk.Coin,
|
||||||
|
) error {
|
||||||
|
amount := coin.Amount.BigInt()
|
||||||
|
// get deployed contract
|
||||||
|
contractAddress, found := k.GetDeployedCosmosCoinContract(ctx, coin.Denom)
|
||||||
|
if !found {
|
||||||
|
// no contract deployed
|
||||||
|
return errorsmod.Wrapf(types.ErrInvalidCosmosDenom, fmt.Sprintf("no erc20 contract found for %s", coin.Denom))
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify sufficient balance
|
||||||
|
balance, err := k.QueryERC20BalanceOf(ctx, contractAddress, initiator)
|
||||||
|
if err != nil {
|
||||||
|
return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error())
|
||||||
|
}
|
||||||
|
if balance.Cmp(amount) == -1 {
|
||||||
|
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, "failed to convert to cosmos coins")
|
||||||
|
}
|
||||||
|
|
||||||
|
// burn initiator's ERC20 tokens
|
||||||
|
err = k.BurnERC20(ctx, contractAddress, initiator, amount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send sdk coins to receiver, unlocking them from the module account
|
||||||
|
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, receiver, sdk.NewCoins(coin))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(sdk.NewEvent(
|
||||||
|
types.EventTypeConvertCosmosCoinFromERC20,
|
||||||
|
sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyReceiver, receiver.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddress.Hex()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyAmount, coin.String()),
|
||||||
|
))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -15,29 +15,29 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/evmutil/types"
|
"github.com/kava-labs/kava/x/evmutil/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConversionCosmosNativeSuite struct {
|
type convertCosmosCoinToERC20Suite struct {
|
||||||
testutil.Suite
|
testutil.Suite
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConversionCosmosNativeSuite(t *testing.T) {
|
func TestConversionCosmosNativeToEvmSuite(t *testing.T) {
|
||||||
suite.Run(t, new(ConversionCosmosNativeSuite))
|
suite.Run(t, new(convertCosmosCoinToERC20Suite))
|
||||||
}
|
}
|
||||||
|
|
||||||
// fail test if contract for denom not registered
|
// fail test if contract for denom not registered
|
||||||
func (suite *ConversionCosmosNativeSuite) denomContractRegistered(denom string) types.InternalEVMAddress {
|
func (suite *convertCosmosCoinToERC20Suite) denomContractRegistered(denom string) types.InternalEVMAddress {
|
||||||
contractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
|
contractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
|
||||||
suite.True(found)
|
suite.True(found)
|
||||||
return contractAddress
|
return contractAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// fail test if contract for denom IS registered
|
// fail test if contract for denom IS registered
|
||||||
func (suite *ConversionCosmosNativeSuite) denomContractNotRegistered(denom string) {
|
func (suite *convertCosmosCoinToERC20Suite) denomContractNotRegistered(denom string) {
|
||||||
_, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
|
_, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
|
||||||
suite.False(found)
|
suite.False(found)
|
||||||
}
|
}
|
||||||
|
|
||||||
// more tests of tests of this method are made to the msg handler, see ./msg_server_test.go
|
// more tests of tests of this method are made to the msg handler, see ./msg_server_test.go
|
||||||
func (suite *ConversionCosmosNativeSuite) TestConvertCosmosCoinToERC20() {
|
func (suite *convertCosmosCoinToERC20Suite) TestConvertCosmosCoinToERC20() {
|
||||||
allowedDenom := "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"
|
allowedDenom := "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"
|
||||||
initialFunding := sdk.NewInt64Coin(allowedDenom, int64(1e10))
|
initialFunding := sdk.NewInt64Coin(allowedDenom, int64(1e10))
|
||||||
initiator := app.RandomAddress()
|
initiator := app.RandomAddress()
|
||||||
@ -171,3 +171,129 @@ func (suite *ConversionCosmosNativeSuite) TestConvertCosmosCoinToERC20() {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type convertCosmosCoinFromERC20Suite struct {
|
||||||
|
testutil.Suite
|
||||||
|
|
||||||
|
denom string
|
||||||
|
initiator types.InternalEVMAddress
|
||||||
|
receiver sdk.AccAddress
|
||||||
|
|
||||||
|
contractAddress types.InternalEVMAddress
|
||||||
|
initialPosition sdk.Coin
|
||||||
|
|
||||||
|
query func(method string, args ...interface{}) ([]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *convertCosmosCoinFromERC20Suite) SetupTest() {
|
||||||
|
var err error
|
||||||
|
suite.Suite.SetupTest()
|
||||||
|
|
||||||
|
suite.denom = "magic"
|
||||||
|
suite.initiator = testutil.RandomInternalEVMAddress()
|
||||||
|
suite.receiver = app.RandomAddress()
|
||||||
|
|
||||||
|
// manually create an initial position - sdk coin locked in module
|
||||||
|
suite.initialPosition = sdk.NewInt64Coin(suite.denom, 1e12)
|
||||||
|
err = suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(suite.initialPosition))
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// deploy erc20 contract for the denom
|
||||||
|
tokenInfo := types.AllowedCosmosCoinERC20Token{
|
||||||
|
CosmosDenom: suite.denom,
|
||||||
|
Name: "Test Token",
|
||||||
|
Symbol: "MAGIC",
|
||||||
|
Decimals: 6,
|
||||||
|
}
|
||||||
|
suite.contractAddress, err = suite.Keeper.GetOrDeployCosmosCoinERC20Contract(suite.Ctx, tokenInfo)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// manually create an initial position - minted tokens
|
||||||
|
err = suite.Keeper.MintERC20(suite.Ctx, suite.contractAddress, suite.initiator, suite.initialPosition.Amount.BigInt())
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
caller, key := testutil.RandomEvmAccount()
|
||||||
|
suite.query = func(method string, args ...interface{}) ([]interface{}, error) {
|
||||||
|
return suite.QueryContract(
|
||||||
|
types.ERC20KavaWrappedCosmosCoinContract.ABI,
|
||||||
|
caller,
|
||||||
|
key,
|
||||||
|
suite.contractAddress,
|
||||||
|
method,
|
||||||
|
args...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *convertCosmosCoinFromERC20Suite) checkTotalSupply(expectedSupply sdkmath.Int) {
|
||||||
|
res, err := suite.query("totalSupply")
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Len(res, 1)
|
||||||
|
suite.BigIntsEqual(expectedSupply.BigInt(), res[0].(*big.Int), "unexpected total supply")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *convertCosmosCoinFromERC20Suite) checkBalanceOf(address types.InternalEVMAddress, expectedBalance sdkmath.Int) {
|
||||||
|
res, err := suite.query("balanceOf", address.Address)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Len(res, 1)
|
||||||
|
suite.BigIntsEqual(expectedBalance.BigInt(), res[0].(*big.Int), fmt.Sprintf("unexpected balanceOf for %s", address))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConversionCosmosNativeFromEVMSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(convertCosmosCoinFromERC20Suite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *convertCosmosCoinFromERC20Suite) TestConvertCosmosCoinFromERC20_NoContractDeployed() {
|
||||||
|
err := suite.Keeper.ConvertCosmosCoinFromERC20(
|
||||||
|
suite.Ctx,
|
||||||
|
suite.initiator,
|
||||||
|
suite.receiver,
|
||||||
|
sdk.NewInt64Coin("unsupported-denom", 1e6),
|
||||||
|
)
|
||||||
|
suite.ErrorContains(err, "no erc20 contract found for unsupported-denom")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *convertCosmosCoinFromERC20Suite) TestConvertCosmosCoinFromERC20() {
|
||||||
|
// half the initial position
|
||||||
|
amount := suite.initialPosition.SubAmount(suite.initialPosition.Amount.QuoRaw(2))
|
||||||
|
|
||||||
|
suite.Run("partial withdraw", func() {
|
||||||
|
err := suite.Keeper.ConvertCosmosCoinFromERC20(
|
||||||
|
suite.Ctx,
|
||||||
|
suite.initiator,
|
||||||
|
suite.receiver,
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.checkTotalSupply(amount.Amount)
|
||||||
|
suite.checkBalanceOf(suite.initiator, amount.Amount)
|
||||||
|
suite.App.CheckBalance(suite.T(), suite.Ctx, suite.receiver, sdk.NewCoins(amount))
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Run("full withdraw", func() {
|
||||||
|
err := suite.Keeper.ConvertCosmosCoinFromERC20(
|
||||||
|
suite.Ctx,
|
||||||
|
suite.initiator,
|
||||||
|
suite.receiver,
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// expect no remaining erc20 balance
|
||||||
|
suite.checkTotalSupply(sdkmath.ZeroInt())
|
||||||
|
suite.checkBalanceOf(suite.initiator, sdkmath.ZeroInt())
|
||||||
|
// expect full amount withdrawn to receiver
|
||||||
|
suite.App.CheckBalance(suite.T(), suite.Ctx, suite.receiver, sdk.NewCoins(suite.initialPosition))
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Run("insufficient balance", func() {
|
||||||
|
err := suite.Keeper.ConvertCosmosCoinFromERC20(
|
||||||
|
suite.Ctx,
|
||||||
|
suite.initiator,
|
||||||
|
suite.receiver,
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
suite.ErrorContains(err, "failed to convert to cosmos coins: insufficient funds")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -158,6 +158,27 @@ func (k Keeper) MintERC20(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BurnERC20 burns the token amount from the initiator's balance.
|
||||||
|
func (k Keeper) BurnERC20(
|
||||||
|
ctx sdk.Context,
|
||||||
|
contractAddr types.InternalEVMAddress,
|
||||||
|
initiator types.InternalEVMAddress,
|
||||||
|
amount *big.Int,
|
||||||
|
) error {
|
||||||
|
_, err := k.CallEVM(
|
||||||
|
ctx,
|
||||||
|
types.ERC20KavaWrappedCosmosCoinContract.ABI,
|
||||||
|
types.ModuleEVMAddress,
|
||||||
|
contractAddr,
|
||||||
|
"burn",
|
||||||
|
// Burn ERC20 args
|
||||||
|
initiator.Address,
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (k Keeper) QueryERC20BalanceOf(
|
func (k Keeper) QueryERC20BalanceOf(
|
||||||
ctx sdk.Context,
|
ctx sdk.Context,
|
||||||
contractAddr types.InternalEVMAddress,
|
contractAddr types.InternalEVMAddress,
|
||||||
|
@ -154,7 +154,36 @@ func (s msgServer) ConvertCosmosCoinToERC20(
|
|||||||
// back into an sdk.Coin.
|
// back into an sdk.Coin.
|
||||||
func (s msgServer) ConvertCosmosCoinFromERC20(
|
func (s msgServer) ConvertCosmosCoinFromERC20(
|
||||||
goCtx context.Context,
|
goCtx context.Context,
|
||||||
req *types.MsgConvertCosmosCoinFromERC20,
|
msg *types.MsgConvertCosmosCoinFromERC20,
|
||||||
) (*types.MsgConvertCosmosCoinFromERC20Response, error) {
|
) (*types.MsgConvertCosmosCoinFromERC20Response, error) {
|
||||||
panic("unimplemented - coming soon!")
|
ctx := sdk.UnwrapSDKContext(goCtx)
|
||||||
|
|
||||||
|
initiator, err := types.NewInternalEVMAddressFromString(msg.Initiator)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid initiator address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver, err := sdk.AccAddressFromBech32(msg.Receiver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid receiver address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.keeper.ConvertCosmosCoinFromERC20(
|
||||||
|
ctx,
|
||||||
|
initiator,
|
||||||
|
receiver,
|
||||||
|
*msg.Amount,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
sdk.EventTypeMessage,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeySender, msg.Initiator),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &types.MsgConvertCosmosCoinFromERC20Response{}, nil
|
||||||
}
|
}
|
||||||
|
@ -509,3 +509,222 @@ func (suite *MsgServerSuite) TestConvertCosmosCoinToERC20_AlreadyDeployedContrac
|
|||||||
suite.Len(totalSupply, 1)
|
suite.Len(totalSupply, 1)
|
||||||
suite.Equal(amount.MulRaw(2).BigInt(), totalSupply[0].(*big.Int))
|
suite.Equal(amount.MulRaw(2).BigInt(), totalSupply[0].(*big.Int))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *MsgServerSuite) TestConvertCosmosCoinFromERC20() {
|
||||||
|
denom := "magic"
|
||||||
|
tokenInfo := types.NewAllowedCosmosCoinERC20Token(denom, "Cosmos Coin", "MAGIC", 6)
|
||||||
|
initialPosition := sdk.NewInt64Coin(denom, 1e10)
|
||||||
|
initiator := testutil.RandomInternalEVMAddress()
|
||||||
|
|
||||||
|
var contractAddress types.InternalEVMAddress
|
||||||
|
setup := func() {
|
||||||
|
suite.SetupTest()
|
||||||
|
|
||||||
|
// allow conversion to the denom
|
||||||
|
params := suite.Keeper.GetParams(suite.Ctx)
|
||||||
|
params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo)
|
||||||
|
suite.Keeper.SetParams(suite.Ctx, params)
|
||||||
|
|
||||||
|
// setup initial position
|
||||||
|
addr := app.RandomAddress()
|
||||||
|
err := suite.App.FundAccount(suite.Ctx, addr, sdk.NewCoins(initialPosition))
|
||||||
|
suite.NoError(err)
|
||||||
|
err = suite.Keeper.ConvertCosmosCoinToERC20(suite.Ctx, addr, initiator, initialPosition)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
contractAddress, _ = suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
msg types.MsgConvertCosmosCoinFromERC20
|
||||||
|
amountConverted sdkmath.Int
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid - full convert",
|
||||||
|
msg: types.NewMsgConvertCosmosCoinFromERC20(
|
||||||
|
initiator.Hex(),
|
||||||
|
app.RandomAddress().String(),
|
||||||
|
initialPosition,
|
||||||
|
),
|
||||||
|
amountConverted: initialPosition.Amount,
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid - partial convert",
|
||||||
|
msg: types.NewMsgConvertCosmosCoinFromERC20(
|
||||||
|
initiator.Hex(),
|
||||||
|
app.RandomAddress().String(),
|
||||||
|
sdk.NewInt64Coin(denom, 123456),
|
||||||
|
),
|
||||||
|
amountConverted: sdkmath.NewInt(123456),
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - bad initiator",
|
||||||
|
msg: types.NewMsgConvertCosmosCoinFromERC20(
|
||||||
|
"invalid-address",
|
||||||
|
app.RandomAddress().String(),
|
||||||
|
sdk.NewInt64Coin(denom, 123456),
|
||||||
|
),
|
||||||
|
amountConverted: sdkmath.ZeroInt(),
|
||||||
|
expectedErr: "invalid initiator address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - bad receiver",
|
||||||
|
msg: types.NewMsgConvertCosmosCoinFromERC20(
|
||||||
|
testutil.RandomEvmAddress().Hex(),
|
||||||
|
"invalid-address",
|
||||||
|
sdk.NewInt64Coin(denom, 123456),
|
||||||
|
),
|
||||||
|
amountConverted: sdkmath.ZeroInt(),
|
||||||
|
expectedErr: "invalid receiver address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - unsupported asset",
|
||||||
|
msg: types.NewMsgConvertCosmosCoinFromERC20(
|
||||||
|
initiator.Hex(),
|
||||||
|
app.RandomAddress().String(),
|
||||||
|
sdk.NewInt64Coin("not-supported", 123456),
|
||||||
|
),
|
||||||
|
amountConverted: sdkmath.ZeroInt(),
|
||||||
|
expectedErr: "no erc20 contract found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - insufficient funds",
|
||||||
|
msg: types.NewMsgConvertCosmosCoinFromERC20(
|
||||||
|
initiator.Hex(),
|
||||||
|
app.RandomAddress().String(),
|
||||||
|
initialPosition.AddAmount(sdkmath.OneInt()),
|
||||||
|
),
|
||||||
|
amountConverted: sdkmath.ZeroInt(),
|
||||||
|
expectedErr: "failed to convert to cosmos coins: insufficient funds",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
setup()
|
||||||
|
|
||||||
|
_, err := suite.msgServer.ConvertCosmosCoinFromERC20(suite.Ctx, &tc.msg)
|
||||||
|
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
suite.ErrorContains(err, tc.expectedErr)
|
||||||
|
// expect no change in erc20 balance
|
||||||
|
balance, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, initiator)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.BigIntsEqual(initialPosition.Amount.BigInt(), balance, "expected no change in initiator's erc20 balance")
|
||||||
|
// expect no change in module balance
|
||||||
|
suite.Equal(initialPosition.Amount, suite.ModuleBalance(denom), "expected no change in module balance")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
receiver := sdk.MustAccAddressFromBech32(tc.msg.Receiver)
|
||||||
|
// expect receiver to have the sdk coins
|
||||||
|
sdkBalance := suite.BankKeeper.GetBalance(suite.Ctx, receiver, denom)
|
||||||
|
suite.Equal(tc.amountConverted, sdkBalance.Amount)
|
||||||
|
|
||||||
|
newEvmBalance := initialPosition.SubAmount(tc.amountConverted)
|
||||||
|
// expect initiator to have the balance deducted
|
||||||
|
evmBalance, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, initiator)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.BigIntsEqual(newEvmBalance.Amount.BigInt(), evmBalance, "unexpected initiator final erc20 balance")
|
||||||
|
|
||||||
|
// expect tokens to be deducted from module account
|
||||||
|
suite.True(newEvmBalance.Amount.Equal(suite.ModuleBalance(denom)), "unexpected module balance")
|
||||||
|
|
||||||
|
// expect erc20 total supply to reflect new value
|
||||||
|
caller, key := testutil.RandomEvmAccount()
|
||||||
|
totalSupply, err := suite.QueryContract(
|
||||||
|
types.ERC20KavaWrappedCosmosCoinContract.ABI,
|
||||||
|
caller,
|
||||||
|
key,
|
||||||
|
contractAddress,
|
||||||
|
"totalSupply",
|
||||||
|
)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.BigIntsEqual(newEvmBalance.Amount.BigInt(), totalSupply[0].(*big.Int), "unexpected total supply")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the test verifies the behavior for when a denom is removed from the params list
|
||||||
|
// after conversions have been made:
|
||||||
|
// - it should prevent more conversions from sdk -> evm for that denom
|
||||||
|
// - existing erc20s should be allowed to get converted back to a sdk.Coins
|
||||||
|
// - allowing the denom again should use existing contract
|
||||||
|
func (suite *MsgServerSuite) TestConvertCosmosCoinForRemovedDenom() {
|
||||||
|
denom := "magic"
|
||||||
|
tokenInfo := types.NewAllowedCosmosCoinERC20Token(denom, "MAGIC COIN", "MAGIC", 6)
|
||||||
|
account := app.RandomAddress()
|
||||||
|
evmAddr := types.BytesToInternalEVMAddress(account.Bytes())
|
||||||
|
coin := func(amt int64) sdk.Coin { return sdk.NewInt64Coin(denom, amt) }
|
||||||
|
|
||||||
|
// fund account
|
||||||
|
suite.NoError(suite.App.FundAccount(suite.Ctx, account, sdk.NewCoins(coin(1e10))))
|
||||||
|
|
||||||
|
// setup the token as allowed
|
||||||
|
params := suite.Keeper.GetParams(suite.Ctx)
|
||||||
|
params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo)
|
||||||
|
suite.Keeper.SetParams(suite.Ctx, params)
|
||||||
|
|
||||||
|
// convert some coins while its allowed
|
||||||
|
msg := types.NewMsgConvertCosmosCoinToERC20(account.String(), evmAddr.Hex(), coin(5e9))
|
||||||
|
_, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// expect contract registered
|
||||||
|
contractAddress, isRegistered := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
|
||||||
|
suite.True(isRegistered)
|
||||||
|
suite.False(contractAddress.IsNil())
|
||||||
|
|
||||||
|
// unregister contract
|
||||||
|
params.AllowedCosmosDenoms = []types.AllowedCosmosCoinERC20Token{}
|
||||||
|
suite.Keeper.SetParams(suite.Ctx, params)
|
||||||
|
|
||||||
|
suite.Run("disallows sdk -> evm when removed", func() {
|
||||||
|
msg := types.NewMsgConvertCosmosCoinToERC20(account.String(), evmAddr.Hex(), coin(5e9))
|
||||||
|
_, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg)
|
||||||
|
suite.ErrorContains(err, "sdk.Coin not enabled to convert to ERC20 token")
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Run("allows conversion of existing ERC20s", func() {
|
||||||
|
msg := types.NewMsgConvertCosmosCoinFromERC20(evmAddr.Hex(), account.String(), coin(5e9))
|
||||||
|
_, err := suite.msgServer.ConvertCosmosCoinFromERC20(suite.Ctx, &msg)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// should be fully withdrawn
|
||||||
|
erc20Bal, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, evmAddr)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.BigIntsEqual(big.NewInt(0), erc20Bal, "cosmos coins were not converted back")
|
||||||
|
sdkBal := suite.BankKeeper.GetBalance(suite.Ctx, account, denom)
|
||||||
|
suite.Equal(coin(1e10), sdkBal)
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Run("contract stays registered", func() {
|
||||||
|
postDisableContractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(contractAddress, postDisableContractAddress)
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Run("re-enable uses original contract", func() {
|
||||||
|
// re-enable contract
|
||||||
|
params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo)
|
||||||
|
suite.Keeper.SetParams(suite.Ctx, params)
|
||||||
|
|
||||||
|
// attempt conversion
|
||||||
|
msg := types.NewMsgConvertCosmosCoinToERC20(account.String(), evmAddr.Hex(), coin(1e10))
|
||||||
|
_, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// should have balance on original ERC20 contract
|
||||||
|
erc20Bal, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, evmAddr)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.BigIntsEqual(big.NewInt(1e10), erc20Bal, "cosmos coins were not converted")
|
||||||
|
sdkBal := suite.BankKeeper.GetBalance(suite.Ctx, account, denom)
|
||||||
|
suite.True(sdkBal.IsZero())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -54,6 +54,8 @@ Only Cosmos co-chain denominations that are in the `AllowedCosmosDenoms` param (
|
|||||||
|
|
||||||
The ERC20 contracts are deployed and managed by x/evmutil. The contract is deployed on first convert of the coin. Once deployed, the addresses of the contracts can be queried via the `DeployedCosmosCoinContracts` query (`deployed_cosmos_coin_contracts` endpoint).
|
The ERC20 contracts are deployed and managed by x/evmutil. The contract is deployed on first convert of the coin. Once deployed, the addresses of the contracts can be queried via the `DeployedCosmosCoinContracts` query (`deployed_cosmos_coin_contracts` endpoint).
|
||||||
|
|
||||||
|
If a denom is removed from the `AllowedCosmosDenoms` param, existing ERC20 tokens can be converted back to the underlying sdk.Coin via `MsgConvertCosmosCoinFromERC20`, but no conversions from sdk.Coin -> ERC via `MsgConvertCosmosCoinToERC20` are allowed.
|
||||||
|
|
||||||
### EVM-Native Assets
|
### EVM-Native Assets
|
||||||
|
|
||||||
ERC-20 tokens native to the EVM can be converted into an `sdk.Coin` in the Cosmos ecosystem. This works by transferring the tokens to `x/evmutil`'s module account and then minting an `sdk.Coin` to the receiver. Converting back is the inverse: the `sdk.Coin` of the initiator is burned and the original ERC-20 tokens that were locked into the module account are transferred back to the receiver.
|
ERC-20 tokens native to the EVM can be converted into an `sdk.Coin` in the Cosmos ecosystem. This works by transferring the tokens to `x/evmutil`'s module account and then minting an `sdk.Coin` to the receiver. Converting back is the inverse: the `sdk.Coin` of the initiator is burned and the original ERC-20 tokens that were locked into the module account are transferred back to the receiver.
|
||||||
|
@ -18,7 +18,7 @@ service Msg {
|
|||||||
rpc ConvertCosmosCoinToERC20(MsgConvertCosmosCoinToERC20) returns (MsgConvertCosmosCoinToERC20Response);
|
rpc ConvertCosmosCoinToERC20(MsgConvertCosmosCoinToERC20) returns (MsgConvertCosmosCoinToERC20Response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertCosmosCoinToERC20 defines a conversion from cosmos sdk.Coin to ERC20.
|
// MsgConvertCosmosCoinToERC20 defines a conversion from cosmos sdk.Coin to ERC20.
|
||||||
message MsgConvertCosmosCoinToERC20 {
|
message MsgConvertCosmosCoinToERC20 {
|
||||||
// Kava bech32 address initiating the conversion.
|
// Kava bech32 address initiating the conversion.
|
||||||
string initiator = 1;
|
string initiator = 1;
|
||||||
@ -36,6 +36,33 @@ message MsgConvertCosmosCoinToERC20 {
|
|||||||
- The `amount` is deducted from the `initiator`'s balance and transferred to the module account.
|
- The `amount` is deducted from the `initiator`'s balance and transferred to the module account.
|
||||||
- An equivalent amount of ERC20 tokens are minted by `x/evmutil` to the `receiver`.
|
- An equivalent amount of ERC20 tokens are minted by `x/evmutil` to the `receiver`.
|
||||||
|
|
||||||
|
## MsgConvertCosmosCoinFromERC20
|
||||||
|
|
||||||
|
`MsgConvertCosmosCoinFromERC20` is the inverse of `MsgConvertCosmosCoinToERC20`. It converts an ERC20 representation of a cosmos-sdk coin back to its underlying sdk.Coin.
|
||||||
|
|
||||||
|
|
||||||
|
```proto
|
||||||
|
service Msg {
|
||||||
|
// ConvertCosmosCoinFromERC20 defines a method for converting a cosmos sdk.Coin to an ERC20.
|
||||||
|
rpc ConvertCosmosCoinFromERC20(MsgConvertCosmosCoinFromERC20) returns (MsgConvertCosmosCoinFromERC20Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgConvertCosmosCoinFromERC20 defines a conversion from ERC20 to cosmos coins for cosmos-native assets.
|
||||||
|
message MsgConvertCosmosCoinFromERC20 {
|
||||||
|
// EVM hex address initiating the conversion.
|
||||||
|
string initiator = 1;
|
||||||
|
// Kava bech32 address that will receive the cosmos coins.
|
||||||
|
string receiver = 2;
|
||||||
|
// Amount is the amount to convert, expressed as a Cosmos coin.
|
||||||
|
cosmos.base.v1beta1.Coin amount = 3;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Changes
|
||||||
|
|
||||||
|
- The `amount` is transferred from the `x/evmutil` module account to the `receiver`.
|
||||||
|
- The same amount of the corresponding ERC20 is burned from the `initiator` account in the EVM.
|
||||||
|
|
||||||
## MsgConvertERC20ToCoin
|
## MsgConvertERC20ToCoin
|
||||||
|
|
||||||
`MsgConvertCoinToERC20` converts a Kava ERC20 coin to sdk.Coin. This message is for moving EVM-native assets from the EVM to the Cosmos ecosystem.
|
`MsgConvertCoinToERC20` converts a Kava ERC20 coin to sdk.Coin. This message is for moving EVM-native assets from the EVM to the Cosmos ecosystem.
|
||||||
|
@ -40,3 +40,14 @@ The evmutil module emits the following events:
|
|||||||
| convert_cosmos_coin_to_erc20 | amount | `{amount}` |
|
| convert_cosmos_coin_to_erc20 | amount | `{amount}` |
|
||||||
| message | module | evmutil |
|
| message | module | evmutil |
|
||||||
| message | sender | {'sender address'} |
|
| message | sender | {'sender address'} |
|
||||||
|
|
||||||
|
### MsgConvertCosmosCoinFromERC20
|
||||||
|
|
||||||
|
| Type | Attribute Key | Attribute Value |
|
||||||
|
| ------------------------------ | ------------- | ------------------ |
|
||||||
|
| convert_cosmos_coin_from_erc20 | initiator | `{initiator}` |
|
||||||
|
| convert_cosmos_coin_from_erc20 | receiver | `{receiver}` |
|
||||||
|
| convert_cosmos_coin_from_erc20 | erc20_address | `{erc20_address}` |
|
||||||
|
| convert_cosmos_coin_from_erc20 | amount | `{amount}` |
|
||||||
|
| message | module | evmutil |
|
||||||
|
| message | sender | {'sender address'} |
|
||||||
|
@ -9,6 +9,7 @@ const (
|
|||||||
EventTypeConvertCoinToERC20 = "convert_evm_erc20_from_coin"
|
EventTypeConvertCoinToERC20 = "convert_evm_erc20_from_coin"
|
||||||
|
|
||||||
EventTypeConvertCosmosCoinToERC20 = "convert_cosmos_coin_to_erc20"
|
EventTypeConvertCosmosCoinToERC20 = "convert_cosmos_coin_to_erc20"
|
||||||
|
EventTypeConvertCosmosCoinFromERC20 = "convert_cosmos_coin_from_erc20"
|
||||||
|
|
||||||
// Event Attributes - Common
|
// Event Attributes - Common
|
||||||
AttributeKeyReceiver = "receiver"
|
AttributeKeyReceiver = "receiver"
|
||||||
|
@ -236,7 +236,7 @@ func (m *MsgConvertERC20ToCoinResponse) XXX_DiscardUnknown() {
|
|||||||
|
|
||||||
var xxx_messageInfo_MsgConvertERC20ToCoinResponse proto.InternalMessageInfo
|
var xxx_messageInfo_MsgConvertERC20ToCoinResponse proto.InternalMessageInfo
|
||||||
|
|
||||||
// ConvertCosmosCoinToERC20 defines a conversion from cosmos sdk.Coin to ERC20 for cosmos-native assets.
|
// MsgConvertCosmosCoinToERC20 defines a conversion from cosmos sdk.Coin to ERC20 for cosmos-native assets.
|
||||||
type MsgConvertCosmosCoinToERC20 struct {
|
type MsgConvertCosmosCoinToERC20 struct {
|
||||||
// Kava bech32 address initiating the conversion.
|
// Kava bech32 address initiating the conversion.
|
||||||
Initiator string `protobuf:"bytes,1,opt,name=initiator,proto3" json:"initiator,omitempty"`
|
Initiator string `protobuf:"bytes,1,opt,name=initiator,proto3" json:"initiator,omitempty"`
|
||||||
@ -337,7 +337,7 @@ func (m *MsgConvertCosmosCoinToERC20Response) XXX_DiscardUnknown() {
|
|||||||
|
|
||||||
var xxx_messageInfo_MsgConvertCosmosCoinToERC20Response proto.InternalMessageInfo
|
var xxx_messageInfo_MsgConvertCosmosCoinToERC20Response proto.InternalMessageInfo
|
||||||
|
|
||||||
// ConvertCosmosCoinFromERC20 defines a conversion from ERC20 to cosmos coins for cosmos-native assets.
|
// MsgConvertCosmosCoinFromERC20 defines a conversion from ERC20 to cosmos coins for cosmos-native assets.
|
||||||
type MsgConvertCosmosCoinFromERC20 struct {
|
type MsgConvertCosmosCoinFromERC20 struct {
|
||||||
// EVM hex address initiating the conversion.
|
// EVM hex address initiating the conversion.
|
||||||
Initiator string `protobuf:"bytes,1,opt,name=initiator,proto3" json:"initiator,omitempty"`
|
Initiator string `protobuf:"bytes,1,opt,name=initiator,proto3" json:"initiator,omitempty"`
|
||||||
|
Loading…
Reference in New Issue
Block a user