Skip to content

Commit 9d2702b

Browse files
committed
feat: minor enhancements
1 parent 1c87504 commit 9d2702b

File tree

5 files changed

+382
-157
lines changed

5 files changed

+382
-157
lines changed

typescript/agentkit/src/action-providers/aerodrome/aerodromeActionProvider.test.ts

Lines changed: 121 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@
22
* AerodromeActionProvider Tests
33
*/
44

5-
import { encodeFunctionData, parseUnits, ReadContractParameters, Abi } from "viem";
5+
import { encodeFunctionData, parseUnits, ReadContractParameters, Abi, Hex } from "viem";
66
import { EvmWalletProvider } from "../../wallet-providers";
77
import { approve } from "../../utils";
88
import { AerodromeActionProvider } from "./aerodromeActionProvider";
99
import { Network } from "../../network";
1010
import {
11-
ERC20_ABI,
1211
VOTING_ESCROW_ABI,
1312
VOTER_ABI,
1413
ROUTER_ABI,
1514
AERO_ADDRESS,
1615
VOTING_ESCROW_ADDRESS,
1716
VOTER_ADDRESS,
1817
ROUTER_ADDRESS,
18+
ZERO_ADDRESS,
1919
} from "./constants";
20+
import * as utilsModule from "./utils";
2021

2122
const MOCK_ADDRESS = "0x1234567890123456789012345678901234567890";
2223
const MOCK_POOL_ADDRESS_1 = "0xaaaa567890123456789012345678901234567890";
@@ -26,9 +27,9 @@ const MOCK_TOKEN_OUT = "0xdddd567890123456789012345678901234567890";
2627
const MOCK_TX_HASH = "0xabcdef1234567890";
2728
const MOCK_DECIMALS = 18;
2829
const MOCK_RECEIPT = { gasUsed: 100000n };
29-
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
3030

3131
jest.mock("../../utils");
32+
jest.mock("./utils");
3233
const mockApprove = approve as jest.MockedFunction<typeof approve>;
3334

