Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Charge NFT contract deploy gas on MsgLiquify #50

Merged
merged 3 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions integration_tests/test_runner/src/tests/liquid_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ pub async fn liquid_accounts_test(
)
.await
.expect("Unable to liquify account");
info!("Got liquify account response:\n{}", liquify_res.raw_log);
info!(
"Got liquify account response:\n{}\nTx Hash: {}\nGas used: {}",
liquify_res.raw_log, liquify_res.txhash, liquify_res.gas_used
);

let (liquid_account, _eth_owner) =
assert_correct_account(web3, &mut microtx_qc, to_liquify.ethermint_address).await;
Expand Down Expand Up @@ -227,7 +230,7 @@ pub async fn liquify_account(
to_liquify,
Fee {
amount: vec![fee],
gas_limit: 1_000_000,
gas_limit: 2_500_000,
payer: None,
granter: None,
},
Expand Down
53 changes: 18 additions & 35 deletions x/microtx/keeper/liquid_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (k Keeper) DoLiquify(
// deployLiquidInfrastructureNFTContract deploys an NFT contract for the given `account` and then transfers ownership of the
// underlying NFT to the given `account`
func (k Keeper) deployLiquidInfrastructureNFTContract(ctx sdk.Context, account sdk.AccAddress) (common.Address, error) {
contract, err := k.DeployContract(ctx, types.LiquidInfrastructureNFT, account.String())
contract, err := k.DeployContract(ctx, account, types.LiquidInfrastructureNFT, account.String())
if err != nil {
return common.Address{}, sdkerrors.Wrap(err, "liquid infrastructure account contract deployment failed")
}
Expand All @@ -68,23 +68,9 @@ func (k Keeper) deployLiquidInfrastructureNFTContract(ctx sdk.Context, account s
return common.Address{}, sdkerrors.Wrapf(err, "expected contract with version %v, got %v", CurrentNFTVersion, version)
}

_, err = k.transferLiquidInfrastructureNFTFromModuleToAddress(ctx, contract, account)
if err != nil {
return common.Address{}, sdkerrors.Wrapf(err, "could not transfer nft from %v to %v", types.ModuleEVMAddress.Hex(), SDKToEVMAddress(account).Hex())
}

return contract, nil
}

// transferLiquidInfrastructureNFTFromModuleToAddress calls the ERC721 "transferFrom" method on `contract`,
// passing the arguments needed to transfer the LiquidInfrastructureNFT Account token
// from the x/microtx module account to to the `newOwner`
func (k Keeper) transferLiquidInfrastructureNFTFromModuleToAddress(ctx sdk.Context, contract common.Address, newOwner sdk.AccAddress) (*evmtypes.MsgEthereumTxResponse, error) {
// ABI: transferFrom( address from, address to, uint256 tokenId)
var args = ToMethodArgs(types.ModuleEVMAddress, SDKToEVMAddress(newOwner), &AccountId)
return k.CallMethod(ctx, "transferFrom", types.LiquidInfrastructureNFT, types.ModuleEVMAddress, &contract, &big.Int{}, args...)
}

// addLiquidInfrastructureEntry Sets a new Liquid Infrastructure Account entry in the bech32 -> EVM NFT address mapping
// accAddress - The account to Liquify
// nftAddress - The deployed LiquidInfrastructureNFT contract address
Expand Down Expand Up @@ -219,7 +205,6 @@ func (k Keeper) IsLiquidAccountWithValue(ctx sdk.Context, account sdk.AccAddress
// returns nil, ErrNoLiquidAccount if `nftAddress` is not a record for any Liquid Infrastructure Account
func (k Keeper) GetLiquidAccountByNFTAddress(ctx sdk.Context, nftAddress common.Address) (*types.LiquidInfrastructureAccount, error) {
var liquidAccount *types.LiquidInfrastructureAccount = nil
var err error = nil

k.IterateLiquidAccounts(
ctx,
Expand All @@ -236,10 +221,6 @@ func (k Keeper) GetLiquidAccountByNFTAddress(ctx sdk.Context, nftAddress common.
},
)

if err != nil {
return nil, sdkerrors.Wrap(err, "failed to find liquid account by NFT")
}

return liquidAccount, nil
}

Expand Down Expand Up @@ -275,7 +256,6 @@ func (k Keeper) GetLiquidAccountsByEVMOwner(ctx sdk.Context, ownerAddress common
// GetLiquidAccountByNFTAddress fetches info about a Liquid Infrastructure Account given the address of the LiquidInfrastructureNFT in the EVM
func (k Keeper) CollectLiquidAccounts(ctx sdk.Context) ([]*types.LiquidInfrastructureAccount, error) {
var liquidAccounts []*types.LiquidInfrastructureAccount
var err error = nil

k.IterateLiquidAccounts(
ctx,
Expand All @@ -289,10 +269,6 @@ func (k Keeper) CollectLiquidAccounts(ctx sdk.Context) ([]*types.LiquidInfrastru
},
)

if err != nil {
return nil, sdkerrors.Wrap(err, "unable to collect liquid accounts")
}

return liquidAccounts, nil
}

Expand Down Expand Up @@ -431,12 +407,14 @@ func ToMethodArgs(args ...interface{}) []interface{} {

// DeployContract will deploy an arbitrary smart-contract. It takes the compiled contract object as
// well as an arbitrary number of arguments which will be supplied to the contructor. All contracts deployed
// are deployed by the module account.
// are deployed by the deployer account.
func (k Keeper) DeployContract(
ctx sdk.Context,
deployer sdk.AccAddress,
contract evmtypes.CompiledContract,
args ...interface{},
) (common.Address, error) {
deployerEVM := SDKToEVMAddress(deployer)
// pack constructor arguments according to compiled contract's abi
// method name is nil in this case, we are calling the constructor
ctorArgs, err := contract.ABI.Pack("", args...)
Expand All @@ -455,23 +433,23 @@ func (k Keeper) DeployContract(
copy(data[len(contract.Bin):], ctorArgs)

// retrieve sequence number first to derive address if not by CREATE2
nonce, err := k.accountKeeper.GetSequence(ctx, types.ModuleAddress.Bytes())
nonce, err := k.accountKeeper.GetSequence(ctx, deployer)
if err != nil {
return common.Address{},
sdkerrors.Wrapf(types.ErrContractDeployment,
"EVM::DeployContract error retrieving nonce: %s", err.Error())
}

amount := big.NewInt(0)
_, err = k.CallEVM(ctx, types.ModuleEVMAddress, nil, amount, data, true)
_, err = k.CallEVM(ctx, deployerEVM, nil, amount, data, true)
if err != nil {
return common.Address{},
sdkerrors.Wrapf(types.ErrContractDeployment,
"EVM::DeployContract error deploying contract: %s", err.Error())
}

// Derive the newly created module smart contract using the module address and nonce
return crypto.CreateAddress(types.ModuleEVMAddress, nonce), nil
return crypto.CreateAddress(deployerEVM, nonce), nil
}

// CallMethod is a function to interact with a contract once it is deployed. It inputs the method name on the
Expand Down Expand Up @@ -517,12 +495,12 @@ func (k Keeper) CallEVM(
return nil, err
}

// As evmKeeper.ApplyMessage does not directly increment the gas meter, any transaction
// completed through the CSR module account will technically be 'free'. As such, we can
// set the gas limit to some arbitrarily high enough number such that every transaction
// from the module account will always go through.
// see: https://github.com/evmos/ethermint/blob/35850e620d2825327a175f46ec3e8c60af84208d/x/evm/keeper/state_transition.go#L466
gasLimit := DefaultGasLimit
cosmosLimit := ctx.GasMeter().Limit()
gasUsed := ctx.GasMeter().GasConsumed()
if cosmosLimit <= gasUsed {
return nil, sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "insufficient gas")
}
gasLimit := cosmosLimit - gasUsed

// Create the EVM msg
msg := ethtypes.NewMessage(
Expand All @@ -545,6 +523,11 @@ func (k Keeper) CallEVM(
return nil, err
}

// ApplyMessage does not consume gas, so we need to consume the gas used within the EVM
// This is completely different than the ethermint gas consumption, where an AnteHandler consumes all the gas
// the EVM Tx is allowed to use, and then refunds remaining gas after execution.
ctx.GasMeter().ConsumeGas(res.GasUsed, "EVM call")

if res.Failed() {
return nil, sdkerrors.Wrap(evmtypes.ErrVMExecution, res.VmError)
}
Expand Down
Loading