Skip to content

Commit

Permalink
Initial EIP-1559 support (#1610).
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo committed May 30, 2021
1 parent d395d16 commit 7a12216
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 26 deletions.
5 changes: 5 additions & 0 deletions packages/abstract-provider/src.ts/index.ts
Expand Up @@ -29,6 +29,9 @@ export type TransactionRequest = {

type?: number;
accessList?: AccessListish;

maxPriorityFeePerGas?: BigNumberish;
maxFeePerGas?: BigNumberish;
}

export interface TransactionResponse extends Transaction {
Expand Down Expand Up @@ -67,6 +70,8 @@ interface _Block {

miner: string;
extraData: string;

baseFee?: null | BigNumber;
}

export interface Block extends _Block {
Expand Down
120 changes: 117 additions & 3 deletions packages/abstract-signer/src.ts/index.ts
Expand Up @@ -10,7 +10,7 @@ import { version } from "./_version";
const logger = new Logger(version);

const allowedTransactionKeys: Array<string> = [
"accessList", "chainId", "data", "from", "gasLimit", "gasPrice", "nonce", "to", "type", "value"
"accessList", "chainId", "data", "from", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "to", "type", "value"
];

const forwardErrors = [
Expand All @@ -19,6 +19,12 @@ const forwardErrors = [
Logger.errors.REPLACEMENT_UNDERPRICED,
];

export interface FeeData {
maxFeePerGas: null | BigNumber;
maxPriorityFeePerGas: null | BigNumber;
gasPrice: null | BigNumber;
}

// EIP-712 Typed Data
// See: https://eips.ethereum.org/EIPS/eip-712

Expand Down Expand Up @@ -139,14 +145,32 @@ export abstract class Signer {
return await this.provider.getGasPrice();
}

async getFeeData(): Promise<FeeData> {
this._checkProvider("getFeeStats");

const { block, gasPrice } = await resolveProperties({
block: this.provider.getBlock(-1),
gasPrice: this.provider.getGasPrice()
});

let maxFeePerGas = null, maxPriorityFeePerGas = null;

if (block && block.baseFee) {
maxFeePerGas = block.baseFee.mul(2);
maxPriorityFeePerGas = BigNumber.from("1000000000");
}

return { maxFeePerGas, maxPriorityFeePerGas, gasPrice };
}


async resolveName(name: string): Promise<string> {
this._checkProvider("resolveName");
return await this.provider.resolveName(name);
}




// Checks a transaction does not contain invalid keys and if
// no "from" is provided, populates it.
// - does NOT require a provider
Expand All @@ -167,6 +191,7 @@ export abstract class Signer {

if (tx.from == null) {
tx.from = this.getAddress();

} else {
// Make sure any provided address matches this signer
tx.from = Promise.all([
Expand All @@ -187,6 +212,9 @@ export abstract class Signer {
// this Signer. Should be used by sendTransaction but NOT by signTransaction.
// By default called from: (overriding these prevents it)
// - sendTransaction
//
// Notes:
// - We allow gasPrice for EIP-1559 as long as it matches maxFeePerGas
async populateTransaction(transaction: Deferrable<TransactionRequest>): Promise<TransactionRequest> {

const tx: Deferrable<TransactionRequest> = await resolveProperties(this.checkTransaction(transaction))
Expand All @@ -201,7 +229,93 @@ export abstract class Signer {
return address;
});
}
if (tx.gasPrice == null) { tx.gasPrice = this.getGasPrice(); }

if ((tx.type === 2 || tx.type == null) && (tx.maxFeePerGas != null && tx.maxPriorityFeePerGas != null)) {
// Fully-formed EIP-1559 transaction

// Check the gasPrice == maxFeePerGas
if (tx.gasPrice != null && !BigNumber.from(tx.gasPrice).eq(<BigNumberish>(tx.maxFeePerGas))) {
logger.throwArgumentError("gasPrice/maxFeePerGas mismatch", "transaction", transaction);
}

tx.type = 2;

} else if (tx.type === -1 || tx.type === 1) {
// Explicit EIP-2930 or Legacy transaction

// Do not allow EIP-1559 properties
if (tx.maxFeePerGas != null || tx.maxPriorityFeePerGas != null) {
logger.throwArgumentError(`transaction type ${ tx.type } does not support eip-1559 keys`, "transaction", transaction);
}

if (tx.gasPrice == null) { tx.gasPrice = this.getGasPrice(); }
tx.type = (tx.accessList ? 1: -1);

} else {

// We need to get fee data to determine things
const feeData = await this.getFeeData();

if (tx.type == null) {
// We need to auto-detect the intended type of this transaction...

if (feeData.maxFeePerGas != null && feeData.maxPriorityFeePerGas != null) {
// The network supports EIP-1559!

if (tx.gasPrice != null && tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
// Legacy or EIP-2930 transaction, but without its type set
tx.type = (tx.accessList ? 1: -1);

} else {
// Use EIP-1559; no gas price or one EIP-1559 property was specified

// Check that gasPrice == maxFeePerGas
if (tx.gasPrice != null) {
// The first condition fails only if gasPrice and maxPriorityFeePerGas
// were specified, which is a weird thing to do
if (tx.maxFeePerGas == null || !BigNumber.from(tx.gasPrice).eq(<BigNumberish>(tx.maxFeePerGas))) {
logger.throwArgumentError("gasPrice/maxFeePerGas mismatch", "transaction", transaction);
}
}

tx.type = 2;
if (tx.maxFeePerGas == null) { tx.maxFeePerGas = feeData.maxFeePerGas; }
if (tx.maxPriorityFeePerGas == null) { tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas; }
}

} else if (feeData.gasPrice != null) {
// Network doesn't support EIP-1559...

// ...but they are trying to use EIP-1559 properties
if (tx.maxFeePerGas != null || tx.maxPriorityFeePerGas != null) {
logger.throwError("network does not support EIP-1559", Logger.errors.UNSUPPORTED_OPERATION, {
operation: "populateTransaction"
});
}

tx.gasPrice = feeData.gasPrice;
tx.type = (tx.accessList ? 1: -1);

} else {
// getFeeData has failed us.
logger.throwError("failed to get consistent fee data", Logger.errors.UNSUPPORTED_OPERATION, {
operation: "signer.getFeeData"
});
}

} else if (tx.type === 2) {
// Explicitly using EIP-1559

// Check gasPrice == maxFeePerGas
if (tx.gasPrice != null && !BigNumber.from(tx.gasPrice).eq(<BigNumberish>(tx.maxFeePerGas))) {
logger.throwArgumentError("gasPrice/maxFeePerGas mismatch", "transaction", transaction);
}

if (tx.maxFeePerGas == null) { tx.maxFeePerGas = feeData.maxFeePerGas; }
if (tx.maxPriorityFeePerGas == null) { tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas; }
}
}

if (tx.nonce == null) { tx.nonce = this.getTransactionCount("pending"); }

if (tx.gasLimit == null) {
Expand Down
3 changes: 2 additions & 1 deletion packages/ethers/src.ts/utils.ts
Expand Up @@ -17,7 +17,7 @@ import { checkProperties, deepCopy, defineReadOnly, getStatic, resolveProperties
import * as RLP from "@ethersproject/rlp";
import { computePublicKey, recoverPublicKey, SigningKey } from "@ethersproject/signing-key";
import { formatBytes32String, nameprep, parseBytes32String, _toEscapedUtf8String, toUtf8Bytes, toUtf8CodePoints, toUtf8String, Utf8ErrorFuncs } from "@ethersproject/strings";
import { accessListify, computeAddress, parse as parseTransaction, recoverAddress, serialize as serializeTransaction } from "@ethersproject/transactions";
import { accessListify, computeAddress, parse as parseTransaction, recoverAddress, serialize as serializeTransaction, TransactionTypes } from "@ethersproject/transactions";
import { commify, formatEther, parseEther, formatUnits, parseUnits } from "@ethersproject/units";
import { verifyMessage, verifyTypedData } from "@ethersproject/wallet";
import { _fetchData, fetchJson, poll } from "@ethersproject/web";
Expand Down Expand Up @@ -153,6 +153,7 @@ export {
accessListify,
parseTransaction,
serializeTransaction,
TransactionTypes,

getJsonWalletAddress,

Expand Down
17 changes: 16 additions & 1 deletion packages/providers/src.ts/formatter.ts
Expand Up @@ -62,7 +62,12 @@ export class Formatter {

from: address,

gasPrice: bigNumber,
// either (gasPrice) or (maxPriorityFeePerGas + maxFeePerGas)
// must be set
gasPrice: Formatter.allowNull(bigNumber),
maxPriorityFeePerGas: Formatter.allowNull(bigNumber),
maxFeePerGas: Formatter.allowNull(bigNumber),

gasLimit: bigNumber,
to: Formatter.allowNull(address, null),
value: bigNumber,
Expand All @@ -83,6 +88,8 @@ export class Formatter {
nonce: Formatter.allowNull(number),
gasLimit: Formatter.allowNull(bigNumber),
gasPrice: Formatter.allowNull(bigNumber),
maxPriorityFeePerGas: Formatter.allowNull(bigNumber),
maxFeePerGas: Formatter.allowNull(bigNumber),
to: Formatter.allowNull(address),
value: Formatter.allowNull(bigNumber),
data: Formatter.allowNull(strictData),
Expand Down Expand Up @@ -135,6 +142,8 @@ export class Formatter {
extraData: data,

transactions: Formatter.allowNull(Formatter.arrayOf(hash)),

baseFee: Formatter.allowNull(bigNumber)
};

formats.blockWithTransactions = shallowCopy(formats.block);
Expand Down Expand Up @@ -323,6 +332,12 @@ export class Formatter {

const result: TransactionResponse = Formatter.check(this.formats.transaction, transaction);

if (result.type === 2) {
if (result.gasPrice == null) {
result.gasPrice = result.maxFeePerGas;
}
}

if (transaction.chainId != null) {
let chainId = transaction.chainId;

Expand Down
2 changes: 1 addition & 1 deletion packages/providers/src.ts/json-rpc-provider.ts
Expand Up @@ -592,7 +592,7 @@ export class JsonRpcProvider extends BaseProvider {
const result: { [key: string]: string | AccessList } = {};

// Some nodes (INFURA ropsten; INFURA mainnet is fine) do not like leading zeros.
["gasLimit", "gasPrice", "type", "nonce", "value"].forEach(function(key) {
["gasLimit", "gasPrice", "type", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "value"].forEach(function(key) {
if ((<any>transaction)[key] == null) { return; }
const value = hexValue((<any>transaction)[key]);
if (key === "gasLimit") { key = "gas"; }
Expand Down

0 comments on commit 7a12216

Please sign in to comment.