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
16 changes: 16 additions & 0 deletions src/common/abis/deployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ const _deployerV6 = [
`function eval4(${_EvalV4} calldata eval) external view returns (${_StackItem}[] calldata stack, bytes32[] calldata writes)`,
] as const;

// IRainlang, the registry contract of the newer rainlang stack that exposes the
// addresses of the four core contracts, in this stack the expression deployer is
// only the parser and integrity checker and no longer knows the interpreter and store
const _rainlang = [
"function expressionDeployerAddress() external pure returns (address)",
"function interpreterAddress() external pure returns (address)",
"function storeAddress() external pure returns (address)",
"function parserAddress() external pure returns (address)",
] as const;

/** Keeps ExpressionDeployer related ABIs */
export namespace DeployerAbi {
/** ExpressionDeployerNPE2 contract primary parsed ABI */
Expand All @@ -31,12 +41,18 @@ export namespace DeployerAbi {

/** ExpressionDeployerNPE2 contract primary parsed ABI */
export const DeployerV6 = parseAbi(_deployerV6);

/** IRainlang registry contract primary parsed ABI */
export const Rainlang = parseAbi(_rainlang);
}

/** Deployer signature ABI */
export namespace Signatures {
/** ExpressionDeployerNPE2 signature ABI */
export const deployer = _deployer;

/** IRainlang registry signature ABI */
export const rainlang = _rainlang;
}

/** Deployer and Interpreter structs ABI */
Expand Down
8 changes: 4 additions & 4 deletions src/gas/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,11 @@ describe("Test GasManager", () => {
} as any);

// class field defaults
expect(manager.gasIncreasePointsPerStep).toBe(3);
expect(manager.gasIncreaseStepTime).toBe(60 * 60 * 1000); // 3_600_000 ms
expect(manager.gasIncreasePointsPerStep).toBe(10);
expect(manager.gasIncreaseStepTime).toBe(15 * 60 * 1000); // 900_000 ms

// maxGasPriceMultiplier defaults to base + 100 when not provided
expect(manager.maxGasPriceMultiplier).toBe(200);
// maxGasPriceMultiplier defaults to base + 1000 when not provided
expect(manager.maxGasPriceMultiplier).toBe(1100);

// multiplier starts at the base value
expect(manager.gasPriceMultiplier).toBe(100);
Expand Down
6 changes: 3 additions & 3 deletions src/gas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ export class GasManager {
/** Maximum gas price multiplier */
readonly maxGasPriceMultiplier: number;
/** The points to increase the gas price multiplier at each step */
readonly gasIncreasePointsPerStep: number = 3; // default increase by 3 points
readonly gasIncreasePointsPerStep: number = 10; // default increase by 10 points
/** The time to stay in increased the gas price multiplier before reseting to base */
readonly gasIncreaseStepTime: number = 60 * 60 * 1000; // default 60 minutes in milliseconds
readonly gasIncreaseStepTime: number = 15 * 60 * 1000; // default 15 minutes in milliseconds
/** The threshold for transaction time before considering it as a trigger for gas price multiplierincrease */
readonly txTimeThreshold: number; // default 15 seconds threshold

Expand Down Expand Up @@ -94,7 +94,7 @@ export class GasManager {
if (config.maxGasPriceMultiplier !== undefined) {
this.maxGasPriceMultiplier = config.maxGasPriceMultiplier;
} else {
this.maxGasPriceMultiplier = this.baseGasPriceMultiplier + 100; // default +1x ceiling
this.maxGasPriceMultiplier = this.baseGasPriceMultiplier + 1000; // default +10x ceiling
}
this.gasPriceMultiplier = config.baseGasPriceMultiplier;
}
Expand Down
2 changes: 1 addition & 1 deletion src/order/quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export async function quoteSingleOrderV4(
args: [TakeOrder.getQuoteConfig(orderDetails.takeOrder.struct)],
}),
gas,
blockTag: "pending",
// blockTag: "pending",
})
.catch((error) => {
orderDetails.takeOrder.quote = undefined;
Expand Down
67 changes: 66 additions & 1 deletion src/signer/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
sendTx,
getTxGas,
broadcastTx,
hasExplicitGasParams,
tryGetReceipt,
waitUntilFree,
getSelfBalance,
Expand Down Expand Up @@ -51,6 +52,7 @@ describe("Test sendTx", () => {
to: "0xdestination" as `0x${string}`,
data: "0xdata" as `0x${string}`,
gas: 100000n,
gasPrice: 20000000000n,
};

beforeEach(() => {
Expand Down Expand Up @@ -220,6 +222,58 @@ describe("Test sendTx", () => {
expect(wait).toBeTypeOf("function");
});

it("should send through viem sendTransaction when gas limit is not set", async () => {
(mockSigner.sendTransaction as Mock).mockResolvedValue("0xviemhash");
const tx = { to: mockTx.to, value: 1n };
const { hash: txHash, wait } = await sendTx(mockSigner, tx as any);

expect(mockSigner.sendTransaction).toHaveBeenCalledTimes(1);
expect(mockSigner.sendTransaction).toHaveBeenCalledWith({ ...tx, nonce: 5 });
expect(mockSigner.account.signTransaction).not.toHaveBeenCalled();
expect(mockSigner.sendRawTransaction).not.toHaveBeenCalled();
expect(txHash).toBe("0xviemhash");
expect(mockSigner.busy).toBe(true);
expect(wait).toBeTypeOf("function");
});

it("should send through viem sendTransaction when gas price is not set", async () => {
(mockSigner.sendTransaction as Mock).mockResolvedValue("0xviemhash");
const tx = { to: mockTx.to, data: mockTx.data, gas: 100000n };
const { hash: txHash } = await sendTx(mockSigner, tx as any);

expect(mockSigner.sendTransaction).toHaveBeenCalledTimes(1);
expect(mockSigner.sendTransaction).toHaveBeenCalledWith({ ...tx, nonce: 5 });
expect(mockSigner.account.signTransaction).not.toHaveBeenCalled();
expect(mockSigner.sendRawTransaction).not.toHaveBeenCalled();
expect(txHash).toBe("0xviemhash");
});

it("should sign locally when gas limit and eip1559 max fee are set", async () => {
const tx = { to: mockTx.to, data: mockTx.data, gas: 100000n, maxFeePerGas: 10n };
const { hash: txHash } = await sendTx(mockSigner, tx as any);

expect(mockSigner.sendTransaction).not.toHaveBeenCalled();
expect(mockSigner.account.signTransaction).toHaveBeenCalledWith(
{ ...tx, nonce: 5, chainId: 1 },
{ serializer: undefined },
);
expect(mockSigner.sendRawTransaction).toHaveBeenCalledTimes(1);
expect(txHash).toBe("0xhash");
});

it("should retry through viem sendTransaction on failure", async () => {
(mockSigner.sendTransaction as Mock)
.mockRejectedValueOnce(new Error("First attempt failed"))
.mockResolvedValueOnce("0xviemhash");
const tx = { to: mockTx.to, value: 1n };
const { hash: txHash } = await sendTx(mockSigner, tx as any, 10);

expect(mockSigner.sendTransaction).toHaveBeenCalledTimes(2);
expect(mockSigner.account.signTransaction).not.toHaveBeenCalled();
expect(txHash).toBe("0xviemhash");
expect(mockSigner.busy).toBe(true);
});

it("should successfully send a transaction on second attempt", async () => {
(mockSigner.sendRawTransaction as Mock)
.mockRejectedValueOnce(new Error("First attempt failed"))
Expand Down Expand Up @@ -364,6 +418,17 @@ describe("Test isAlreadyKnownTxError", () => {
});
});

describe("Test hasExplicitGasParams", () => {
it("should be true only with gas limit and a gas price field", () => {
expect(hasExplicitGasParams({ to: "0x", gas: 1n, gasPrice: 1n } as any)).toBe(true);
expect(hasExplicitGasParams({ to: "0x", gas: 1n, maxFeePerGas: 1n } as any)).toBe(true);
expect(hasExplicitGasParams({ to: "0x", gas: 1n } as any)).toBe(false);
expect(hasExplicitGasParams({ to: "0x", gasPrice: 1n } as any)).toBe(false);
expect(hasExplicitGasParams({ to: "0x", maxFeePerGas: 1n } as any)).toBe(false);
expect(hasExplicitGasParams({ to: "0x", value: 1n } as any)).toBe(false);
});
});

describe("Test broadcastTx", () => {
const serialized = "0x02abcd" as `0x${string}`;
const localHash = keccak256(serialized);
Expand Down Expand Up @@ -694,7 +759,7 @@ describe("Test estimateGasCost", () => {
it("should calculate basic gas cost non-L2 chains", async () => {
const result = await estimateGasCost(mockSigner, mockTx);

expect(mockSigner.estimateGas).toHaveBeenCalledWith({ ...mockTx, blockTag: "pending" });
expect(mockSigner.estimateGas).toHaveBeenCalledWith({ ...mockTx /*blockTag: "pending"*/ });
expect(result).toEqual({
gas: 100000n,
gasPrice: 20000000000n, // 20 gwei * 110%
Expand Down
32 changes: 29 additions & 3 deletions src/signer/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,27 @@ export namespace RainSolverSignerActions {
}
}

/**
* Determines if the given tx has its gas limit and gas price (legacy or eip1559)
* explicitly set, such a tx can be signed locally and sent as a raw tx without
* any rpc call for populating its fields
* @param tx - The transaction parameters
*/
export function hasExplicitGasParams(
tx: SendTransactionParameters<Chain, HDAccount | PrivateKeyAccount>,
): boolean {
return (
typeof tx.gas === "bigint" &&
(typeof tx.gasPrice === "bigint" || typeof tx.maxFeePerGas === "bigint")
);
}

/**
* A wrapper for viem sendTransactions that handles nonce and manages signer busy
* state while the transaction is being sent ensuring proper busy state management
* state while the transaction is being sent ensuring proper busy state management,
* a tx with explicit gas limit and gas price is signed locally and broadcasted as
* a raw tx (see broadcastTx), otherwise it is sent through viem sendTransaction
* which populates the missing fields over rpc before signing
*
* @param signer - The RainSolverSigner instance to use for sending the transaction
* @param tx - The transaction parameters to send
Expand All @@ -179,6 +197,10 @@ export async function sendTx(
tx.gas = getTxGas(signer.state, tx.gas);
}

// only a fully populated tx can be signed locally, the rest go
// through viem sendTransaction that fills in the missing fields
const canBroadcast = hasExplicitGasParams(tx);

async function send() {
if (typeof nonce !== "number") {
await signer
Expand All @@ -192,7 +214,11 @@ export async function sendTx(
throw e;
});
}
return await broadcastTx(signer, { ...(tx as any), nonce });
if (canBroadcast) {
return await broadcastTx(signer, { ...(tx as any), nonce });
} else {
return await signer.sendTransaction({ ...(tx as any), nonce });
}
}
try {
const hash = await send();
Expand Down Expand Up @@ -226,7 +252,7 @@ export async function estimateGasCost(
tx: EstimateGasParameters<Chain>,
): Promise<EstimateGasCostResult> {
const gasPrice = signer.state.gasPrice;
const gas = await signer.estimateGas({ ...tx, blockTag: "pending" } as any);
const gas = await signer.estimateGas({ ...tx } as any);
const result: EstimateGasCostResult = {
gas,
gasPrice,
Expand Down
Loading
Loading