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: ðTxHash, 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 }