Skip to content

Commit

Permalink
Merge pull request #12 from celo-org/feat/cip64
Browse files Browse the repository at this point in the history
feat: cip64 and eip1559
  • Loading branch information
jmrossy authored Oct 5, 2023
2 parents 8fd1431 + c64f904 commit aaefd11
Show file tree
Hide file tree
Showing 8 changed files with 1,156 additions and 927 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
},
"devDependencies": {
"@types/node": "^16.11.10",
"ethers": "^5.6.8",
"ethers": "^5.7.2",
"ts-node": "^10.8.1",
"typescript": "^4.5.2"
},
Expand Down
7 changes: 7 additions & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const Y_PARITY_EIP_2098 = 27;
export const EIP155_NUMBER = 35;
// NOTE: Black magic number, unsure where it comes from
export const EIGHT = 8;

// NOTE: Logic stolen from https://github.com/celo-org/celo-monorepo/blob/e7ebc92cb0715dc56c9d7f613dca81e076541cf3/packages/sdk/connect/src/connection.ts#L382-L396
export const GAS_INFLATION_FACTOR = 1.3;
100 changes: 69 additions & 31 deletions src/lib/CeloProvider.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,10 @@
import { BigNumber, providers, utils } from "ethers";
import { getNetwork } from "./networks";
import { parseCeloTransaction } from "./transactions";
import { CeloTransactionRequest, parseCeloTransaction } from "./transactions";

const logger = new utils.Logger("CeloProvider");

export class CeloProvider extends providers.JsonRpcProvider {
constructor(
url?: utils.ConnectionInfo | string,
network?: providers.Networkish
) {
super(url, network);

// Override certain block formatting properties that don't exist on Celo blocks
// Reaches into https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/formatter.ts
const blockFormat = this.formatter.formats.block;
blockFormat.gasLimit = () => BigNumber.from(0);
blockFormat.nonce = () => "";
blockFormat.difficulty = () => 0;

const blockWithTransactionsFormat =
this.formatter.formats.blockWithTransactions;
blockWithTransactionsFormat.gasLimit = () => BigNumber.from(0);
blockWithTransactionsFormat.nonce = () => "";
blockWithTransactionsFormat.difficulty = () => 0;
}

/**
* Override to parse transaction correctly
* https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/base-provider.ts
Expand All @@ -33,17 +13,21 @@ export class CeloProvider extends providers.JsonRpcProvider {
signedTransaction: string | Promise<string>
): Promise<providers.TransactionResponse> {
await this.getNetwork();
const signedTx = await Promise.resolve(signedTransaction);
const hexTx = utils.hexlify(signedTx);
const tx = parseCeloTransaction(signedTx);
const hexTx = await Promise.resolve(signedTransaction).then((t) =>
utils.hexlify(t)
);
const tx = parseCeloTransaction(hexTx);
const blockNumber = await this._getInternalBlockNumber(
100 + 2 * this.pollingInterval
);
try {
const hash = await this.perform("sendTransaction", {
signedTransaction: hexTx,
});
return this._wrapTransaction(tx, hash);
} catch (error: any) {
error.transaction = tx;
error.transactionHash = tx.hash;
return this._wrapTransaction(tx, hash, blockNumber);
} catch (error) {
(<any>error).transaction = tx;
(<any>error).transactionHash = tx.hash;
throw error;
}
}
Expand All @@ -70,21 +54,75 @@ export class CeloProvider extends providers.JsonRpcProvider {
return ["eth_gasPrice", param];
}

if (method === "estimateGas") {
// NOTE: somehow estimategas trims lots of fields
// this overrides it
const extraneous_keys = [
["from", (x: string) => x],
["feeCurrency", utils.hexlify],
["gatewayFeeRecipient", (x: string) => x],
["gatewayFee", utils.hexlify],
] as const;

const tx = {
...providers.JsonRpcProvider.hexlifyTransaction(
params.transaction,
extraneous_keys.reduce((acc, [key]) => {
acc[key] = true;
return acc;
}, {} as Record<string, true>)
),
};
extraneous_keys.forEach(([key, fn]) => {
if (params.transaction[key]) {
tx[key] = fn(params.transaction[key]);
}
});

return ["eth_estimateGas", [tx]];
}

return super.prepareRequest(method, params);
}

static getNetwork(networkish: providers.Networkish): providers.Network {
const network = getNetwork(networkish == null ? 'celo' : networkish);
const network = getNetwork(networkish == null ? "celo" : networkish);
if (network == null) {
return logger.throwError(
`unknown network: ${JSON.stringify(network)}`,
utils.Logger.errors.UNSUPPORTED_OPERATION,
{
operation: 'getNetwork',
operation: "getNetwork",
value: networkish,
},
}
);
}
return network;
}

async estimateGas(
transaction: utils.Deferrable<CeloTransactionRequest>
): Promise<BigNumber> {
// NOTE: Overrides the ethers method to make sure feeCurrency and from are sent
// to the rpc node
await this.getNetwork();
const params = await utils.resolveProperties({
transaction,
});
const result = await this.perform("estimateGas", params);
try {
return BigNumber.from(result);
} catch (error) {
return logger.throwError(
"bad result from backend",
utils.Logger.errors.SERVER_ERROR,
{
method: "estimateGas",
params,
result,
error,
}
);
}
}
}
23 changes: 17 additions & 6 deletions src/lib/CeloWallet.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { BigNumber, providers, utils, Wallet, Wordlist } from "ethers";
import type { CeloProvider } from "./CeloProvider";
import {
CeloTransaction,
CeloTransactionRequest,
getTxType,
serializeCeloTransaction,
} from "./transactions";
import { adjustForGasInflation } from "./transaction/utils";

const logger = new utils.Logger("CeloWallet");

Expand All @@ -20,15 +23,23 @@ export class CeloWallet extends Wallet {
*/
async populateTransaction(
transaction: utils.Deferrable<CeloTransactionRequest>
): Promise<any> {
): Promise<CeloTransactionRequest> {
const tx: any = await utils.resolveProperties(transaction);

if (tx.to != null) {
tx.to = Promise.resolve(tx.to).then((to) => this.resolveName(to));
tx.to = Promise.resolve(tx.to).then((to) =>
this.resolveName(to as string)
);
}

if (tx.from == null) {
tx.from = this.address;
}
if (tx.gasPrice == null) {

const type = getTxType(tx);
if (!type && tx.gasPrice == null) {
tx.gasPrice = this.getGasPrice();
}

if (tx.nonce == null) {
tx.nonce = this.getTransactionCount("pending");
}
Expand Down Expand Up @@ -68,7 +79,7 @@ export class CeloWallet extends Wallet {
});
}

return utils.resolveProperties(tx);
return utils.resolveProperties<CeloTransactionRequest>(tx);
}

