2022-08-23 17:04:40 +00:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
|
|
"github.com/cosmos/cosmos-sdk/x/auth/signing"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
2023-04-04 00:08:45 +00:00
|
|
|
"github.com/evmos/ethermint/crypto/hd"
|
|
|
|
"github.com/evmos/ethermint/server/config"
|
|
|
|
etherminttypes "github.com/evmos/ethermint/types"
|
|
|
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
|
|
|
feemarkettypes "github.com/evmos/ethermint/x/feemarket/types"
|
2022-08-23 17:04:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// CanSignEthTx returns an error if the signing key algorithm is not eth_secp256k1.
|
|
|
|
func CanSignEthTx(ctx client.Context) error {
|
|
|
|
keyInfo, err := ctx.Keyring.KeyByAddress(ctx.FromAddress)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-04-04 00:08:45 +00:00
|
|
|
pubKey, err := keyInfo.GetPubKey()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if pubKey.Type() != string(hd.EthSecp256k1Type) {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"invalid from address pubkey type, expected %s but got %s",
|
|
|
|
hd.EthSecp256k1Type,
|
|
|
|
pubKey.Type(),
|
|
|
|
)
|
2022-08-23 17:04:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PackContractCallData creates a smart contract method call data with the
|
|
|
|
// provided method and args.
|
|
|
|
func PackContractCallData(abi abi.ABI, method string, args ...interface{}) ([]byte, error) {
|
|
|
|
data, err := abi.Pack(method, args...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create transaction data: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateEthCallContractTx creates and signs a Eth transaction wrapped in a
|
|
|
|
// cosmos Tx.
|
|
|
|
func CreateEthCallContractTx(
|
|
|
|
ctx client.Context,
|
|
|
|
contractAddr *common.Address,
|
|
|
|
data []byte,
|
|
|
|
) (signing.Tx, error) {
|
|
|
|
evmQueryClient := evmtypes.NewQueryClient(ctx)
|
|
|
|
feemarketQueryClient := feemarkettypes.NewQueryClient(ctx)
|
|
|
|
|
|
|
|
chainID, err := etherminttypes.ParseChainID(ctx.ChainID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to parse chain ID: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
evmParamsRes, err := evmQueryClient.Params(context.Background(), &evmtypes.QueryParamsRequest{})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to fetch evm params: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Estimate Gas
|
|
|
|
from := common.BytesToAddress(ctx.FromAddress.Bytes())
|
|
|
|
transactionArgs := evmtypes.TransactionArgs{
|
|
|
|
From: &from,
|
|
|
|
To: contractAddr,
|
|
|
|
Data: (*hexutil.Bytes)(&data),
|
|
|
|
}
|
|
|
|
|
|
|
|
args, err := json.Marshal(transactionArgs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to marshal transaction args for gas estimate: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := evmQueryClient.EstimateGas(context.Background(), &evmtypes.EthCallRequest{
|
|
|
|
Args: args,
|
|
|
|
GasCap: config.DefaultGasCap,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to estimate gas from EVM: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch base fee
|
|
|
|
basefeeRes, err := feemarketQueryClient.BaseFee(
|
|
|
|
context.Background(),
|
|
|
|
&feemarkettypes.QueryBaseFeeRequest{},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to fetch basefee from feemarket: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch account nonce, ignore error to use use 0 nonce if first tx
|
|
|
|
_, accSeq, _ := ctx.AccountRetriever.GetAccountNumberSequence(ctx, ctx.FromAddress)
|
|
|
|
|
|
|
|
// Create MsgEthereumTx
|
|
|
|
ethTx := evmtypes.NewTx(
|
|
|
|
chainID,
|
|
|
|
accSeq, // nonce
|
|
|
|
contractAddr, // to
|
|
|
|
nil, // amount
|
|
|
|
res.Gas, // gasLimit
|
|
|
|
nil, // gasPrice
|
|
|
|
basefeeRes.BaseFee.BigInt(), // gasFeeCap
|
|
|
|
big.NewInt(1), // gasTipCap
|
|
|
|
data, // input
|
|
|
|
ðtypes.AccessList{},
|
|
|
|
)
|
|
|
|
|
|
|
|
// Must set from address before signing
|
|
|
|
ethTx.From = from.String()
|
|
|
|
|
|
|
|
// Sign Ethereum TX (not the cosmos Msg)
|
|
|
|
signer := ethtypes.LatestSignerForChainID(chainID)
|
|
|
|
|
|
|
|
// Must sign with a `/ethermint.crypto.v1.ethsecp256k1.PubKey` and not
|
|
|
|
// `/cosmos.crypto.secp256k1.PubKey` or this will panic with the following:
|
|
|
|
// panic: wrong size for signature: got 64, want 65
|
|
|
|
if err := ethTx.Sign(signer, ctx.Keyring); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ethTx.BuildTx(ctx.TxConfig.NewTxBuilder(), evmParamsRes.Params.EvmDenom)
|
|
|
|
}
|