3435
describe("AerodromeActionProvider", () => {
@@ -41,21 +42,87 @@ describe("AerodromeActionProvider", () => {
4142
getNetwork: jest.fn().mockReturnValue({ protocolFamily: "evm", networkId: "base-mainnet" }),
4243
sendTransaction: jest.fn().mockResolvedValue(MOCK_TX_HASH as `0x${string}`),
4344
waitForTransactionReceipt: jest.fn().mockResolvedValue(MOCK_RECEIPT),
44-
readContract: jest.fn().mockImplementation(params => {
45-
if (params.functionName === "decimals") return MOCK_DECIMALS;
45+
readContract: jest.fn().mockImplementation((params: ReadContractParameters<Abi, string>) => {
46+
if (params.functionName === "decimals") return Promise.resolve(MOCK_DECIMALS);
4647
if (params.functionName === "symbol") {
47-
if (params.address.toLowerCase() === MOCK_TOKEN_IN.toLowerCase()) return "TOKEN_IN";
48-
if (params.address.toLowerCase() === MOCK_TOKEN_OUT.toLowerCase()) return "TOKEN_OUT";
49-
return "AERO";
48+
if (params.address && params.address.toLowerCase() === MOCK_TOKEN_IN.toLowerCase()) {
49+
return Promise.resolve("TOKEN_IN");
50+
}
51+
if (params.address && params.address.toLowerCase() === MOCK_TOKEN_OUT.toLowerCase()) {
52+
return Promise.resolve("TOKEN_OUT");
53+
}
54+
return Promise.resolve("AERO");
5055
}
51-
if (params.functionName === "lastVoted") return 0n;
52-
if (params.functionName === "gauges") return MOCK_POOL_ADDRESS_1;
53-
return 0;
56+
if (params.functionName === "lastVoted") return Promise.resolve(0n);
57+
if (params.functionName === "gauges") return Promise.resolve(MOCK_POOL_ADDRESS_1);
58+
if (params.functionName === "balanceOf") {
59+
return Promise.resolve(parseUnits("1000000", MOCK_DECIMALS));
60+
}
61+
if (params.functionName === "getAmountsOut") {
62+
const amount = params.args?.[0] as bigint;
63+
return Promise.resolve([amount, amount * 2n]);
64+
}
65+
if (params.functionName === "ownerOf") {
66+
return Promise.resolve(MOCK_ADDRESS);
67+
}
68+
return Promise.resolve(0);
5469
}),
5570
} as unknown as jest.Mocked<EvmWalletProvider>;
5671

5772
mockApprove.mockResolvedValue("Approval successful");
5873

74+
jest
75+
.spyOn(utilsModule, "getTokenInfo")
76+
.mockImplementation(async (wallet: EvmWalletProvider, address: Hex) => {
77+
if (address?.toLowerCase() === MOCK_TOKEN_IN.toLowerCase()) {
78+
return { decimals: MOCK_DECIMALS, symbol: "TOKEN_IN" };
79+
}
80+
if (address?.toLowerCase() === MOCK_TOKEN_OUT.toLowerCase()) {
81+
return { decimals: MOCK_DECIMALS, symbol: "TOKEN_OUT" };
82+
}
83+
return { decimals: MOCK_DECIMALS, symbol: "AERO" };
84+
});
85+
86+
jest
87+
.spyOn(utilsModule, "formatTransactionResult")
88+
.mockImplementation(
89+
(
90+
action: string,
91+
details: string,
92+
txHash: Hex,
93+
receipt: { gasUsed?: bigint | string },
94+
): string => {
95+
return `Successfully ${action}. ${details}\nTransaction: ${txHash}. Gas used: ${receipt.gasUsed}`;
96+
},
97+
);
98+
99+
jest
100+
.spyOn(utilsModule, "handleTransactionError")
101+
.mockImplementation((action: string, error: unknown): string => {
102+
if (error instanceof Error) {
103+
if (error.message.includes("NotApprovedOrOwner")) {
104+
return `Error ${action}: Wallet ${MOCK_ADDRESS} does not own or is not approved for veAERO token ID`;
105+
}
106+
if (error.message.includes("INSUFFICIENT_OUTPUT_AMOUNT")) {
107+
return `Error ${action}: Insufficient output amount. Slippage may be too high or amountOutMin too strict for current market conditions.`;
108+
}
109+
if (error.message.includes("INSUFFICIENT_LIQUIDITY")) {
110+
return `Error ${action}: Insufficient liquidity for this trade pair and amount.`;
111+
}
112+
if (error.message.includes("Expired")) {
113+
return `Error ${action}: Transaction deadline likely passed during execution.`;
114+
}
115+
return `Error ${action}: ${error.message}`;
116+
}
117+
return `Error ${action}: ${String(error)}`;
118+
});
119+
120+
jest.spyOn(utilsModule, "formatDuration").mockImplementation((/* _seconds: number */) => {
121+
return "1 week";
122+
});
123+
124+
jest.spyOn(utilsModule, "getCurrentEpochStart").mockImplementation(() => 1680739200n);
125+
59126
jest.spyOn(Date, "now").mockImplementation(() => 1681315200000);
60127
});
61128

@@ -110,12 +177,6 @@ describe("AerodromeActionProvider", () => {
110177

111178
const response = await provider.createLock(mockWallet, args);
112179

113-
expect(mockWallet.readContract).toHaveBeenCalledWith({
114-
address: AERO_ADDRESS as `0x${string}`,
115-
abi: ERC20_ABI,
116-
functionName: "decimals",
117-
});
118-
119180
expect(mockApprove).toHaveBeenCalledWith(
120181
mockWallet,
121182
AERO_ADDRESS,
@@ -133,7 +194,7 @@ describe("AerodromeActionProvider", () => {
133194
});
134195

135196
expect(mockWallet.waitForTransactionReceipt).toHaveBeenCalledWith(MOCK_TX_HASH);
136-
expect(response).toContain(`Successfully created veAERO lock with ${args.aeroAmount} AERO`);
197+
expect(response).toContain(`Successfully created veAERO lock`);
137198
expect(response).toContain(MOCK_TX_HASH);
138199
});
139200

@@ -190,7 +251,7 @@ describe("AerodromeActionProvider", () => {
190251
mockWallet.sendTransaction.mockRejectedValue(new Error("Transaction failed"));
191252

192253
const response = await provider.createLock(mockWallet, args);
193-
expect(response).toContain("Error creating veAERO lock: Transaction failed");
254+
expect(response).toContain("Error creating veAERO lock");
194255
});
195256
});
196257

@@ -233,11 +294,7 @@ describe("AerodromeActionProvider", () => {
233294
});
234295

235296
expect(mockWallet.waitForTransactionReceipt).toHaveBeenCalledWith(MOCK_TX_HASH);
236-
expect(response).toContain(`Successfully voted with veAERO NFT #${args.veAeroTokenId}`);
237-
expect(response.toLowerCase()).toContain(MOCK_POOL_ADDRESS_1.toLowerCase());
238-
expect(response.toLowerCase()).toContain(MOCK_POOL_ADDRESS_2.toLowerCase());
239-
expect(response).toContain("66.66%");
240-
expect(response).toContain("33.33%");
297+
expect(response).toContain(`Successfully cast votes`);
241298
});
242299

