Skip to content

Commit

Permalink
feat!: breaking change of percent to multiplier for feeOverrides + dr…
Browse files Browse the repository at this point in the history
…opAndReplace (alchemyplatform#461)
  • Loading branch information
rthomare authored Feb 15, 2024
1 parent 6fd817f commit 10a1b85
Show file tree
Hide file tree
Showing 22 changed files with 160 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ const givenConnectedProvider = async ({
opts: {
feeOptions: {
...feeOptions,
maxFeePerGas: { percentage: 50 },
maxPriorityFeePerGas: { percentage: 50 },
maxFeePerGas: { multiplier: 1.5 },
maxPriorityFeePerGas: { multiplier: 1.5 },
},
txMaxRetries: 100,
},
Expand Down
10 changes: 5 additions & 5 deletions packages/alchemy/e2e-tests/light-account.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ describe("Light Account Tests", () => {
},
opts: {
feeOptions: {
maxFeePerGas: { percentage: 50 },
maxPriorityFeePerGas: { percentage: 50 },
preVerificationGas: { percentage: 50 },
maxFeePerGas: { multiplier: 1.5 },
maxPriorityFeePerGas: { multiplier: 1.5 },
preVerificationGas: { multiplier: 1.5 },
},
},
});
Expand Down Expand Up @@ -320,8 +320,8 @@ const givenConnectedProvider = async ({
txMaxRetries: 10,
...opts,
feeOptions: {
maxFeePerGas: { percentage: 50 },
maxPriorityFeePerGas: { percentage: 50 },
maxFeePerGas: { multiplier: 1.5 },
maxPriorityFeePerGas: { multiplier: 1.5 },
...opts?.feeOptions,
},
},
Expand Down
6 changes: 3 additions & 3 deletions packages/alchemy/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export const getDefaultUserOperationFeeOptions = (
chain: Chain
): UserOperationFeeOptions => {
const feeOptions: UserOperationFeeOptions = {
maxFeePerGas: { percentage: 50 },
maxPriorityFeePerGas: { percentage: 5 },
maxFeePerGas: { multiplier: 1.5 },
maxPriorityFeePerGas: { multiplier: 1.05 },
};

if (
Expand All @@ -27,7 +27,7 @@ export const getDefaultUserOperationFeeOptions = (
optimismSepolia.id,
]).has(chain.id)
) {
feeOptions.preVerificationGas = { percentage: 5 };
feeOptions.preVerificationGas = { multiplier: 1.05 };
}

return feeOptions;
Expand Down
28 changes: 13 additions & 15 deletions packages/alchemy/src/middleware/gasManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {
defaultGasEstimator,
filterUndefined,
isBigNumberish,
isPercentage,
isMultiplier,
resolveProperties,
type Hex,
type Percentage,
type Multiplier,
type UserOperationFeeOptions,
type UserOperationRequest,
} from "@alchemy/aa-core";
Expand All @@ -19,11 +19,11 @@ import type { ClientWithAlchemyMethods } from "../client/types";
import { alchemyFeeEstimator } from "./feeEstimator.js";

export type RequestGasAndPaymasterAndDataOverrides = Partial<{
maxFeePerGas: UserOperationRequest["maxFeePerGas"] | Percentage;
maxPriorityFeePerGas: UserOperationRequest["maxFeePerGas"] | Percentage;
callGasLimit: UserOperationRequest["maxFeePerGas"] | Percentage;
preVerificationGas: UserOperationRequest["maxFeePerGas"] | Percentage;
verificationGasLimit: UserOperationRequest["maxFeePerGas"] | Percentage;
maxFeePerGas: UserOperationRequest["maxFeePerGas"] | Multiplier;
maxPriorityFeePerGas: UserOperationRequest["maxFeePerGas"] | Multiplier;
callGasLimit: UserOperationRequest["maxFeePerGas"] | Multiplier;
preVerificationGas: UserOperationRequest["maxFeePerGas"] | Multiplier;
verificationGasLimit: UserOperationRequest["maxFeePerGas"] | Multiplier;
}>;

export interface AlchemyGasManagerConfig {
Expand Down Expand Up @@ -138,26 +138,24 @@ const requestGasAndPaymasterData: <C extends ClientWithAlchemyMethods>(

const overrideField = (
field: keyof UserOperationFeeOptions
): Hex | Percentage | undefined => {
): Hex | Multiplier | undefined => {
if (overrides?.[field] != null) {
// one-off absolute override
if (isBigNumberish(overrides[field])) {
return deepHexlify(overrides[field]);
}
// one-off percentage overrides
// one-off multiplier overrides
else {
return {
percentage:
100 + Number((overrides[field] as Percentage).percentage),
multiplier: Number((overrides[field] as Multiplier).multiplier),
};
}
}

// provider level fee options with percentage
if (isPercentage(feeOptions?.[field])) {
// provider level fee options with multiplier
if (isMultiplier(feeOptions?.[field])) {
return {
percentage:
100 + Number((feeOptions![field] as Percentage).percentage),
multiplier: Number((feeOptions![field] as Multiplier).multiplier),
};
}

Expand Down
8 changes: 4 additions & 4 deletions packages/core/e2e-tests/simple-account.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe("Simple Account Tests", () => {
expect(isAddress(address)).toBe(true);
});

it("should correctly handle percentage overrides for buildUserOperation", async () => {
it("should correctly handle multiplier overrides for buildUserOperation", async () => {
const signer = await givenConnectedProvider({
owner,
chain,
Expand All @@ -95,7 +95,7 @@ describe("Simple Account Tests", () => {
owner,
chain,
feeOptions: {
preVerificationGas: { percentage: 100 },
preVerificationGas: { multiplier: 2 },
},
});

Expand Down Expand Up @@ -147,12 +147,12 @@ describe("Simple Account Tests", () => {
expect(struct.preVerificationGas).toBe(100_000_000n);
}, 60000);

it("should correctly handle percentage overrides for sendUserOperation", async () => {
it("should correctly handle multiplier overrides for sendUserOperation", async () => {
const signer = await givenConnectedProvider({
owner,
chain,
feeOptions: {
preVerificationGas: { percentage: 100 },
preVerificationGas: { multiplier: 2 },
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { SendUserOperationResult } from "../../client/types";
import { AccountNotFoundError } from "../../errors/account.js";
import { IncompatibleClientError } from "../../errors/client.js";
import type { UserOperationOverrides, UserOperationStruct } from "../../types";
import { bigIntMax, bigIntPercent } from "../../utils/index.js";
import { bigIntMax, bigIntMultiply } from "../../utils/index.js";
import { _runMiddlewareStack } from "./internal/runMiddlewareStack.js";
import { _sendUserOperation } from "./internal/sendUserOperation.js";
import type { DropAndReplaceUserOperationParameters } from "./types";
Expand Down Expand Up @@ -54,11 +54,11 @@ export const dropAndReplaceUserOperation: <
const _overrides: UserOperationOverrides = {
maxFeePerGas: bigIntMax(
BigInt(maxFeePerGas ?? 0n),
bigIntPercent(uoToDrop.maxFeePerGas, 110n)
bigIntMultiply(uoToDrop.maxFeePerGas, 1.1)
),
maxPriorityFeePerGas: bigIntMax(
BigInt(maxPriorityFeePerGas ?? 0n),
bigIntPercent(uoToDrop.maxPriorityFeePerGas, 110n)
bigIntMultiply(uoToDrop.maxPriorityFeePerGas, 1.1)
),
};

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/client/schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Transport } from "viem";
import { z } from "zod";

import { BigNumberishRangeSchema, PercentageSchema } from "../utils/index.js";
import { BigNumberishRangeSchema, MultiplierSchema } from "../utils/index.js";
import type { BundlerClient } from "./bundlerClient.js";

export const createPublicErc4337ClientSchema = <
Expand Down Expand Up @@ -42,7 +42,7 @@ export const ConnectionConfigSchema = z.union([
]);

export const UserOperationFeeOptionsFieldSchema =
BigNumberishRangeSchema.merge(PercentageSchema).partial();
BigNumberishRangeSchema.merge(MultiplierSchema).partial();

export const UserOperationFeeOptionsSchema = z
.object({
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export {
applyUserOpOverrideOrFeeOption,
asyncPipe,
bigIntMax,
bigIntPercent,
bigIntMultiply,
deepHexlify,
defineReadOnly,
filterUndefined,
Expand All @@ -129,6 +129,6 @@ export {
getDefaultUserOperationFeeOptions,
getUserOperationHash,
isBigNumberish,
isPercentage,
isMultiplier,
resolveProperties,
} from "./utils/index.js";
14 changes: 7 additions & 7 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
BigNumberishRangeSchema,
BigNumberishSchema,
HexSchema,
PercentageSchema,
MultiplierSchema,
} from "./utils";

export type Hex = z.input<typeof HexSchema>;
Expand All @@ -17,7 +17,7 @@ export type EmptyHex = `0x`;
// based on @account-abstraction/common
export type PromiseOrValue<T> = T | Promise<T>;
export type BytesLike = Uint8Array | Hex;
export type Percentage = z.input<typeof PercentageSchema>;
export type Multiplier = z.input<typeof MultiplierSchema>;

export type BigNumberish = z.input<typeof BigNumberishSchema>;
export type BigNumberishRange = z.input<typeof BigNumberishRangeSchema>;
Expand All @@ -44,15 +44,15 @@ export type UserOperationFeeOptions = z.input<
>;

export type UserOperationOverrides = Partial<{
callGasLimit: UserOperationStruct["callGasLimit"] | Percentage;
maxFeePerGas: UserOperationStruct["maxFeePerGas"] | Percentage;
callGasLimit: UserOperationStruct["callGasLimit"] | Multiplier;
maxFeePerGas: UserOperationStruct["maxFeePerGas"] | Multiplier;
maxPriorityFeePerGas:
| UserOperationStruct["maxPriorityFeePerGas"]
| Percentage;
preVerificationGas: UserOperationStruct["preVerificationGas"] | Percentage;
| Multiplier;
preVerificationGas: UserOperationStruct["preVerificationGas"] | Multiplier;
verificationGasLimit:
| UserOperationStruct["verificationGasLimit"]
| Percentage;
| Multiplier;
paymasterAndData: UserOperationStruct["paymasterAndData"];
}>;

Expand Down
29 changes: 29 additions & 0 deletions packages/core/src/utils/__tests__/bigint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { RoundingMode, bigIntMultiply } from "../bigint.js";

describe("BigInt utility", () => {
describe("multiplication operations", () => {
it("should multiply a big int correctly with an int", async () => {
expect(bigIntMultiply(10n, 2, RoundingMode.ROUND_UP)).toEqual(20n);
});

it("should multiply a big int correctly with a float", async () => {
expect(bigIntMultiply(10n, 0.5, RoundingMode.ROUND_UP)).toEqual(5n);
});

it("should fail to multiply a big int and a float with too much precision", async () => {
expect(() =>
bigIntMultiply(10n, 0.53958491842948789, RoundingMode.ROUND_UP)
).toThrowError(
"bigIntMultiply requires a multiplier validated number as the second argument"
);
});

it("should multiply a big int correctly when rouding up", async () => {
expect(bigIntMultiply(10n, 0.53, RoundingMode.ROUND_UP)).toEqual(6n);
});

it("should multiply a big int correctly when rouding down", async () => {
expect(bigIntMultiply(10n, 0.53, RoundingMode.ROUND_DOWN)).toEqual(5n);
});
});
});
38 changes: 22 additions & 16 deletions packages/core/src/utils/bigint.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { keccak256, toHex } from "viem";
import type { BigNumberish } from "../types";
import type { BigNumberish, Multiplier } from "../types";
import { isMultiplier } from "./schema.js";

/**
* Returns the max bigint in a list of bigints
Expand Down Expand Up @@ -67,29 +68,34 @@ export enum RoundingMode {
}

/**
* Useful if you want to increment a bigint by N% or decrement by N%
* Given a bigint and a number (which can be a float), returns the bigint value.
* Note: this function has loss and will round down to the nearest integer.
*
* example:
* ```
* const tenPercentIncrease = bigIntPercent(100n, 110n);
* const tenPercentDecrease = bigIntPercent(100n, 90n);
* ```
*
* @param base -- the base bigint that we want to apply a percent to
* @param percent -- the percent to apply to the base
* @param a -- the bigint value to multiply
* @param b -- the number to multiply by
* @param roundingMode -- the rounding mode to use when calculating the percent. defaults to ROUND_UP
* @returns the base multiplied by the percent and divided by 100
* @returns -- the bigint value of the multiplication with the number rounded by the rounding mode
*/
export const bigIntPercent = (
export const bigIntMultiply = (
base: BigNumberish,
percent: bigint,
multiplier: Multiplier["multiplier"],
roundingMode: RoundingMode = RoundingMode.ROUND_UP
) => {
if (roundingMode === RoundingMode.ROUND_UP) {
return (BigInt(base) * percent + 99n) / 100n;
if (!isMultiplier({ multiplier })) {
throw new Error(
"bigIntMultiply requires a multiplier validated number as the second argument"
);
}

return (BigInt(base) * percent) / 100n;
// Get decimal places of b. Max decimal places is defined by the MultiplerSchema.
const decimalPlaces = multiplier.toString().split(".")[1]?.length ?? 0;
const val =
roundingMode === RoundingMode.ROUND_UP
? BigInt(base) * BigInt(multiplier * 10 ** decimalPlaces) +
BigInt(10 ** decimalPlaces - 1)
: BigInt(base) * BigInt(multiplier * 10 ** decimalPlaces);

return val / BigInt(10 ** decimalPlaces);
};

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/utils/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const getDefaultUserOperationFeeOptions = (
return {
maxPriorityFeePerGas: {
min: minPriorityFeePerBidDefaults.get(chain.id) ?? 100_000_000n,
percentage: 33,
multiplier: 1.33,
},
};
};
16 changes: 1 addition & 15 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ import type { Address, Chain, Hash, Hex } from "viem";
import { encodeAbiParameters, hexToBigInt, keccak256, toHex } from "viem";
import * as chains from "viem/chains";
import * as alchemyChains from "../chains/index.js";
import type {
BigNumberish,
Percentage,
PromiseOrValue,
UserOperationRequest,
} from "../types.js";
import { BigNumberishSchema, PercentageSchema } from "./schema.js";
import type { PromiseOrValue, UserOperationRequest } from "../types.js";

export const AlchemyChainMap = new Map<number, Chain>(
Object.values(alchemyChains).map((c) => [c.id, c])
Expand Down Expand Up @@ -167,14 +161,6 @@ export function defineReadOnly<T, K extends keyof T>(
});
}

export function isBigNumberish(x: any): x is BigNumberish {
return x != null && BigNumberishSchema.safeParse(x).success;
}

export function isPercentage(x: any): x is Percentage {
return x != null && PercentageSchema.safeParse(x).success;
}

export function filterUndefined(
obj: Record<string, unknown>
): Record<string, unknown> {
Expand Down
Loading

0 comments on commit 10a1b85

Please sign in to comment.