/**
Expand Down Expand Up @@ -115,7 +126,7 @@ export class CeloWallet extends Wallet {
): Promise<BigNumber> {
this._checkProvider("estimateGas");
const tx = await utils.resolveProperties(transaction);
return await this.provider.estimateGas(tx);
return this.provider.estimateGas(tx).then(adjustForGasInflation);
}

/**
Expand Down
63 changes: 63 additions & 0 deletions src/lib/transaction/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { BigNumber, BigNumberish } from "ethers";
import { hexlify } from "ethers/lib/utils";
import { GAS_INFLATION_FACTOR } from "../../consts";

function isEmpty(value: string | BigNumberish | undefined | null) {
return (
value === undefined ||
value === null ||
value === "0" ||
(typeof value == "string"
? value.toLowerCase() === "0x" || value.toLowerCase() === "0x0"
: hexlify(value) === "0x0")
);
}

function isPresent(value: string | BigNumberish | undefined | null) {
return !isEmpty(value);
}

export function isEIP1559(tx: any): boolean {
return isPresent(tx.maxFeePerGas) && isPresent(tx.maxPriorityFeePerGas);
}

export function isCIP64(tx: any) {
return (
isEIP1559(tx) &&
isPresent(tx.feeCurrency) &&
!isPresent(tx.gatewayFeeRecipient) &&
!isPresent(tx.gatewayFeeRecipient)
);
}

export function isCIP42(tx: any): boolean {
return (
isEIP1559(tx) &&
(isPresent(tx.feeCurrency) ||
isPresent(tx.gatewayFeeRecipient) ||
isPresent(tx.gatewayFee))
);
}

export function concatHex(values: string[]): `0x${string}` {
return `0x${values.reduce((acc, x) => acc + x.replace("0x", ""), "")}`;
}

export function omit<T extends Object, K extends (keyof T)[]>(
object: T,
...keys: K
): {
[Key in keyof T as Key extends K ? never : Key]: T[Key];
} {
return Object.keys(object)
.filter((key) => !keys.includes(key as keyof T))
.reduce((acc, key) => {
acc[key as keyof T] = object[key as keyof T];
return acc;
}, {} as T);
}

export function adjustForGasInflation(gas: BigNumber): BigNumber {
// NOTE: prevent floating point math
return gas.mul(Math.floor(GAS_INFLATION_FACTOR * 100)).div(100);
}
Loading

0 comments on commit aaefd11

Please sign in to comment.