Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 14 additions & 1 deletion e2e_test/debug-fee-currency/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ set -xeo pipefail
# $3: highGasOnCredit (bool)
# if true, this will make the DebugFeeCurrenc.CreditFees() call use
# a high amount of gas
# $4: intrinsicGas (num):
# intrinsic gas set for the fee currency. If it's not set, it will
# default to 60000.
# returns:
# deployed fee-currency address
function deploy_fee_currency() {
(
DEFAULT_INTRINSIC_GAS=60000
local fee_currency=$(
forge create --root "$SCRIPT_DIR/debug-fee-currency" --contracts "$SCRIPT_DIR/debug-fee-currency" --private-key $ACC_PRIVKEY DebugFeeCurrency.sol:DebugFeeCurrency --constructor-args '100000000000000000000000000' $1 $2 $3 --json | jq .deployedTo -r
)
Expand All @@ -22,7 +26,7 @@ function deploy_fee_currency() {
fi
# this always resets the token address for the predeployed oracle3
cast send --private-key $ACC_PRIVKEY $ORACLE3 'setExchangeRate(address, uint256, uint256)' $fee_currency 2ether 1ether > /dev/null
cast send --private-key $ACC_PRIVKEY $FEE_CURRENCY_DIRECTORY_ADDR 'setCurrencyConfig(address, address, uint256)' $fee_currency $ORACLE3 60000 > /dev/null
cast send --private-key $ACC_PRIVKEY $FEE_CURRENCY_DIRECTORY_ADDR 'setCurrencyConfig(address, address, uint256)' $fee_currency $ORACLE3 ${4:-$DEFAULT_INTRINSIC_GAS} > /dev/null
echo "$fee_currency"
)
}
Expand Down Expand Up @@ -76,3 +80,12 @@ function assert_cip_64_tx() {
fi
echo "$value" | jq .error | grep -qE "$expected_error"
}

# args:
# $1: value (num):
# value to send in the transaction
# $2: feeCurrencyAddress (string):
# which fee-currency address to use for the default CIP-64 transaction
function estimate_tx() {
$SCRIPT_DIR/js-tests/estimate_tx.mjs "$(cast chain-id)" $1 $2
}
21 changes: 21 additions & 0 deletions e2e_test/js-tests/estimate_tx.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node
import { publicClient, account } from "./viem_setup.mjs"

const [chainId, celoValue, feeCurrency] = process.argv.slice(2);

async function main() {
let bigCeloValue = BigInt(celoValue)

let result = await publicClient.estimateGas({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: bigCeloValue,
feeCurrency
});
console.log(result.toString())

return result;
}

await main();
process.exit(0);
14 changes: 13 additions & 1 deletion e2e_test/js-tests/test_ethers_tx.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { assert } from "chai";
import "mocha";
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider(process.env.ETH_RPC_URL);
let provider
if (process.env.ETH_RPC_URL.startsWith("ws")) {
provider = new ethers.WebSocketProvider(process.env.ETH_RPC_URL);
} else {
provider = new ethers.JsonRpcProvider(process.env.ETH_RPC_URL);
}
const signer = new ethers.Wallet(process.env.ACC_PRIVKEY, provider);

describe("ethers.js send tx", () => {
Expand Down Expand Up @@ -50,3 +55,10 @@ describe("ethers.js compatibility tests with state", () => {
assert.isTrue(fullBlock.hasOwnProperty("baseFeePerGas"));
});
});

// Close the WebSocket connection after all tests
after(async () => {
if (provider instanceof ethers.WebSocketProvider) {
await provider.destroy(); // Close the WebSocket connection
}
});
19 changes: 17 additions & 2 deletions e2e_test/js-tests/viem_setup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createPublicClient,
createWalletClient,
http,
webSocket,
defineChain,
} from "viem";
import { celo, celoAlfajores } from "viem/chains";
Expand All @@ -17,6 +18,7 @@ const devChain = defineChain({
rpcUrls: {
default: {
http: [process.env.ETH_RPC_URL],
webSocket: [process.env.ETH_RPC_URL],
},
},
});
Expand All @@ -28,6 +30,7 @@ const celoBaklava = defineChain({
rpcUrls: {
default: {
http: [process.env.ETH_RPC_URL],
webSocket: [process.env.ETH_RPC_URL],
},
},
});
Expand All @@ -37,6 +40,7 @@ const celoMainnet = defineChain({
rpcUrls: {
default: {
http: [process.env.ETH_RPC_URL],
webSocket: [process.env.ETH_RPC_URL],
},
},
});
Expand All @@ -54,14 +58,25 @@ const chain = (() => {
};
})();

