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

Deprecates Celo legacy transaction type (0) #20

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fca2aa3
docs(CONTRIBUTING): adds instructions for first-time contributors
arthurgousset Apr 16, 2024
8da85b4
chore(Nodejs): bumps required version to 18.14.2
arthurgousset Apr 16, 2024
59ee8f9
chore(dotenv): adds dotenv dependency
arthurgousset Apr 16, 2024
afdaff8
chore(test): adds `.env` example file
arthurgousset Apr 16, 2024
3227dc0
chore(test): adds dotenv and configures file path
arthurgousset Apr 16, 2024
85bd354
chore(test): adds TODOs
arthurgousset Apr 16, 2024
c9dfc81
chore(lib): adds various TODOs
arthurgousset Apr 16, 2024
69c7b83
test(CeloscanProvider): adds TODO note
arthurgousset Apr 16, 2024
ef7d602
chore(transactions): adds TODO
arthurgousset Apr 16, 2024
b6c7c24
fix(CeloscanProvider): update endpoint URL
arthurgousset Apr 17, 2024
0cde16d
nit(test): renaming Forno variable for ease of reference
arthurgousset Apr 17, 2024
46476e7
test(history): fixes tests and extends to 3 networks
arthurgousset Apr 17, 2024
2a7c6e3
test(history): small improvement to description
arthurgousset Apr 17, 2024
f2c2112
chore(history): removes TODO
arthurgousset Apr 17, 2024
1aeeab8
test(history): removes duplicate test
arthurgousset Apr 17, 2024
8faca33
fix(utils): fixes duplicate check in `isCIP64`
arthurgousset Apr 18, 2024
7179c4f
docs(CONTRIBUTING): adds USDC faucet instructions
arthurgousset Apr 18, 2024
1e7c0a5
chore(transactions): deletes legacy celo tx fields
arthurgousset Apr 18, 2024
28b107b
chore(transactions): simplifies and un-nests `getTxType`
arthurgousset Apr 18, 2024
a589019
test(transfers): changes ERC-20 transfer test
arthurgousset Apr 18, 2024
118d109
test(transfers): comments out celo-legacy test
arthurgousset Apr 18, 2024
cb488c6
test(transfers): covers deprecation changes
arthurgousset Apr 18, 2024
500a1b5
test(transfers): adds notes and modifies tests minimally
arthurgousset Apr 18, 2024
c888262
chore(transactions): Removes Celo legacy tx type
arthurgousset Apr 18, 2024
f29e450
chore(CeloWallet): adds TODO note
arthurgousset Apr 18, 2024
263f346
test(transfer): deletes `[celo-legacy]` tests
arthurgousset Apr 22, 2024
391a7f4
chore(CeloProvider): adds `getFeeData(feeCurrency)` and other
arthurgousset Apr 22, 2024
e4d8488
chore(CeloWallet): adds `getFeeData(feeCurrency)` estimation call
arthurgousset Apr 22, 2024
6f71710
chore(transaction): adds `isFeeCurrency` with refactoring TODO
arthurgousset Apr 22, 2024
fd0c0d0
chore(const): deletes duplicate tx type enum
arthurgousset Apr 22, 2024
80568be
test(transfer): adds new and re-organises existing tests
arthurgousset Apr 22, 2024
abe6bc5
test(transfer): moves tests and renames file
arthurgousset Apr 22, 2024
b6f36a8
chore: removes remaining TODOs
arthurgousset Apr 22, 2024
50bd6c3
test(transactions): nit changes expect order
arthurgousset Apr 22, 2024
9d1d30a
docs(CONTRIBUTING): updates nr of CELO and USDC needed
arthurgousset Apr 23, 2024
1f4d587
test(transactions): removes `ethCompatible` test
arthurgousset Apr 23, 2024
4f87e04
chore(common): removes unused `getTransactionByHash`
arthurgousset Apr 23, 2024
ed7dfbe
refactor(CeloWallet): simplifies Promises and uses `isPresent`/`isEmpty`
arthurgousset Apr 23, 2024
c48bd5e
refactor(transactions): simplifies `getTxType`, `isCIP64`, `isEIP1559…
arthurgousset Apr 23, 2024
8c3891d
chore: linting
arthurgousset Apr 23, 2024
4eaa529
nit: typo
arthurgousset Apr 23, 2024
d4ef506
fix(CeloProvider): calculates base fee in fee currency using `eth_gas…
arthurgousset Apr 25, 2024
d504868
chore(CeloProvider): nit removes unnecessary `!`
arthurgousset Apr 25, 2024
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
193 changes: 100 additions & 93 deletions src/lib/CeloProvider.ts
Original file line number Diff line number Diff line change
@@ -1,116 +1,123 @@
import {
JsonRpcProvider,
PerformActionRequest,
TransactionResponse,
TransactionResponseParams,
getBigInt,
resolveProperties,
toBeHex,
FeeData,
JsonRpcProvider,
PerformActionRequest,
TransactionResponse,
TransactionResponseParams,
getBigInt,
resolveProperties,
} from "ethers";
import { CeloTransactionRequest, parseCeloTransaction } from "./transactions";

