2022-08-23 17:04:40 +00:00
|
|
|
package keeper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math/big"
|
|
|
|
|
2023-04-05 23:21:59 +00:00
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
|
|
sdkmath "cosmossdk.io/math"
|
2022-08-23 17:04:40 +00:00
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
|
|
|
|
"github.com/kava-labs/kava/x/evmutil/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
// MintConversionPairCoin mints the given amount of a ConversionPair denom and
|
|
|
|
// sends it to the provided address.
|
|
|
|
func (k Keeper) MintConversionPairCoin(
|
|
|
|
ctx sdk.Context,
|
|
|
|
pair types.ConversionPair,
|
|
|
|
amount *big.Int,
|
|
|
|
recipient sdk.AccAddress,
|
|
|
|
) (sdk.Coin, error) {
|
2023-04-05 23:21:59 +00:00
|
|
|
coin := sdk.NewCoin(pair.Denom, sdkmath.NewIntFromBigInt(amount))
|
2022-08-23 17:04:40 +00:00
|
|
|
coins := sdk.NewCoins(coin)
|
|
|
|
|
|
|
|
if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil {
|
|
|
|
return sdk.Coin{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, coins); err != nil {
|
|
|
|
return sdk.Coin{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return coin, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// BurnConversionPairCoin transfers the provided amount to the module account
|
|
|
|
// then burns it.
|
|
|
|
func (k Keeper) BurnConversionPairCoin(
|
|
|
|
ctx sdk.Context,
|
|
|
|
pair types.ConversionPair,
|
|
|
|
coin sdk.Coin,
|
|
|
|
account sdk.AccAddress,
|
|
|
|
) error {
|
|
|
|
coins := sdk.NewCoins(coin)
|
|
|
|
|
|
|
|
if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, account, types.ModuleName, coins); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, coins); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConvertCoinToERC20 converts an sdk.Coin from the originating account to an
|
|
|
|
// ERC20 to the receiver account.
|
|
|
|
func (k Keeper) ConvertCoinToERC20(
|
|
|
|
ctx sdk.Context,
|
|
|
|
initiatorAccount sdk.AccAddress,
|
|
|
|
receiverAccount types.InternalEVMAddress,
|
|
|
|
coin sdk.Coin,
|
|
|
|
) error {
|
|
|
|
pair, err := k.GetEnabledConversionPairFromDenom(ctx, coin.Denom)
|
|
|
|
if err != nil {
|
|
|
|
// Coin not in enabled conversion pair list
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := k.BurnConversionPairCoin(ctx, pair, coin, initiatorAccount); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-03-25 17:43:31 +00:00
|
|
|
// handle bep3 conversion pair (8 decimals sdk.Coin -> 18 decimals erc20)
|
|
|
|
// when converting bep3 bank coins to erc20, we will need to unlock the 18
|
|
|
|
// decimals erc20 equivalent of the 8 decimals bep3 sdk.Coins
|
|
|
|
amountToUnlock := coin.Amount.BigInt()
|
|
|
|
if isBep3Asset(pair.Denom) {
|
|
|
|
amountToUnlock = convertBep3CoinAmountToERC20Amount(coin.Amount.BigInt())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := k.UnlockERC20Tokens(ctx, pair, amountToUnlock, receiverAccount); err != nil {
|
2022-08-23 17:04:40 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
|
|
types.EventTypeConvertCoinToERC20,
|
|
|
|
sdk.NewAttribute(types.AttributeKeyInitiator, initiatorAccount.String()),
|
|
|
|
sdk.NewAttribute(types.AttributeKeyReceiver, receiverAccount.String()),
|
|
|
|
sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()),
|
|
|
|
sdk.NewAttribute(types.AttributeKeyAmount, coin.String()),
|
|
|
|
))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConvertERC20ToCoin converts an ERC20 coin from the originating account to an
|
|
|
|
// sdk.Coin to the receiver account.
|
|
|
|
func (k Keeper) ConvertERC20ToCoin(
|
|
|
|
ctx sdk.Context,
|
|
|
|
initiator types.InternalEVMAddress,
|
|
|
|
receiver sdk.AccAddress,
|
|
|
|
contractAddr types.InternalEVMAddress,
|
2023-04-05 23:21:59 +00:00
|
|
|
amount sdkmath.Int,
|
2022-08-23 17:04:40 +00:00
|
|
|
) error {
|
|
|
|
// Check that the contract is enabled to convert to coin
|
|
|
|
pair, err := k.GetEnabledConversionPairFromERC20Address(ctx, contractAddr)
|
|
|
|
if err != nil {
|
|
|
|
// contract not in enabled conversion pair list
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-03-25 17:43:31 +00:00
|
|
|
amountToLock := amount.BigInt()
|
|
|
|
amountToMint := amount.BigInt()
|
|
|
|
|
|
|
|
if isBep3Asset(pair.Denom) {
|
|
|
|
amountToMint, amountToLock, err = bep3ERC20AmountToCoinMintAndERC20LockAmount(amount.BigInt())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-23 17:04:40 +00:00
|
|
|
// lock erc20 tokens
|
2024-03-25 17:43:31 +00:00
|
|
|
if err := k.LockERC20Tokens(ctx, pair, amountToLock, initiator); err != nil {
|
2022-08-23 17:04:40 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// mint conversion pair coin
|
2024-03-25 17:43:31 +00:00
|
|
|
coin, err := k.MintConversionPairCoin(ctx, pair, amountToMint, receiver)
|
2022-08-23 17:04:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
|
|
types.EventTypeConvertERC20ToCoin,
|
|
|
|
sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddr.String()),
|
|
|
|
sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()),
|
|
|
|
sdk.NewAttribute(types.AttributeKeyReceiver, receiver.String()),
|
|
|
|
sdk.NewAttribute(types.AttributeKeyAmount, coin.String()),
|
|
|
|
))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnlockERC20Tokens transfers the given amount of a conversion pair ERC20 token
|
|
|
|
// to the provided account.
|
|
|
|
func (k Keeper) UnlockERC20Tokens(
|
|
|
|
ctx sdk.Context,
|
|
|
|
pair types.ConversionPair,
|
|
|
|
amount *big.Int,
|
|
|
|
receiver types.InternalEVMAddress,
|
|
|
|
) error {
|
|
|
|
contractAddr := pair.GetAddress()
|
|
|
|
startBal, err := k.QueryERC20BalanceOf(ctx, contractAddr, receiver)
|
|
|
|
if err != nil {
|
2023-04-05 23:21:59 +00:00
|
|
|
return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error())
|
2022-08-23 17:04:40 +00:00
|
|
|
}
|
|
|
|
res, err := k.CallEVM(
|
|
|
|
ctx,
|
|
|
|
types.ERC20MintableBurnableContract.ABI, // abi
|
|
|
|
types.ModuleEVMAddress, // from addr
|
|
|
|
pair.GetAddress(), // contract addr
|
|
|
|
"transfer", // method
|
|
|
|
// Transfer ERC20 args
|
|
|
|
receiver.Address,
|
|
|
|
amount,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// validate end bal
|
|
|
|
endBal, err := k.QueryERC20BalanceOf(ctx, contractAddr, receiver)
|
|
|
|
if err != nil {
|
2023-04-05 23:21:59 +00:00
|
|
|
return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error())
|
2022-08-23 17:04:40 +00:00
|
|
|
}
|
|
|
|
expectedEndBal := big.NewInt(0).Add(startBal, amount)
|
|
|
|
if expectedEndBal.Cmp(endBal) != 0 {
|
2023-04-05 23:21:59 +00:00
|
|
|
return errorsmod.Wrapf(
|
2022-08-23 17:04:40 +00:00
|
|
|
types.ErrBalanceInvariance,
|
|
|
|
"invalid token balance - expected: %v, actual: %v",
|
|
|
|
expectedEndBal, endBal,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for unexpected `Approval` event in logs
|
|
|
|
if err := k.monitorApprovalEvent(res); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// LockERC20Tokens transfers the given amount of a conversion pair ERC20 token
|
|
|
|
// from the initiator account to the module account.
|
|
|
|
func (k Keeper) LockERC20Tokens(
|
|
|
|
ctx sdk.Context,
|
|
|
|
pair types.ConversionPair,
|
|
|
|
amount *big.Int,
|
|
|
|
initiator types.InternalEVMAddress,
|
|
|
|
) error {
|
|
|
|
contractAddr := pair.GetAddress()
|
|
|
|
initiatorStartBal, err := k.QueryERC20BalanceOf(ctx, contractAddr, initiator)
|
|
|
|
if err != nil {
|
2023-07-24 23:00:19 +00:00
|
|
|
return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance: %s", err.Error())
|
2022-08-23 17:04:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
res, err := k.CallEVM(
|
|
|
|
ctx,
|
|
|
|
types.ERC20MintableBurnableContract.ABI, // abi
|
|
|
|
initiator.Address, // from addr
|
|
|
|
contractAddr, // contract addr
|
|
|
|
"transfer", // method
|
|
|
|
// Transfer ERC20 args
|
|
|
|
types.ModuleEVMAddress,
|
|
|
|
amount,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// validate end bal
|
|
|
|
initiatorEndBal, err := k.QueryERC20BalanceOf(ctx, contractAddr, initiator)
|
|
|
|
if err != nil {
|
2023-04-05 23:21:59 +00:00
|
|
|
return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error())
|
2022-08-23 17:04:40 +00:00
|
|
|
}
|
|
|
|
expectedEndBal := big.NewInt(0).Sub(initiatorStartBal, amount)
|
|
|
|
if expectedEndBal.Cmp(initiatorEndBal) != 0 {
|
2023-04-05 23:21:59 +00:00
|
|
|
return errorsmod.Wrapf(
|
2022-08-23 17:04:40 +00:00
|
|
|
types.ErrBalanceInvariance,
|
|
|
|
"invalid token balance - expected: %v, actual: %v",
|
|
|
|
expectedEndBal, initiatorEndBal,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for unexpected `Approval` event in logs
|
|
|
|
if err := k.monitorApprovalEvent(res); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|