0g-chain/x/evmutil/keeper/conversion_evm_native.go
2024-09-25 15:00:59 +00:00

243 lines
6.8 KiB
Go

package keeper
import (
"math/big"
errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/0glabs/0g-chain/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) {
coin := sdk.NewCoin(pair.Denom, sdkmath.NewIntFromBigInt(amount))
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
}
// 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 {
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,
amount sdkmath.Int,
) 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
}
amountToLock := amount.BigInt()
amountToMint := amount.BigInt()
if isBep3Asset(pair.Denom) {
amountToMint, amountToLock, err = bep3ERC20AmountToCoinMintAndERC20LockAmount(amount.BigInt())
if err != nil {
return err
}
}
// lock erc20 tokens
if err := k.LockERC20Tokens(ctx, pair, amountToLock, initiator); err != nil {
return err
}
// mint conversion pair coin
coin, err := k.MintConversionPairCoin(ctx, pair, amountToMint, receiver)
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 {
return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error())
}
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 {
return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error())
}
expectedEndBal := big.NewInt(0).Add(startBal, amount)
if expectedEndBal.Cmp(endBal) != 0 {
return errorsmod.Wrapf(
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 {
return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance: %s", err.Error())
}
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 {
return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error())
}
expectedEndBal := big.NewInt(0).Sub(initiatorStartBal, amount)
if expectedEndBal.Cmp(initiatorEndBal) != 0 {
return errorsmod.Wrapf(
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
}