export default class CeloProvider extends JsonRpcProvider {
async _perform(req: PerformActionRequest): Promise<any> {
// Legacy networks do not like the type field being passed along (which
// is fair), so we delete type if it is 0 and a non-EIP-1559 network
if (req.method === "call" || req.method === "estimateGas") {
let tx = req.transaction;
if (tx && tx.type != null && getBigInt(tx.type)) {
// If there are no EIP-1559 properties, it might be non-EIP-1559
if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
const feeData = await this.getFeeData();
if (
feeData.maxFeePerGas == null &&
feeData.maxPriorityFeePerGas == null
) {
// Network doesn't know about EIP-1559 (and hence type)
req = Object.assign({}, req, {
transaction: Object.assign({}, tx, { type: undefined }),
});
}
async _perform(req: PerformActionRequest): Promise<any> {
// Legacy networks do not like the type field being passed along (which
// is fair), so we delete type if it is 0 and a non-EIP-1559 network
if (req.method === "call" || req.method === "estimateGas") {
let tx = req.transaction;
if (tx && tx.type != null && getBigInt(tx.type)) {
// If there are no EIP-1559 properties, it might be non-EIP-1559
if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
const feeData = await this.getFeeData();
if (feeData.maxFeePerGas == null && feeData.maxPriorityFeePerGas == null) {
// Network doesn't know about EIP-1559 (and hence type)
req = Object.assign({}, req, {
transaction: Object.assign({}, tx, { type: undefined }),
});
}
}
}
}
}
}

const request = this.getRpcRequest(req);

if (request != null) {
return await this.send(request.method, request.args);
}
const request = this.getRpcRequest(req);

return super._perform(req);
}
if (request != null) {
return await this.send(request.method, request.args);
}

/**
* Override to handle alternative gas currencies
* prepareRequest in https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/json-rpc-provider.ts
*/
getRpcRequest(
req: PerformActionRequest
): null | { method: string; args: Array<any> } {
if (req.method === "getGasPrice") {
// @ts-expect-error
const param = req.feeCurrencyAddress
? // @ts-expect-error
[req.feeCurrencyAddress]
: [];
return { method: "eth_gasPrice", args: param };
return super._perform(req);
}

/**
* TODO(Arthur): gatewayFee and gatewayFeeRecipient are not supported anymore
* Override to handle alternative gas currencies
* prepareRequest in https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/json-rpc-provider.ts
*/
if (req.method === "estimateGas") {
const extraneous_keys = [
["from", (x: string) => x],
["feeCurrency", (x: string) => x],
["gatewayFeeRecipient", (x: string) => x],
["gatewayFee", toBeHex],
] as const;
getRpcRequest(req: PerformActionRequest): null | { method: string; args: Array<any> } {
if (req.method === "getGasPrice") {
// @ts-expect-error
const param = req.feeCurrencyAddress
? // @ts-expect-error
[req.feeCurrencyAddress]
: [];
return { method: "eth_gasPrice", args: param };
}

const tx = {
...this.getRpcTransaction(req.transaction),
};
extraneous_keys.forEach(([key, fn]) => {
// @ts-expect-error
if (req.transaction[key]) {
// @ts-expect-error
tx[key] = fn(req.transaction[key]);
/**
* TODO(Arthur): gatewayFee and gatewayFeeRecipient are not supported anymore
*/
if (req.method === "estimateGas") {
const extraneous_keys = [
["from", (x: string) => x],
["feeCurrency", (x: string) => x],
] as const;

const tx = {
...this.getRpcTransaction(req.transaction),
};
extraneous_keys.forEach(([key, fn]) => {
// @ts-expect-error
if (req.transaction[key]) {
// @ts-expect-error
tx[key] = fn(req.transaction[key]);
}
});

return { method: "eth_estimateGas", args: [tx] };
}
});

return { method: "eth_estimateGas", args: [tx] };
return super.getRpcRequest(req);
}

return super.getRpcRequest(req);
}
async estimateGas(_tx: CeloTransactionRequest): Promise<bigint> {
return getBigInt(
await this._perform({
method: "estimateGas",
// @ts-ignore
transaction: _tx,
}),
"%response"
);
}

async estimateGas(_tx: CeloTransactionRequest): Promise<bigint> {
return getBigInt(
await this._perform({
method: "estimateGas",
// @ts-ignore
transaction: _tx,
}),
"%response"
);
}
async getFeeData(feeCurrency?: string): Promise<FeeData> {
if (!feeCurrency) {
return super.getFeeData();
}
const maxPriorityFeePerGas = getBigInt(
await this.send("eth_maxPriorityFeePerGas", [feeCurrency])
);
// TODO(Arthur): handle errors using try-catch
const { baseFeePerGas } = (await this.getBlock("latest", false))!
arthurgousset marked this conversation as resolved.
Show resolved Hide resolved
const gasPrice = null;
const maxFeePerGas = baseFeePerGas! * 2n + maxPriorityFeePerGas;
return new FeeData(gasPrice, maxFeePerGas, maxPriorityFeePerGas)
}

async broadcastTransaction(signedTx: string): Promise<TransactionResponse> {
const { hash } = await resolveProperties({
blockNumber: this.getBlockNumber(),
hash: this._perform({
method: "broadcastTransaction",
signedTransaction: signedTx,
}),
network: this.getNetwork(),
});
async broadcastTransaction(signedTx: string): Promise<TransactionResponse> {
const { hash } = await resolveProperties({
blockNumber: this.getBlockNumber(),
hash: this._perform({
method: "broadcastTransaction",
signedTransaction: signedTx,
}),
network: this.getNetwork(),
});

const tx = parseCeloTransaction(signedTx);
if (tx.hash !== hash) {
throw new Error("@TODO: the returned hash did not match");
}
const tx = parseCeloTransaction(signedTx);
if (tx.hash !== hash) {
throw new Error("@TODO: the returned hash did not match");
}

return new TransactionResponse(tx as TransactionResponseParams, this);
}
return new TransactionResponse(tx as TransactionResponseParams, this);
}
}
24 changes: 11 additions & 13 deletions src/lib/CeloWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
Wordlist,
} from "ethers";
import CeloProvider from "./CeloProvider";
import { adjustForGasInflation } from "./transaction/utils";
import { adjustForGasInflation, isPresent } from "./transaction/utils";
import {
CeloTransaction,
CeloTransactionRequest,
getTxType,
serializeCeloTransaction,
TxTypeToPrefix,
} from "./transactions";

const forwardErrors = [
Expand All @@ -45,18 +46,7 @@ export default class CeloWallet extends Wallet {
}

const type = getTxType(tx);
/**
* TODO(Arthur): `getTxType` currently returns:
*
* 1. 123 (CIP64)
* 2. 2 (EIP1559)
* 3. "" (default)
*
* Type 0 is handled by the if statement below, but what about type 1 (access list)?
*
* Seems like we only want to support access list transactions as type 2, and not type 1.
* Probably makes sense, just a note to confirm.
*/

if (!type && tx.gasPrice == null) {
tx.gasPrice = this.getGasPrice();
}
Expand All @@ -83,6 +73,14 @@ export default class CeloWallet extends Wallet {
});
}