const transportForNetwork = (() => {
switch (process.env.NETWORK) {
case 'alfajores':
case 'baklava':
case 'mainnet':
return webSocket(process.env.ETH_RPC_URL);
default:
return http(process.env.ETH_RPC_URL);
};
})

// Set up clients/wallet
export const publicClient = createPublicClient({
chain: chain,
transport: http(),
transport: transportForNetwork(),
});
export const account = privateKeyToAccount(process.env.ACC_PRIVKEY);
export const walletClient = createWalletClient({
account,
chain: chain,
transport: http(),
transport: transportForNetwork(),
});
2 changes: 1 addition & 1 deletion e2e_test/run_all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ for f in test_*"$TEST_GLOB"*; do
if [[ -n $NETWORK ]]; then
case $f in
# Skip tests that require a local network.
test_fee_currency_fails_on_credit.sh|test_fee_currency_fails_on_debit.sh|test_fee_currency_fails_intrinsic.sh|test_value_and_fee_currency_balance_check.sh)
test_fee_currency_fails_on_credit.sh|test_fee_currency_fails_on_debit.sh|test_fee_currency_fails_intrinsic.sh|test_value_and_fee_currency_balance_check.sh|test_fee_currency_gas_estimation.sh)
echo "skipping file $f"
continue
;;
Expand Down
6 changes: 3 additions & 3 deletions e2e_test/shared.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ case $NETWORK in
# cast call 0x000000000000000000000000000000000000ce10 "getAddressForStringOrDie(string calldata identifier) returns (address)" $contract
# end
mainnet)
export ETH_RPC_URL=https://forno.celo.org
export ETH_RPC_URL=wss://forno.celo.org/ws
export TOKEN_ADDR=0x471EcE3750Da237f93B8E339c536989b8978a438
export FEE_HANDLER=0xcD437749E43A154C07F3553504c68fBfD56B8778
export FEE_CURRENCY=0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73
export FEE_CURRENCY_DIRECTORY_ADDR=0x15F344b9E6c3Cb6F0376A36A64928b13F62C6276
echo "Using mainnet network"
;;
alfajores)
export ETH_RPC_URL=https://alfajores-forno.celo-testnet.org
export ETH_RPC_URL=wss://alfajores-forno.celo-testnet.org/ws
export TOKEN_ADDR=0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9
export FEE_HANDLER=0xEAaFf71AB67B5d0eF34ba62Ea06Ac3d3E2dAAA38
export FEE_CURRENCY=0x4822e58de6f5e485eF90df51C41CE01721331dC0
export FEE_CURRENCY_DIRECTORY_ADDR=0x9212Fb72ae65367A7c887eC4Ad9bE310BAC611BF
echo "Using Alfajores network"
;;
baklava)
export ETH_RPC_URL=https://baklava-forno.celo-testnet.org
export ETH_RPC_URL=wss://baklava-forno.celo-testnet.org/ws
export TOKEN_ADDR=0xdDc9bE57f553fe75752D61606B94CBD7e0264eF8
export FEE_HANDLER=0xeed0A69c51079114C280f7b936C79e24bD94013e
export FEE_CURRENCY=0x62492A644A588FD904270BeD06ad52B9abfEA1aE
Expand Down
17 changes: 17 additions & 0 deletions e2e_test/test_fee_currency_gas_estimation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash
#shellcheck disable=SC2086
set -eo pipefail

