Skip to content
Open
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
32 changes: 31 additions & 1 deletion src/core/modes/inter/simulate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import { RainSolverSigner } from "../../../signer";
import { SimulationHaltReason } from "../simulator";
import { ABI, Dispair, Result } from "../../../common";
import { describe, it, expect, vi, beforeEach, Mock, assert } from "vitest";
import { encodeAbiParameters, encodeFunctionData, formatUnits, maxUint256, parseUnits } from "viem";
import {
maxUint256,
parseUnits,
formatUnits,
zeroAddress,
encodeFunctionData,
encodeAbiParameters,
} from "viem";
import {
InterOrderbookTradeSimulator,
SimulateInterOrderbookTradeArgs,
Expand Down Expand Up @@ -326,6 +333,29 @@ describe("Test InterOrderbookTradeSimulator", () => {

getCalldataSpy.mockRestore();
});

it("should use empty task when noTask is set", async () => {
const getCalldataSpy = vi.spyOn(simulator, "getCalldata");
getCalldataSpy.mockReturnValue("0xencodedData");

const result = await simulator.setTransactionData({
...preparedParams,
noTask: true,
});
assert(result.isOk());
expect(preparedParams.rawtx.data).toBe("0xencodedData");
expect(getEnsureBountyTaskBytecode).not.toHaveBeenCalled();
expect(getCalldataSpy).toHaveBeenCalledWith(preparedParams.takeOrdersConfigStruct, {
evaluable: {
interpreter: zeroAddress,
store: zeroAddress,
bytecode: "0x",
},
signedContext: [],
});

getCalldataSpy.mockRestore();
});
});