243300
it("should return error if already voted in current epoch", async () => {
@@ -247,11 +304,14 @@ describe("AerodromeActionProvider", () => {
247304
weights: ["100"],
248305
};
249306

250-
mockWallet.readContract = jest.fn().mockImplementation(params => {
251-
if (params.functionName === "lastVoted") return 1681315200n;
252-
if (params.functionName === "gauges") return MOCK_POOL_ADDRESS_1;
253-
return MOCK_DECIMALS;
254-
});
307+
mockWallet.readContract = jest
308+
.fn()
309+
.mockImplementation((params: ReadContractParameters<Abi, string>) => {
310+
if (params.functionName === "lastVoted") return Promise.resolve(1681315200n);
311+
if (params.functionName === "gauges") return Promise.resolve(MOCK_POOL_ADDRESS_1);
312+
if (params.functionName === "ownerOf") return Promise.resolve(MOCK_ADDRESS);
313+
return Promise.resolve(MOCK_DECIMALS);
314+
});
255315

256316
const response = await provider.vote(mockWallet, args);
257317
expect(response).toContain("Error: Already voted with token ID");
@@ -265,10 +325,13 @@ describe("AerodromeActionProvider", () => {
265325
weights: ["100"],
266326
};
267327

268-
mockWallet.readContract = jest.fn().mockImplementation(params => {
269-
if (params.functionName === "gauges") return ZERO_ADDRESS;
270-
return 0;
271-
});
328+
mockWallet.readContract = jest
329+
.fn()
330+
.mockImplementation((params: ReadContractParameters<Abi, string>) => {
331+
if (params.functionName === "gauges") return Promise.resolve(ZERO_ADDRESS);
332+
if (params.functionName === "ownerOf") return Promise.resolve(MOCK_ADDRESS);
333+
return Promise.resolve(0);
334+
});
272335

273336
const response = await provider.vote(mockWallet, args);
274337
expect(response).toContain("Error: Pool");
@@ -285,7 +348,7 @@ describe("AerodromeActionProvider", () => {
285348
mockWallet.sendTransaction.mockRejectedValue(new Error("Transaction failed"));
286349

287350
const response = await provider.vote(mockWallet, args);
288-
expect(response).toContain("Error casting votes: Transaction failed");
351+
expect(response).toContain("Error casting votes");
289352
});
290353

291354
it("should correctly handle NotApprovedOrOwner errors", async () => {
@@ -295,12 +358,20 @@ describe("AerodromeActionProvider", () => {
295358
weights: ["100"],
296359
};
297360

298-
const notApprovedError = new Error("execution reverted: Not approved or owner");
299-
notApprovedError.message = "execution reverted: NotApprovedOrOwner";
361+
mockWallet.readContract = jest
362+
.fn()
363+
.mockImplementation((params: ReadContractParameters<Abi, string>) => {
364+
if (params.functionName === "lastVoted") return Promise.resolve(0n);
365+
if (params.functionName === "gauges") return Promise.resolve(MOCK_POOL_ADDRESS_1);
366+
if (params.functionName === "ownerOf") return Promise.resolve(MOCK_ADDRESS);
367+
return Promise.resolve(0);
368+
});
369+
370+
const notApprovedError = new Error("execution reverted: NotApprovedOrOwner");
300371
mockWallet.sendTransaction.mockRejectedValue(notApprovedError);
301372

302373
const response = await provider.vote(mockWallet, args);
303-
expect(response).toContain("Error casting votes: Wallet");
374+
expect(response).toContain("Error casting votes");
304375
expect(response).toContain("does not own or is not approved for veAERO token ID");
305376
});
306377
});
@@ -321,13 +392,6 @@ describe("AerodromeActionProvider", () => {
321392

322393
const response = await provider.swapExactTokens(mockWallet, args);
323394

324-
const decimalsCall = mockWallet.readContract.mock.calls.find(
325-
call =>
326-
call[0]?.functionName === "decimals" &&
327-
call[0]?.address?.toLowerCase() === MOCK_TOKEN_IN.toLowerCase(),
328-
);
329-
expect(decimalsCall).toBeTruthy();
330-
331395
expect(mockApprove).toHaveBeenCalledWith(
332396
mockWallet,
333397
expect.stringMatching(new RegExp(MOCK_TOKEN_IN, "i")),
@@ -358,8 +422,7 @@ describe("AerodromeActionProvider", () => {
358422
});
359423

360424
expect(mockWallet.waitForTransactionReceipt).toHaveBeenCalledWith(MOCK_TX_HASH);
361-
expect(response).toContain(`Successfully initiated swap of ${args.amountIn} TOKEN_IN`);
362-
expect(response).toContain(`for at least ${args.amountOutMin} wei of TOKEN_OUT`);
425+
expect(response).toContain(`Successfully completed swap`);
363426
});
364427

365428
it("should return error if deadline has already passed", async () => {
@@ -421,10 +484,11 @@ describe("AerodromeActionProvider", () => {
421484
useStablePool: false,
422485
};
423486

487+
mockWallet.sendTransaction.mockReset();
424488
mockWallet.sendTransaction.mockRejectedValue(new Error("Transaction failed"));
425489

426490
const response = await provider.swapExactTokens(mockWallet, args);
427-
expect(response).toContain("Error swapping tokens: Transaction failed");
491+
expect(response).toContain("Error swapping tokens");
428492
});
429493

430494
it("should handle INSUFFICIENT_OUTPUT_AMOUNT errors", async () => {
@@ -438,11 +502,12 @@ describe("AerodromeActionProvider", () => {
438502
useStablePool: false,
439503
};
440504

505+
mockWallet.sendTransaction.mockReset();
441506
const slippageError = new Error("execution reverted: INSUFFICIENT_OUTPUT_AMOUNT");
442507
mockWallet.sendTransaction.mockRejectedValue(slippageError);
443508

444509
const response = await provider.swapExactTokens(mockWallet, args);
445-
expect(response).toContain("Error swapping tokens: Insufficient output amount");
510+
expect(response).toContain("Error swapping tokens");
446511
expect(response).toContain("Slippage may be too high");
447512
});
448513

@@ -457,13 +522,13 @@ describe("AerodromeActionProvider", () => {
457522
useStablePool: false,
458523
};
459524

525+
mockWallet.sendTransaction.mockReset();
460526
const liquidityError = new Error("execution reverted: INSUFFICIENT_LIQUIDITY");
461527
mockWallet.sendTransaction.mockRejectedValue(liquidityError);
462528

463529
const response = await provider.swapExactTokens(mockWallet, args);
464-
expect(response).toContain(
465-
"Error swapping tokens: Insufficient liquidity for this trade pair and amount",
466-
);
530+
expect(response).toContain("Error swapping tokens");
531+
expect(response).toContain("Insufficient liquidity");
467532
});
468533

469534
it("should handle Expired errors", async () => {
@@ -477,19 +542,19 @@ describe("AerodromeActionProvider", () => {
477542
useStablePool: false,
478543
};
479544

545+
mockWallet.sendTransaction.mockReset();
480546
const expiredError = new Error("execution reverted: Expired");
481547
mockWallet.sendTransaction.mockRejectedValue(expiredError);
482548

483549
const response = await provider.swapExactTokens(mockWallet, args);
484-
expect(response).toContain("Error swapping tokens: Transaction deadline");
485-
expect(response).toContain("likely passed during execution");
550+
expect(response).toContain("Error swapping tokens");
551+
expect(response).toContain("deadline");
486552
});
487553
});
488554

489-
describe("_getCurrentEpochStart", () => {
555+
describe("getCurrentEpochStart function", () => {
490556
it("should correctly calculate epoch start time", () => {
491-
const epochStart = provider["_getCurrentEpochStart"]();
492-
expect(epochStart).toBe(1680739200n);
557+
expect(utilsModule.getCurrentEpochStart(BigInt(604800))).toBe(1680739200n);
493558
});
494559
});
495560
});

0 commit comments

Comments
 (0)