github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/erc20/keeper/ibc.go (about)

     1  package keeper
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  	"strings"
     8  
     9  	"github.com/ethereum/go-ethereum/common"
    10  
    11  	ethermint "github.com/fibonacci-chain/fbc/app/types"
    12  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    13  	ibctransfertypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/apps/transfer/types"
    14  	ibcclienttypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/02-client/types"
    15  	tmtypes "github.com/fibonacci-chain/fbc/libs/tendermint/types"
    16  	"github.com/fibonacci-chain/fbc/x/erc20/types"
    17  	evmtypes "github.com/fibonacci-chain/fbc/x/evm/types"
    18  )
    19  
    20  // OnMintVouchers after minting vouchers on this chain
    21  func (k Keeper) OnMintVouchers(ctx sdk.Context, vouchers sdk.SysCoins, receiver string) error {
    22  	if err := k.ConvertVouchers(ctx, receiver, vouchers); err != nil {
    23  		k.Logger(ctx).Error(
    24  			fmt.Sprintf("Failed to convert vouchers to evm tokens for receiver %s, coins %s. Receive error %s",
    25  				receiver, vouchers.String(), err))
    26  		return err
    27  	}
    28  	return nil
    29  }
    30  
    31  // OnUnescrowNatives after unescrow natives on this chain
    32  func (k Keeper) OnUnescrowNatives(ctx sdk.Context, natives sdk.SysCoins, receiver string) error {
    33  	if err := k.ConvertNatives(ctx, receiver, natives); err != nil {
    34  		k.Logger(ctx).Error(
    35  			fmt.Sprintf("Failed to convert natives to evm tokens for receiver %s, coins %s. Receive error %s",
    36  				receiver, natives.String(), err))
    37  		return err
    38  	}
    39  	return nil
    40  }
    41  
    42  // ConvertVouchers convert vouchers into evm tokens.
    43  func (k Keeper) ConvertVouchers(ctx sdk.Context, from string, vouchers sdk.SysCoins) error {
    44  	if len(strings.TrimSpace(from)) == 0 {
    45  		return errors.New("empty from address string is not allowed")
    46  	}
    47  	fromAddr, err := sdk.AccAddressFromBech32(from)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	params := k.GetParams(ctx)
    53  	for _, c := range vouchers {
    54  		// okc1:xxb----->okc2:ibc/xxb---->okc2:erc20/xxb
    55  		if err := k.ConvertVoucherToERC20(ctx, fromAddr, c, params.EnableAutoDeployment); err != nil {
    56  			return err
    57  		}
    58  	}
    59  
    60  	return nil
    61  }
    62  
    63  // ConvertNatives convert natives into evm tokens.
    64  func (k Keeper) ConvertNatives(ctx sdk.Context, from string, vouchers sdk.SysCoins) error {
    65  	if len(strings.TrimSpace(from)) == 0 {
    66  		return errors.New("empty from address string is not allowed")
    67  	}
    68  	fromAddr, err := sdk.AccAddressFromBech32(from)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	for _, c := range vouchers {
    74  		// if there is a contract associated with this native coin,
    75  		// the native coin come from native erc20
    76  		// okc1:erc20/xxb----->okc2:ibc/xxb---->okc1:ibc/yyb---->okc2:erc20/xxb
    77  		if contract, found := k.GetContractByDenom(ctx, c.Denom); found {
    78  			if err := k.ConvertNativeToERC20(ctx, fromAddr, c, contract); err != nil {
    79  				return err
    80  			}
    81  		}
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  // ConvertVoucherToERC20 convert voucher into evm token.
    88  func (k Keeper) ConvertVoucherToERC20(ctx sdk.Context, from sdk.AccAddress, voucher sdk.SysCoin, autoDeploy bool) error {
    89  	k.Logger(ctx).Info("convert vouchers into evm tokens",
    90  		"fromBech32", from.String(),
    91  		"fromEth", common.BytesToAddress(from.Bytes()).String(),
    92  		"voucher", voucher.String())
    93  
    94  	if !types.IsValidIBCDenom(voucher.Denom) {
    95  		return fmt.Errorf("coin %s is not supported for wrapping", voucher.Denom)
    96  	}
    97  
    98  	var err error
    99  	contract, found := k.GetContractByDenom(ctx, voucher.Denom)
   100  	if !found {
   101  		// automated deployment contracts
   102  		if !autoDeploy {
   103  			k.Logger(ctx).Error("no contract found and not auto deploy for the denom", "denom", voucher.Denom)
   104  			return types.ErrNoContractNotAuto
   105  		}
   106  		contract, err = k.DeployModuleERC20(ctx, voucher.Denom)
   107  		if err != nil {
   108  			return err
   109  		}
   110  		k.SetContractForDenom(ctx, voucher.Denom, contract)
   111  		k.Logger(ctx).Info("contract created for coin", "contract", contract.String(), "denom", voucher.Denom)
   112  
   113  		ctx.EventManager().EmitEvents(sdk.Events{
   114  			sdk.NewEvent(
   115  				types.EventTypDeployModuleERC20,
   116  				sdk.NewAttribute(types.AttributeKeyContractAddr, contract.String()),
   117  				sdk.NewAttribute(ibctransfertypes.AttributeKeyDenom, voucher.Denom),
   118  			),
   119  		})
   120  	}
   121  
   122  	// 1. transfer voucher from user address to contact address in bank
   123  	if err := k.bankKeeper.SendCoins(ctx, from, sdk.AccAddress(contract.Bytes()), sdk.NewCoins(voucher)); err != nil {
   124  		return err
   125  	}
   126  	// 2. call contract, mint token to user address in contract
   127  	if _, err := k.CallModuleERC20(
   128  		ctx,
   129  		contract,
   130  		types.ContractMintMethod,
   131  		common.BytesToAddress(from.Bytes()),
   132  		voucher.Amount.BigInt()); err != nil {
   133  		return err
   134  	}
   135  
   136  	ctx.EventManager().EmitEvents(sdk.Events{
   137  		sdk.NewEvent(
   138  			types.EventTypLock,
   139  			sdk.NewAttribute(types.AttributeKeyFrom, from.String()),
   140  			sdk.NewAttribute(types.AttributeKeyTo, contract.String()),
   141  			sdk.NewAttribute(sdk.AttributeKeyAmount, voucher.String()),
   142  		),
   143  		sdk.NewEvent(
   144  			types.EventTypCallModuleERC20,
   145  			sdk.NewAttribute(types.AttributeKeyContractAddr, contract.String()),
   146  			sdk.NewAttribute(types.AttributeKeyContractMethod, types.ContractMintMethod),
   147  			sdk.NewAttribute(ibctransfertypes.AttributeKeyReceiver, from.String()),
   148  			sdk.NewAttribute(ibctransfertypes.AttributeKeyAmount, voucher.Amount.BigInt().String()),
   149  		),
   150  	})
   151  	return nil
   152  }
   153  
   154  // ConvertNativeToERC20 convert native into evm token.
   155  func (k Keeper) ConvertNativeToERC20(ctx sdk.Context, from sdk.AccAddress, native sdk.SysCoin, contract common.Address) error {
   156  	k.Logger(ctx).Info("convert native into evm tokens",
   157  		"fromBech32", from.String(),
   158  		"fromEth", common.BytesToAddress(from.Bytes()).String(),
   159  		"native", native.String(),
   160  		"contract", contract.String())
   161  
   162  	// 1. transfer native from user address to module address and burn them
   163  	if err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, from, types.ModuleName, sdk.NewCoins(native)); err != nil {
   164  		return err
   165  	}
   166  	if err := k.supplyKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(native)); err != nil {
   167  		return err
   168  	}
   169  
   170  	// 2. call contract, mint token to user address in contract
   171  	if _, err := k.CallModuleERC20(
   172  		ctx,
   173  		contract,
   174  		types.ContractMintMethod,
   175  		common.BytesToAddress(from.Bytes()),
   176  		native.Amount.BigInt()); err != nil {
   177  		return err
   178  	}
   179  
   180  	ctx.EventManager().EmitEvents(sdk.Events{
   181  		sdk.NewEvent(
   182  			types.EventTypBurn,
   183  			sdk.NewAttribute(types.AttributeKeyFrom, from.String()),
   184  			sdk.NewAttribute(sdk.AttributeKeyAmount, native.String()),
   185  		),
   186  		sdk.NewEvent(
   187  			types.EventTypCallModuleERC20,
   188  			sdk.NewAttribute(types.AttributeKeyContractAddr, contract.String()),
   189  			sdk.NewAttribute(types.AttributeKeyContractMethod, types.ContractMintMethod),
   190  			sdk.NewAttribute(ibctransfertypes.AttributeKeyReceiver, from.String()),
   191  			sdk.NewAttribute(ibctransfertypes.AttributeKeyAmount, native.Amount.BigInt().String()),
   192  		),
   193  	})
   194  	return nil
   195  }
   196  
   197  // DeployModuleERC20 deploy an embed erc20 contract
   198  func (k Keeper) DeployModuleERC20(ctx sdk.Context, denom string) (common.Address, error) {
   199  	implContract, found := k.GetImplementTemplateContract(ctx)
   200  	if !found {
   201  		return common.Address{}, errors.New("not found implement contract")
   202  	}
   203  	proxyContract, found := k.GetProxyTemplateContract(ctx)
   204  	if !found {
   205  		return common.Address{}, errors.New("not found proxy contract")
   206  	}
   207  
   208  	// 1. deploy implement contract
   209  	byteCode := common.Hex2Bytes(implContract.Bin)
   210  	_, implRes, err := k.callEvmByModule(ctx, nil, big.NewInt(0), byteCode)
   211  	if err != nil {
   212  		return common.Address{}, err
   213  	}
   214  
   215  	// 2. deploy proxy contract
   216  	byteCode = common.Hex2Bytes(proxyContract.Bin)
   217  	implInput, err := implContract.ABI.Pack("initialize", denom, uint8(0))
   218  	if err != nil {
   219  		return common.Address{}, err
   220  	}
   221  	input, err := proxyContract.ABI.Pack("", implRes.ContractAddress, implInput)
   222  	if err != nil {
   223  		return common.Address{}, err
   224  	}
   225  	data := append(byteCode, input...)
   226  	_, res, err := k.callEvmByModule(ctx, nil, big.NewInt(0), data)
   227  	if err != nil {
   228  		return common.Address{}, err
   229  	}
   230  	return res.ContractAddress, nil
   231  }
   232  
   233  // CallModuleERC20 call a method of ModuleERC20 contract
   234  func (k Keeper) CallModuleERC20(ctx sdk.Context, contract common.Address, method string, args ...interface{}) ([]byte, error) {
   235  	k.Logger(ctx).Info("call erc20 module contract", "contract", contract.String(), "method", method, "args", args)
   236  
   237  	implContract, found := k.GetImplementTemplateContract(ctx)
   238  	if !found {
   239  		return nil, errors.New("not found implement contract")
   240  	}
   241  	data, err := implContract.ABI.Pack(method, args...)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	_, res, err := k.callEvmByModule(ctx, &contract, big.NewInt(0), data)
   247  	if err != nil {
   248  		return nil, fmt.Errorf("call contract failed: %s, %s, %s", contract.Hex(), method, err)
   249  	}
   250  	return res.Ret, nil
   251  }
   252  
   253  func (k Keeper) CallModuleERC20Proxy(ctx sdk.Context, contract common.Address, method string, args ...interface{}) ([]byte, error) {
   254  	k.Logger(ctx).Info("call proxy erc20 module contract", "contract", contract.String(), "method", method, "args", args)
   255  
   256  	proxyContract, found := k.GetProxyTemplateContract(ctx)
   257  	if !found {
   258  		return nil, errors.New("not found proxy contract")
   259  	}
   260  	data, err := proxyContract.ABI.Pack(method, args...)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  
   265  	_, res, err := k.callEvmByModule(ctx, &contract, big.NewInt(0), data)
   266  	if err != nil {
   267  		return nil, fmt.Errorf("call contract failed: %s, %s, %s", contract.Hex(), method, err)
   268  	}
   269  	return res.Ret, nil
   270  }
   271  
   272  // callEvmByModule execute an evm message from native module
   273  func (k Keeper) callEvmByModule(ctx sdk.Context, to *common.Address, value *big.Int, data []byte) (*evmtypes.ExecutionResult, *evmtypes.ResultData, error) {
   274  	config, found := k.evmKeeper.GetChainConfig(ctx)
   275  	if !found {
   276  		return nil, nil, types.ErrChainConfigNotFound
   277  	}
   278  
   279  	chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
   280  	if err != nil {
   281  		return nil, nil, err
   282  	}
   283  
   284  	acc := k.accountKeeper.GetAccount(ctx, types.IbcEvmModuleBechAddr)
   285  	if acc == nil {
   286  		acc = k.accountKeeper.NewAccountWithAddress(ctx, types.IbcEvmModuleBechAddr)
   287  	}
   288  	nonce := acc.GetSequence()
   289  	txHash := tmtypes.Tx(ctx.TxBytes()).Hash(ctx.BlockHeight())
   290  	ethTxHash := common.BytesToHash(txHash)
   291  
   292  	gasLimit := ctx.GasMeter().Limit()
   293  	if gasLimit == sdk.NewInfiniteGasMeter().Limit() {
   294  		gasLimit = k.evmKeeper.GetParams(ctx).MaxGasLimitPerTx
   295  	}
   296  
   297  	st := evmtypes.StateTransition{
   298  		AccountNonce: nonce,
   299  		Price:        big.NewInt(0),
   300  		GasLimit:     gasLimit,
   301  		Recipient:    to,
   302  		Amount:       value,
   303  		Payload:      data,
   304  		Csdb:         evmtypes.CreateEmptyCommitStateDB(k.evmKeeper.GenerateCSDBParams(), ctx),
   305  		ChainID:      chainIDEpoch,
   306  		TxHash:       &ethTxHash,
   307  		Sender:       types.IbcEvmModuleETHAddr,
   308  		Simulate:     ctx.IsCheckTx(),
   309  		TraceTx:      false,
   310  		TraceTxLog:   false,
   311  	}
   312  
   313  	executionResult, resultData, err, innertxs, contracts := st.TransitionDb(ctx, config)
   314  	if !ctx.IsCheckTx() && !ctx.IsTraceTx() {
   315  		k.addEVMInnerTx(ethTxHash.Hex(), innertxs, contracts)
   316  	}
   317  	if err != nil {
   318  		return nil, nil, err
   319  	}
   320  
   321  	st.Csdb.Commit(false) // write code to db
   322  	acc.SetSequence(nonce + 1)
   323  	k.accountKeeper.SetAccount(ctx, acc)
   324  
   325  	return executionResult, resultData, err
   326  }
   327  
   328  // IbcTransferVouchers transfer vouchers to other chain by ibc
   329  func (k Keeper) IbcTransferVouchers(ctx sdk.Context, from, to string, vouchers sdk.SysCoins) error {
   330  	if len(strings.TrimSpace(from)) == 0 {
   331  		return errors.New("empty from address string is not allowed")
   332  	}
   333  	fromAddr, err := sdk.AccAddressFromBech32(from)
   334  	if err != nil {
   335  		return err
   336  	}
   337  
   338  	if len(strings.TrimSpace(to)) == 0 {
   339  		return errors.New("to address cannot be empty")
   340  	}
   341  	k.Logger(ctx).Info("transfer vouchers to other chain by ibc", "from", from, "to", to, "vouchers", vouchers)
   342  
   343  	for _, c := range vouchers {
   344  		if _, found := k.GetContractByDenom(ctx, c.Denom); !found {
   345  			return fmt.Errorf("coin %s is not supported", c.Denom)
   346  		}
   347  		// okc2:erc20/xxb----->okc2:ibc/xxb---ibc--->okc1:xxb
   348  		if err := k.ibcSendTransfer(ctx, fromAddr, to, c); err != nil {
   349  			return err
   350  		}
   351  	}
   352  
   353  	return nil
   354  }
   355  
   356  // IbcTransferNative20 transfer native20 to other chain by ibc
   357  func (k Keeper) IbcTransferNative20(ctx sdk.Context, from, to string, native20s sdk.SysCoins, portID, channelID string) error {
   358  	if len(strings.TrimSpace(from)) == 0 {
   359  		return errors.New("empty from address string is not allowed")
   360  	}
   361  	fromAddr, err := sdk.AccAddressFromBech32(from)
   362  	if err != nil {
   363  		return err
   364  	}
   365  
   366  	if len(strings.TrimSpace(to)) == 0 {
   367  		return errors.New("to address cannot be empty")
   368  	}
   369  	k.Logger(ctx).Info("transfer native20 to other chain by ibc", "from", from, "to", to, "vouchers", native20s)
   370  
   371  	for _, c := range native20s {
   372  		if _, found := k.GetContractByDenom(ctx, c.Denom); !found {
   373  			return fmt.Errorf("coin %s is not supported", c.Denom)
   374  		}
   375  		// okc2:erc20/xxb----->okc2:ibc/xxb---ibc--->okc1:xxb
   376  		if err := k.ibcSendTransferWithChannel(ctx, fromAddr, to, c, portID, channelID); err != nil {
   377  			return err
   378  		}
   379  	}
   380  
   381  	return nil
   382  }
   383  
   384  func (k Keeper) ibcSendTransfer(ctx sdk.Context, sender sdk.AccAddress, to string, coin sdk.Coin) error {
   385  	// Coin needs to be a voucher so that we can extract the channel id from the denom
   386  	channelID, err := k.GetSourceChannelID(ctx, coin.Denom)
   387  	if err != nil {
   388  		return err
   389  	}
   390  
   391  	// Transfer coins to receiver through IBC
   392  	params := k.GetParams(ctx)
   393  	timeoutTimestamp := uint64(ctx.BlockTime().UnixNano()) + params.IbcTimeout
   394  	timeoutHeight := ibcclienttypes.ZeroHeight()
   395  
   396  	return k.transferKeeper.SendTransfer(
   397  		ctx,
   398  		ibctransfertypes.PortID,
   399  		channelID,
   400  		sdk.NewCoinAdapter(coin.Denom, sdk.NewIntFromBigInt(coin.Amount.BigInt())),
   401  		sender,
   402  		to,
   403  		timeoutHeight,
   404  		timeoutTimestamp,
   405  	)
   406  }
   407  
   408  func (k Keeper) ibcSendTransferWithChannel(ctx sdk.Context, sender sdk.AccAddress, to string, coin sdk.Coin, portID, channelID string) error {
   409  	// Transfer coins to receiver through IBC
   410  	params := k.GetParams(ctx)
   411  	timeoutTimestamp := uint64(ctx.BlockTime().UnixNano()) + params.IbcTimeout
   412  	timeoutHeight := ibcclienttypes.ZeroHeight()
   413  
   414  	return k.transferKeeper.SendTransfer(
   415  		ctx,
   416  		portID,
   417  		channelID,
   418  		sdk.NewCoinAdapter(coin.Denom, sdk.NewIntFromBigInt(coin.Amount.BigInt())),
   419  		sender,
   420  		to,
   421  		timeoutHeight,
   422  		timeoutTimestamp,
   423  	)
   424  }