source shared.sh
source debug-fee-currency/lib.sh

fee_currency=$(deploy_fee_currency false false false 70000)
gas=$(estimate_tx 20 $fee_currency)

cleanup_fee_currency $fee_currency

# intrinsic of fee_currency: 70000
# intrinsic of tx: 21000
# total: 91000
if [ $gas -ne 91000 ]; then exit 1; fi

51 changes: 37 additions & 14 deletions eth/gasestimator/gasestimator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/contracts"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -52,15 +53,25 @@ type Options struct {
// Estimate returns the lowest possible gas limit that allows the transaction to
// run successfully with the provided context options. It returns an error if the
// transaction would always revert, or if there are unexpected failures.
func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64, exchangeRates common.ExchangeRates, feeCurrencyBalance *big.Int) (uint64, []byte, error) {
func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64) (uint64, []byte, error) {
// Celo specific: get balance of fee currency if fee currency is specified
feeCurrencyBalance := new(big.Int)
if call.FeeCurrency != nil {
feeCurrencyBalance = getFeeBalance(call, opts)
}

feeCurrencyContext := core.GetFeeCurrencyContext(opts.Header, opts.Config, opts.State)
// currency intrinsic gas for celo = 0
extraIntrinsicGas, _ := common.CurrencyIntrinsicGasCost(feeCurrencyContext.IntrinsicGasCosts, call.FeeCurrency)

// Binary search the gas limit, as it may need to be higher than the amount used
var (
lo uint64 // lowest-known gas limit where tx execution fails
hi uint64 // lowest-known gas limit where tx execution succeeds
)
// Determine the highest gas limit can be used during the estimation.
hi = opts.Header.GasLimit
if call.GasLimit >= params.TxGas {
if call.GasLimit >= (params.TxGas + extraIntrinsicGas) {
hi = call.GasLimit
}
// Normalize the max fee per gas the call is willing to spend.
Expand All @@ -81,7 +92,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
// CIP-66, prices are given in native token.
// We need to check the allowance in the converted feeCurrency
var err error
feeCap, err = exchange.ConvertCeloToCurrency(exchangeRates, call.FeeCurrency, feeCap)
feeCap, err = exchange.ConvertCeloToCurrency(feeCurrencyContext.ExchangeRates, call.FeeCurrency, feeCap)
if err != nil {
return 0, nil, err
}
Expand Down Expand Up @@ -140,15 +151,20 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
// unused access list items). Ever so slightly wasteful, but safer overall.
if len(call.Data) == 0 {
if call.To != nil && opts.State.GetCodeSize(*call.To) == 0 {
failed, _, err := execute(ctx, call, opts, params.TxGas)
gasLimit := params.TxGas
if call.FeeCurrency != nil {
// if the feeCurrency is not supported, the returned extraIntrinsicGas is 0, which will end up failing before the balance check
gasLimit += extraIntrinsicGas
}
failed, _, err := execute(ctx, call, opts, gasLimit, feeCurrencyContext)
if !failed && err == nil {
return params.TxGas, nil, nil
return gasLimit, nil, nil
}
}
}
// We first execute the transaction at the highest allowable gas limit, since if this fails we
// can return error immediately.
failed, result, err := execute(ctx, call, opts, hi)
failed, result, err := execute(ctx, call, opts, hi, feeCurrencyContext)
if err != nil {
return 0, nil, err
}
Expand All @@ -170,7 +186,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
// check that gas amount and use as a limit for the binary search.
optimisticGasLimit := (result.UsedGas + result.RefundedGas + params.CallStipend) * 64 / 63
if optimisticGasLimit < hi {
failed, _, err = execute(ctx, call, opts, optimisticGasLimit)
failed, _, err = execute(ctx, call, opts, optimisticGasLimit, feeCurrencyContext)
if err != nil {
// This should not happen under normal conditions since if we make it this far the
// transaction had run without error at least once before.
Expand Down Expand Up @@ -201,7 +217,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
// range here is skewed to favor the low side.
mid = lo * 2
}
failed, _, err = execute(ctx, call, opts, mid)
failed, _, err = execute(ctx, call, opts, mid, feeCurrencyContext)
if err != nil {
// This should not happen under normal conditions since if we make it this far the
// transaction had run without error at least once before.
Expand All @@ -221,14 +237,14 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
// returns true if the transaction fails for a reason that might be related to
// not enough gas. A non-nil error means execution failed due to reasons unrelated
// to the gas limit.
func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit uint64) (bool, *core.ExecutionResult, error) {
func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit uint64, feeCurrencyContext *common.FeeCurrencyContext) (bool, *core.ExecutionResult, error) {
// Configure the call for this specific execution (and revert the change after)
defer func(gas uint64) { call.GasLimit = gas }(call.GasLimit)
call.GasLimit = gasLimit

// Execute the call and separate execution faults caused by a lack of gas or
// other non-fixable conditions
result, err := run(ctx, call, opts)
result, err := run(ctx, call, opts, feeCurrencyContext)
if err != nil {
if errors.Is(err, core.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
Expand All @@ -240,12 +256,11 @@ func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit ui

// run assembles the EVM as defined by the consensus rules and runs the requested
// call invocation.
func run(ctx context.Context, call *core.Message, opts *Options) (*core.ExecutionResult, error) {
func run(ctx context.Context, call *core.Message, opts *Options, feeCurrencyContext *common.FeeCurrencyContext) (*core.ExecutionResult, error) {
// Assemble the call and the call context
var (
feeCurrencyContext = core.GetFeeCurrencyContext(opts.Header, opts.Config, opts.State)
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil, opts.Config, opts.State, feeCurrencyContext)
dirtyState = opts.State.Copy()
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil, opts.Config, opts.State, feeCurrencyContext)
dirtyState = opts.State.Copy()
)
if opts.BlockOverrides != nil {
opts.BlockOverrides.Apply(&evmContext)
Expand Down Expand Up @@ -280,3 +295,11 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
}
return result, nil
}

func getFeeBalance(call *core.Message, opts *Options) *big.Int {
cb := &contracts.CeloBackend{
ChainConfig: opts.Config,
State: opts.State,
}
return contracts.GetFeeBalance(cb, call.From, call.FeeCurrency)
}
3 changes: 3 additions & 0 deletions ethclient/ethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,9 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.BlobHashes != nil {
arg["blobVersionedHashes"] = msg.BlobHashes
}
if msg.FeeCurrency != nil {
arg["feeCurrency"] = msg.FeeCurrency
}
return arg
}

Expand Down
3 changes: 3 additions & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ type CallMsg struct {
// For BlobTxType
BlobGasFeeCap *big.Int
BlobHashes []common.Hash

// For CeloDynamicFeeTxType
FeeCurrency *common.Address
}

// A ContractCaller provides contract calls, essentially transactions that are executed by
Expand Down
11 changes: 1 addition & 10 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -963,17 +963,8 @@ func DoEstimateGas(ctx context.Context, b CeloBackend, args TransactionArgs, blo

call := args.ToMessage(header.BaseFee, true, true, exchangeRates)

// Celo specific: get balance of fee currency if fee currency is specified
feeCurrencyBalance := new(big.Int)
if args.FeeCurrency != nil {
feeCurrencyBalance, err = b.GetFeeBalance(ctx, blockNrOrHash, call.From, args.FeeCurrency)
if err != nil {
return 0, err
}
}

// Run the gas estimation and wrap any revertals into a custom return
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap, exchangeRates, feeCurrencyBalance)
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
if err != nil {
if len(revert) > 0 {
return 0, newRevertError(revert)
Expand Down
Loading