Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
aussedatlo committed Feb 11, 2025
1 parent b6a508d commit 09666b9
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 68 deletions.
4 changes: 2 additions & 2 deletions packages/device-management-kit/src/api/utils/HexaString.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export type HexaString = `0x${string}`;

export const isHexaString = (value: string): value is HexaString => {
return /^0x[0-9a-fA-F]*$/.test(value);
export const isHexaString = (value: unknown): value is HexaString => {
return typeof value === "string" && /^0x[0-9a-fA-F]*$/.test(value);
};

export const hexaStringToBuffer = (value: string): Uint8Array | null => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Interface, type TransactionDescription } from "ethers";
import { Right } from "purify-ts";

import { ClearSignContextType } from "@/shared/model/ClearSignContext";
import { type TransactionContext } from "@/shared/model/TransactionContext";
import { type HttpTokenDataSource } from "@/token/data/HttpTokenDataSource";
import {
UNISWAP_EXECUTE_SELECTOR,
UNISWAP_UNIVERSAL_ROUTER_ADDRESS,
} from "@/uniswap/constants/uniswap";
import { type CommandDecoderDataSource } from "@/uniswap/data/CommandDecoderDataSource";
import { DefaultCommandDecoderDataSource } from "@/uniswap/data/DefaultCommandDecoderDataSource";
import { EthersAbiDecoderDataSource } from "@/uniswap/data/EthersAbiDecoderDataSource";
Expand All @@ -18,71 +23,139 @@ describe("UniswapContextLoader", () => {
};
let loader: UniswapContextLoader;

beforeEach(() => {
vi.spyOn(tokenDataSourceMock, "getTokenInfosPayload").mockImplementation(
({ address }) => Promise.resolve(Right(`payload-${address}`)),
);
loader = new UniswapContextLoader(
new DefaultCommandDecoderDataSource(new EthersAbiDecoderDataSource()),
tokenDataSourceMock as unknown as HttpTokenDataSource,
);
});
describe("load", () => {
describe("with tokenDataSourceMock", () => {
beforeEach(() => {
vi.spyOn(
tokenDataSourceMock,
"getTokenInfosPayload",
).mockImplementation(({ address }) =>
Promise.resolve(Right(`payload-${address}`)),
);
loader = new UniswapContextLoader(
new DefaultCommandDecoderDataSource(new EthersAbiDecoderDataSource()),
tokenDataSourceMock as unknown as HttpTokenDataSource,
);
});

describe("load", async () => {
it("should return the external plugin context and the decoded contexts", async () => {
// GIVEN
// https://etherscan.io/getRawTx?tx=0xc9821c32c1782d9ab6baf6f9ca4b2565bce11aaf5146a2d303215bda026f5e48
const transactionContext = {
chainId: 1,
data: "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000067a4855b000000000000000000000000000000000000000000000000000000000000000300060c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000a18f07d736b90be5500000000000000000000000000000000000000000000000000000000b0b8122abd8412900000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bb9f599ce614feb2e1bbe58f180f370d05b39344e002710c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000017cc6042605381c158d2adab487434bde79aa61c000000000000000000000000000000000000000000000000000000000000005b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000b0b8122abd84129c001a053efc49d03b694742500f49aa09df2360bd7e297fa730c745e0d4320b42fc33fa01ca1c85a46c561f624830fffbae2441ba0f2d54eb5b0f17e0e3561d10db0b7ee",
} as TransactionContext;

// WHEN
const result = await loader.load(transactionContext);

// THEN
expect(result).toEqual([
{
type: ClearSignContextType.EXTERNAL_PLUGIN,
payload:
"07556e69737761703fc91a3afd70395cd496c647d5a6cc9d4b2b7fad3593564c3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935",
},
{
type: "token",
payload: "payload-0xb9f599ce614feb2e1bbe58f180f370d05b39344e",
}, // ERC20
{
type: "token",
payload: "payload-0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
}, // WETH
]);
});
it("should return the external plugin context and the decoded contexts", async () => {
// GIVEN
// https://etherscan.io/getRawTx?tx=0xc9821c32c1782d9ab6baf6f9ca4b2565bce11aaf5146a2d303215bda026f5e48
const transactionContext = {
chainId: 1,
data: "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000067a4855b000000000000000000000000000000000000000000000000000000000000000300060c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000a18f07d736b90be5500000000000000000000000000000000000000000000000000000000b0b8122abd8412900000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bb9f599ce614feb2e1bbe58f180f370d05b39344e002710c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000017cc6042605381c158d2adab487434bde79aa61c000000000000000000000000000000000000000000000000000000000000005b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000b0b8122abd84129c001a053efc49d03b694742500f49aa09df2360bd7e297fa730c745e0d4320b42fc33fa01ca1c85a46c561f624830fffbae2441ba0f2d54eb5b0f17e0e3561d10db0b7ee",
} as TransactionContext;

// WHEN
const result = await loader.load(transactionContext);

// THEN
expect(result).toEqual([
{
type: ClearSignContextType.EXTERNAL_PLUGIN,
payload:
"07556e69737761703fc91a3afd70395cd496c647d5a6cc9d4b2b7fad3593564c3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935",
},
{
type: "token",
payload: "payload-0xb9f599ce614feb2e1bbe58f180f370d05b39344e",
}, // ERC20
{
type: "token",
payload: "payload-0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
}, // WETH
]);
});

it("should return an empty array if the transaction is not supported", async () => {
// GIVEN
const transactionContext = {} as TransactionContext;
vi.spyOn(commandDecoderMock, "decode").mockReturnValue([]);

// WHEN
const result = await loader.load(transactionContext);

// THEN
expect(result).toEqual([]);
});

it("should return an empty array if the transaction is not supported", async () => {
// GIVEN
const transactionContext = {} as TransactionContext;
vi.spyOn(commandDecoderMock, "decode").mockReturnValue([]);
it("should return an empty array if a command is not supported", async () => {
// GIVEN
// https://etherscan.io/getRawTx?tx=0xa628c8b3da2ad984aed0354d8ca4b4c3c42941491dc4e0d6a5f423fd65e6c90a
// The command 0x05 (TRANSFER) is not supported
const transactionContext = {
chainId: 1,
data: "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000067a4bef600000000000000000000000000000000000000000000000000000000000000050b0105040c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000078e6708e70aed0f000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000059a16770000000000000000000000000000000000000000000000000078e6708e70aed0f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000027213e28d7fda5c57fe9e5dd923818dbccf71c4700000000000000000000000000000000000000000000000000000000003938700000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006f939b43d65be049b7533907db08e80ba6969f450000000000000000000000000000000000000000000000000000000059682f0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000006f939b43d65be049b7533907db08e80ba6969f4500000000000000000000000000000000000000000000000000000000000000000cc080a07c86dd5813ec1f3725c402f8bea6105546d1093c59676023b971f1d83f61bd4ea02a353bf3028cabcbe68c5dc8906752bafe55afc86b6344edb3ffd8a3650332e8",
} as TransactionContext;

// WHEN
const result = await loader.load(transactionContext);
// WHEN
const result = await loader.load(transactionContext);

// THEN
expect(result).toEqual([]);
// THEN
expect(result).toEqual([]);
});
});

it("should return an empty array if a command is not supported", async () => {
// GIVEN
// https://etherscan.io/getRawTx?tx=0xa628c8b3da2ad984aed0354d8ca4b4c3c42941491dc4e0d6a5f423fd65e6c90a
const transactionContext = {
chainId: 1,
data: "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000067a4bef600000000000000000000000000000000000000000000000000000000000000050b0105040c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000078e6708e70aed0f000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000059a16770000000000000000000000000000000000000000000000000078e6708e70aed0f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000027213e28d7fda5c57fe9e5dd923818dbccf71c4700000000000000000000000000000000000000000000000000000000003938700000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006f939b43d65be049b7533907db08e80ba6969f450000000000000000000000000000000000000000000000000000000059682f0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000006f939b43d65be049b7533907db08e80ba6969f4500000000000000000000000000000000000000000000000000000000000000000cc080a07c86dd5813ec1f3725c402f8bea6105546d1093c59676023b971f1d83f61bd4ea02a353bf3028cabcbe68c5dc8906752bafe55afc86b6344edb3ffd8a3650332e8",
} as TransactionContext;
describe("with mocked ethers parseTransaction", () => {
beforeEach(() => {
loader = new UniswapContextLoader(
commandDecoderMock,
tokenDataSourceMock as unknown as HttpTokenDataSource,
);
});

it("should return an empty array if no command are returned from parseTransaction", async () => {
// GIVEN
const transactionContext = {
to: UNISWAP_UNIVERSAL_ROUTER_ADDRESS,
data: UNISWAP_EXECUTE_SELECTOR,
} as TransactionContext;
vi.spyOn(Interface.prototype, "parseTransaction").mockReturnValue({
args: [""],
} as TransactionDescription);

// WHEN
const result = await loader.load(transactionContext);

// THEN
expect(result).toEqual([]);
});

it("should return an empty array if no inputs are returned from parseTransaction", async () => {
// GIVEN
const transactionContext = {
to: UNISWAP_UNIVERSAL_ROUTER_ADDRESS,
data: UNISWAP_EXECUTE_SELECTOR,
} as TransactionContext;
vi.spyOn(Interface.prototype, "parseTransaction").mockReturnValue({
args: ["0x08", []],
} as TransactionDescription);

// WHEN
const result = await loader.load(transactionContext);

// THEN
expect(result).toEqual([]);
});

it("should return an empty array if parseTransaction throws an error", async () => {
// GIVEN
const transactionContext = {
to: UNISWAP_UNIVERSAL_ROUTER_ADDRESS,
data: UNISWAP_EXECUTE_SELECTOR,
} as TransactionContext;
vi.spyOn(Interface.prototype, "parseTransaction").mockImplementation(
() => {
throw new Error();
},
);

// WHEN
const result = await loader.load(transactionContext);
// WHEN
const result = await loader.load(transactionContext);

// THEN
expect(result).toEqual([]);
// THEN
expect(result).toEqual([]);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ export class UniswapContextLoader implements ContextLoader {
return [];
}

const externalPluginContext = this._getExternalPluginUniswapContext();
const tokenContexts = await this._extractClearSignContexts(
transaction.data,
transaction.chainId,
);
const externalPluginContext = this._getExternalPluginUniswapContext();

if (tokenContexts.length > 0)
return [externalPluginContext, ...tokenContexts];

Expand Down Expand Up @@ -84,13 +85,8 @@ export class UniswapContextLoader implements ContextLoader {
]);

const tx = iface.parseTransaction({ data: calldata });
const commands = tx?.args[0] as HexaString;
const inputs = tx?.args[1] as HexaString[];

console.log("calldata", calldata);
console.log("tx", tx);
console.log("commands", commands);
console.log("inputs", inputs);
const commands: unknown = tx?.args[0];
const inputs: unknown = tx?.args[1];

if (!isHexaString(commands) || !this._isHexaStringArray(inputs)) {
return [];
Expand Down Expand Up @@ -141,14 +137,16 @@ export class UniswapContextLoader implements ContextLoader {
.map((b) => `0x${b}` as HexaString)
.map((b) => UNISWAP_COMMANDS[b]);

// if one command is not supported, return an empty array
// as the plugin can't handle this kind of transaction
if (extractedCommands.some((cmd) => cmd === undefined)) {
return [];
}

return extractedCommands as UniswapSupportedCommand[];
}

private _isHexaStringArray(array: unknown[]): array is HexaString[] {
private _isHexaStringArray(array: unknown): array is HexaString[] {
return (
Array.isArray(array) &&
array.every((item) => typeof item === "string" && isHexaString(item))
Expand Down

0 comments on commit 09666b9

Please sign in to comment.