if (type === TxTypeToPrefix.cip64
&& (!isPresent(tx.maxPriorityFeePerGas) || !isPresent(tx.maxFeePerGas))
) {
const { maxFeePerGas, maxPriorityFeePerGas } = (await (this.provider as CeloProvider)?.getFeeData(tx.feeCurrency))!;
tx.maxFeePerGas = maxFeePerGas;
tx.maxPriorityFeePerGas = maxPriorityFeePerGas;
}

if (tx.chainId == null) {
tx.chainId = (await this.provider!.getNetwork()).chainId;
} else {
Expand Down
21 changes: 12 additions & 9 deletions src/lib/transaction/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function isEmpty(value: string | BigNumberish | undefined | null) {
return toBeHex(value) === "0x0";
}

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

Expand All @@ -20,7 +20,7 @@ export function isEIP1559(tx: any): boolean {
}

/**
* TODO(Arthur): Fix duplicate condition here.
* TODO(Arthur): Fix overlapping isCIP64 and isFeeCurrency
*/
export function isCIP64(tx: any) {
return (
Expand All @@ -32,15 +32,18 @@ export function isCIP64(tx: any) {
}

/**
* TODO(Arthur): Remove CIP42 support
* Identifies transactions that specify the `feeCurrency` field, but are not yet EIP-1559,
* because they don't specify `maxPriorityFeePerGas`, `maxFeePerGas`, and `gasLimit`.
*
* @param tx object with transaction parameters
* @returns true if `feeCurrency` field is specified, false otherwise.
*/
export function isCIP42(tx: any): boolean {
export function isFeeCurrency(tx: any): boolean {
return (
isEIP1559(tx) &&
(isPresent(tx.feeCurrency) ||
isPresent(tx.gatewayFeeRecipient) ||
isPresent(tx.gatewayFee))
);
isPresent(tx.feeCurrency) &&
!isPresent(tx.gatewayFeeRecipient) &&
!isPresent(tx.gatewayFee)
)
}

export function concatHex(values: string[]): `0x${string}` {
Expand Down
17 changes: 7 additions & 10 deletions src/lib/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,9 @@ import {
TransactionRequest,
zeroPadValue,
} from "ethers";
import { concatHex, isCIP64, isEIP1559 } from "./transaction/utils";
import { concatHex, isCIP64, isEIP1559, isFeeCurrency } from "./transaction/utils";
import { EIGHT, EIP155_NUMBER, Y_PARITY_EIP_2098 } from "../consts";

/**
* TODO(Arthur): gatewayFee and gatewayFeeRecipient are deprecated
*/
export interface CeloTransactionRequest extends TransactionRequest {
feeCurrency?: string;
}
Expand Down Expand Up @@ -76,9 +73,6 @@ export const celoAllowedTransactionKeys = {
accessList: true,
} as const;

/**
* TODO(Arthur): gatewayFee and gatewayFeeRecipient are not supported anymore
*/
type CeloFieldName =
| keyof Omit<
CeloTransactionRequest,
Expand All @@ -97,9 +91,6 @@ type CeloFieldName =
>
| "feeCurrency";

/**
* TODO(Arthur): gatewayFee and gatewayFeeRecipient are not supported anymore
*/
export const celoTransactionFields: Record<CeloFieldName, Field> = {
nonce: { maxLength: 32, numeric: true } as Field,
gasPrice: { maxLength: 32, numeric: true } as Field,
Expand Down Expand Up @@ -171,6 +162,12 @@ export function getTxType(tx: CeloTransaction) {
if (isCIP64(tx)) {
return TxTypeToPrefix.cip64;
}
/**
* TODO(Arthur): Refactor isCIP64 and isFeeCurrency
*/
if (isFeeCurrency(tx)) {
return TxTypeToPrefix.cip64;
}
if (isEIP1559(tx)) {
// @ts-ignore
delete tx.feeCurrency;
Expand Down
7 changes: 0 additions & 7 deletions tests/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ export const CELO_DERIVATION_PATH = "m/44'/52752'/0'/0/0";
export const HELLO_WORLD_ADDRESS = "0x21F637331830096a1fc6115c3dB9382fef4343Bf";
export const CUSD_ADDRESS = "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1";

export enum TxTypeNumber {
CIP64 = 123,
EIP1559 = 2,
ACCESS_LIST = 1,
ETHEREUM_LEGACY = 0,
}
aaronmgdr marked this conversation as resolved.
Show resolved Hide resolved

// Source: https://www.circle.com/en/multi-chain-usdc/celo
export const USDC_ALFAJORES_ADDRESS = "0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B";
// Source: https://docs.celo.org/protocol/transaction/erc20-transaction-fees#adapters-by-network
Expand Down
Loading