describe("Test estimateProfit method", () => {
Expand Down
78 changes: 47 additions & 31 deletions src/core/modes/inter/simulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import { WasmEncodedError } from "@rainlanguage/float";
import { TradeType, FailedSimulation, TaskType } from "../../types";
import { SimulationHaltReason, TradeSimulatorBase } from "../simulator";
import { Result, ABI, RawTransaction, maxFloat, toFloat, minFloat } from "../../../common";
import { encodeAbiParameters, encodeFunctionData, formatUnits, maxUint256, parseUnits } from "viem";
import {
maxUint256,
parseUnits,
formatUnits,
zeroAddress,
encodeFunctionData,
encodeAbiParameters,
} from "viem";
import {
EnsureBountyTaskType,
EnsureBountyTaskErrorType,
Expand Down Expand Up @@ -51,6 +58,8 @@ export type InterOrderbookTradePreparedParams = {
takeOrdersConfigStruct: TakeOrdersConfigType;
minimumExpected: bigint;
price?: bigint;
/** If set, builds the tx data with an empty task */
noTask?: boolean;
};

/**
Expand Down Expand Up @@ -166,39 +175,46 @@ export class InterOrderbookTradeSimulator extends TradeSimulatorBase {
params.type,
)!;

// try to get task bytecode for ensure bounty task
const taskBytecodeResult = await getEnsureBountyTaskBytecode(
{
type: EnsureBountyTaskType.External,
inputToEthPrice: parseUnits(this.tradeArgs.inputToEthPrice, 18),
outputToEthPrice: parseUnits(this.tradeArgs.outputToEthPrice, 18),
minimumExpected: params.minimumExpected,
sender: this.tradeArgs.signer.account.address,
},
this.tradeArgs.solver.state.client,
addresses.dispair,
);
if (taskBytecodeResult.isErr()) {
const errMsg = await errorSnapshot("", taskBytecodeResult.error);
this.spanAttributes["isNodeError"] =
taskBytecodeResult.error.type === EnsureBountyTaskErrorType.ParseError;
this.spanAttributes["error"] = errMsg;
const result = {
type: TradeType.InterOrderbook,
spanAttributes: this.spanAttributes,
reason: SimulationHaltReason.FailedToGetTaskBytecode,
};
this.spanAttributes["duration"] = performance.now() - this.startTime;
return Result.err(result);
// build the ensure bounty task bytecode, unless an empty task is
// explicitly requested or gas coverage is 0, in which cases the tx
// wont need onchain bounty assurance
let bytecode: `0x${string}` = "0x";
let interpreter: `0x${string}` = zeroAddress;
let store: `0x${string}` = zeroAddress;
if (!params.noTask && this.tradeArgs.solver.appOptions.gasCoveragePercentage !== "0") {
const taskBytecodeResult = await getEnsureBountyTaskBytecode(
{
type: EnsureBountyTaskType.External,
inputToEthPrice: parseUnits(this.tradeArgs.inputToEthPrice, 18),
outputToEthPrice: parseUnits(this.tradeArgs.outputToEthPrice, 18),
minimumExpected: params.minimumExpected,
sender: this.tradeArgs.signer.account.address,
},
this.tradeArgs.solver.state.client,
addresses.dispair,
);
if (taskBytecodeResult.isErr()) {
const errMsg = await errorSnapshot("", taskBytecodeResult.error);
this.spanAttributes["isNodeError"] =
taskBytecodeResult.error.type === EnsureBountyTaskErrorType.ParseError;
this.spanAttributes["error"] = errMsg;
const result = {
type: TradeType.InterOrderbook,
spanAttributes: this.spanAttributes,
reason: SimulationHaltReason.FailedToGetTaskBytecode,
};
this.spanAttributes["duration"] = performance.now() - this.startTime;
return Result.err(result);
}
bytecode = taskBytecodeResult.value;
interpreter = addresses.dispair.interpreter as `0x${string}`;
store = addresses.dispair.store as `0x${string}`;
}
const task = {
evaluable: {
interpreter: addresses.dispair.interpreter as `0x${string}`,
store: addresses.dispair.store as `0x${string}`,
bytecode:
this.tradeArgs.solver.appOptions.gasCoveragePercentage === "0"
? "0x"
: taskBytecodeResult.value,
interpreter,
store,
bytecode,
},
signedContext: [],
};
Expand Down
29 changes: 26 additions & 3 deletions src/core/modes/intra/simulation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { RainSolverSigner } from "../../../signer";
import { SimulationHaltReason } from "../simulator";
import { Order, Pair, TakeOrderDetails } from "../../../order";
import { ABI, Dispair, maxFloat, Result } from "../../../common";
import { encodeFunctionData, formatUnits, maxUint256, parseUnits } from "viem";
import { encodeFunctionData, formatUnits, maxUint256, parseUnits, zeroAddress } from "viem";
import { describe, it, expect, vi, beforeEach, Mock, assert } from "vitest";
import {
IntraOrderbookTradeSimulator,
Expand Down Expand Up @@ -264,6 +264,29 @@ describe("Test IntraOrderbookTradeSimulator", () => {
TradeType.IntraOrderbook,
);
});

it("should use empty task when noTask is set", async () => {
const getCalldataSpy = vi.spyOn(simulator, "getCalldata");
getCalldataSpy.mockReturnValue("0xencodedData");

const result = await simulator.setTransactionData({
...preparedParams,
noTask: true,
});
assert(result.isOk());
expect(preparedParams.rawtx.data).toBe("0xencodedData");
expect(getEnsureBountyTaskBytecode).not.toHaveBeenCalled();
expect(getCalldataSpy).toHaveBeenCalledWith({
evaluable: {
interpreter: zeroAddress,
store: zeroAddress,
bytecode: "0x",
},
signedContext: [],
});

getCalldataSpy.mockRestore();
});
});

describe("Test estimateProfit method", () => {
Expand Down Expand Up @@ -550,7 +573,7 @@ describe("Test IntraOrderbookTradeSimulator", () => {
.mockReturnValueOnce("0xencodedData2")
.mockReturnValueOnce("0xencodedData3")
.mockReturnValueOnce("0xmulticallData");
const task = { task: "task-value" } as any;
const task = { task: "task-value", evaluable: { bytecode: "0xbytecode" } } as any;
const result = simulator.getCalldataForV3Order(task);
expect(result).toBe("0xmulticallData");
expect(encodeFunctionData).toHaveBeenCalledTimes(4);
Expand Down Expand Up @@ -619,7 +642,7 @@ describe("Test IntraOrderbookTradeSimulator", () => {
.mockReturnValueOnce("0xencodedData2")
.mockReturnValueOnce("0xencodedData3")
.mockReturnValueOnce("0xmulticallData");
const task = { task: "task-value" } as any;
const task = { task: "task-value", evaluable: { bytecode: "0xbytecode" } } as any;
const result = simulator.getCalldataForV4Order(task);
expect(result).toBe("0xmulticallData");
expect(encodeFunctionData).toHaveBeenCalledTimes(4);
Expand Down
84 changes: 48 additions & 36 deletions src/core/modes/intra/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Pair, TakeOrderDetails } from "../../../order";
import { TradeType, FailedSimulation, TaskType } from "../../types";
import { Result, ABI, RawTransaction, maxFloat } from "../../../common";
import { SimulationHaltReason, TradeSimulatorBase } from "../simulator";
import { encodeFunctionData, formatUnits, maxUint256, parseUnits } from "viem";
import { encodeFunctionData, formatUnits, maxUint256, parseUnits, zeroAddress } from "viem";
import {
EnsureBountyTaskType,
EnsureBountyTaskErrorType,
Expand Down Expand Up @@ -43,6 +43,8 @@ export type IntraOrderbookTradePrepareedParams = {
rawtx: RawTransaction;
minimumExpected: bigint;
price?: bigint;
/** If set, builds the tx data with an empty task */
noTask?: boolean;
};

/**
Expand Down Expand Up @@ -124,41 +126,51 @@ export class IntraOrderbookTradeSimulator extends TradeSimulatorBase {
params.type,
)!;

// build clear function call data and withdraw tasks
const taskBytecodeResult = await getEnsureBountyTaskBytecode(
{
type: EnsureBountyTaskType.Internal,
botAddress: this.tradeArgs.signer.account.address,
inputToken: this.tradeArgs.orderDetails.buyToken,
outputToken: this.tradeArgs.orderDetails.sellToken,
orgInputBalance: this.tradeArgs.inputBalance,
orgOutputBalance: this.tradeArgs.outputBalance,
inputToEthPrice: parseUnits(this.tradeArgs.inputToEthPrice, 18),
outputToEthPrice: parseUnits(this.tradeArgs.outputToEthPrice, 18),
minimumExpected: params.minimumExpected,
sender: this.tradeArgs.signer.account.address,
},
this.tradeArgs.solver.state.client,
addresses.dispair,
);
if (taskBytecodeResult.isErr()) {
const errMsg = await errorSnapshot("", taskBytecodeResult.error);
this.spanAttributes["isNodeError"] =
taskBytecodeResult.error.type === EnsureBountyTaskErrorType.ParseError;
this.spanAttributes["error"] = errMsg;
const result = {
type: TradeType.IntraOrderbook,
spanAttributes: this.spanAttributes,
reason: SimulationHaltReason.FailedToGetTaskBytecode,
};
this.spanAttributes["duration"] = performance.now() - this.startTime;
return Result.err(result);
// build the ensure bounty task bytecode for the withdraw tasks, unless
// an empty task is explicitly requested or gas coverage is 0, in which
// cases the tx wont need onchain bounty assurance
let bytecode: `0x${string}` = "0x";
let interpreter: `0x${string}` = zeroAddress;
let store: `0x${string}` = zeroAddress;
if (!params.noTask && this.tradeArgs.solver.appOptions.gasCoveragePercentage !== "0") {
const taskBytecodeResult = await getEnsureBountyTaskBytecode(
{
type: EnsureBountyTaskType.Internal,
botAddress: this.tradeArgs.signer.account.address,
inputToken: this.tradeArgs.orderDetails.buyToken,
outputToken: this.tradeArgs.orderDetails.sellToken,
orgInputBalance: this.tradeArgs.inputBalance,
orgOutputBalance: this.tradeArgs.outputBalance,
inputToEthPrice: parseUnits(this.tradeArgs.inputToEthPrice, 18),
outputToEthPrice: parseUnits(this.tradeArgs.outputToEthPrice, 18),
minimumExpected: params.minimumExpected,
sender: this.tradeArgs.signer.account.address,
},
this.tradeArgs.solver.state.client,
addresses.dispair,
);
if (taskBytecodeResult.isErr()) {
const errMsg = await errorSnapshot("", taskBytecodeResult.error);
this.spanAttributes["isNodeError"] =
taskBytecodeResult.error.type === EnsureBountyTaskErrorType.ParseError;
this.spanAttributes["error"] = errMsg;
const result = {
type: TradeType.IntraOrderbook,
spanAttributes: this.spanAttributes,
reason: SimulationHaltReason.FailedToGetTaskBytecode,
};
this.spanAttributes["duration"] = performance.now() - this.startTime;
return Result.err(result);
}
bytecode = taskBytecodeResult.value;
interpreter = addresses.dispair.interpreter as `0x${string}`;
store = addresses.dispair.store as `0x${string}`;
}
const task = {
evaluable: {
interpreter: addresses.dispair.interpreter as `0x${string}`,
store: addresses.dispair.store as `0x${string}`,
bytecode: taskBytecodeResult.value,
interpreter,
store,
bytecode,
},
signedContext: [],
};
Expand Down Expand Up @@ -228,7 +240,7 @@ export class IntraOrderbookTradeSimulator extends TradeSimulatorBase {
this.tradeArgs.orderDetails.sellToken,
BigInt(this.outputBountyVaultId),
maxUint256,
this.tradeArgs.solver.appOptions.gasCoveragePercentage === "0" ? [] : [task],
task.evaluable.bytecode === "0x" ? [] : [task],
],
});
const clear2Calldata = encodeFunctionData({
Expand Down Expand Up @@ -286,7 +298,7 @@ export class IntraOrderbookTradeSimulator extends TradeSimulatorBase {
this.tradeArgs.orderDetails.sellToken,
this.outputBountyVaultId,
maxFloat(this.tradeArgs.orderDetails.sellTokenDecimals),
this.tradeArgs.solver.appOptions.gasCoveragePercentage === "0" ? [] : [task],
task.evaluable.bytecode === "0x" ? [] : [task],
],
});
const clear3Calldata = encodeFunctionData({
Expand Down Expand Up @@ -344,7 +356,7 @@ export class IntraOrderbookTradeSimulator extends TradeSimulatorBase {
this.tradeArgs.orderDetails.sellToken,
this.outputBountyVaultId,
maxFloat(this.tradeArgs.orderDetails.sellTokenDecimals),
this.tradeArgs.solver.appOptions.gasCoveragePercentage === "0" ? [] : [task],
task.evaluable.bytecode === "0x" ? [] : [task],
],
});
const clear2Calldata = encodeFunctionData({
Expand Down
Loading
Loading