import nock from "nock";
import { BigNumber } from "@ethersproject/bignumber";
import { AddressZero } from "@ethersproject/constants";
import { type Transaction } from "@ethersproject/transactions";
import SignatureCALEth from "../fixtures/SignatureCALEth";
import {
  UNISWAP_EXECUTE_SELECTOR,
  UNISWAP_UNIVERSAL_ROUTER_ADDRESS,
} from "../../src/modules/Uniswap/constants";
import {
  getCommandsAndTokensFromUniswapCalldata,
  isSupported,
  loadInfosForUniswap,
} from "../../src/modules/Uniswap";

nock.disableNetConnect();

describe("Uniswap", () => {
  describe("index", () => {
    describe("isSupported", () => {
      it("should return false for a non-Uniswap transaction", () => {
        expect(isSupported("0x", AddressZero, 1, [])).toBe(false);
      });

      it("should return false for a Uniswap transaction with an invalid selector", () => {
        expect(isSupported("0x123456", UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [])).toBe(false);
      });

      it("should return false for a Uniswap transaction with no commands", () => {
        expect(isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [])).toBe(
          false,
        );
      });

      it("should return false for a transaction of multiple hops with different pool versions", () => {
        expect(
          isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
            ["V2_SWAP_EXACT_IN", ["0x1", "0x2"]],
            ["V3_SWAP_EXACT_IN", ["0x2", "0x3"]],
          ]),
        ).toBe(false);
      });

      it("should return false for a transaction of multiple hops with non chained assets", () => {
        expect(
          isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
            ["V2_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
            ["V2_SWAP_EXACT_IN", ["0x0A", "0x0C"]],
          ]),
        ).toBe(false);
      });

      it("should return false for a transaction of multiple hops with non chained assets", () => {
        expect(
          isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
            ["V2_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
            ["V2_SWAP_EXACT_IN", ["0x0A", "0x0C"]],
          ]),
        ).toBe(false);
      });

      it("should return true for a valid Uniswap transaction", () => {
        expect(
          isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
            ["V2_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
            ["V2_SWAP_EXACT_IN", ["0x0B", "0x0C"]],
          ]),
        ).toBe(true);
        expect(
          isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
            ["V3_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
            ["V3_SWAP_EXACT_IN", ["0x0B", "0x0C"]],
          ]),
        ).toBe(true);
        expect(
          isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
            ["V3_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
            ["V3_SWAP_EXACT_OUT", ["0x0B", "0x0C"]],
          ]),
        ).toBe(true);
        expect(
          isSupported(UNISWAP_EXECUTE_SELECTOR, UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 1, [
            ["V2_SWAP_EXACT_IN", ["0x0A", "0x0B"]],
            ["V2_SWAP_EXACT_OUT", ["0x0B", "0x0C"]],
          ]),
        ).toBe(true);
      });
    });

    describe("getCommandsAndTokensFromUniswapCalldata", () => {
      it("should return the commands and tokens from a Uniswap calldata", () => {
        // see tx 0xc4df7ccc0527541d0e80856a8f38deedc48c84825e9355469ba02d873502ce2f
        const calldata =
          "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669b9ec100000000000000000000000000000000000000000000000000000000000000030a010c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000016000000000000000000000000055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000066c32b0d0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000669b9ec100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000410d756f55acf289e9754faf91bba0a704b5c7c0aa4b1dfd551115ccbe4c7f290234e1a14265e1da0bc872a23627d997fe37a689c290d519f7b8c9bdde1b79108e1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000030ba49cbff5a00000000000000000000000000000000000000000000000089677c957272141800000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000030ba49cbff5a000";

        expect(getCommandsAndTokensFromUniswapCalldata(calldata, 1)).toEqual([
          ["PERMIT2_PERMIT", []],
          [
            "V3_SWAP_EXACT_OUT",
            [
              "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
              "0x55747be9f9f5beb232ad59fe7af013b81d95fd5e",
            ],
          ],
          ["UNWRAP_ETH", ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"]],
        ]);
      });

      it("should return an undefined command for unsupported commands by the Uniswap plugin", () => {
        // see tx 0xc0668eb799ba8b73a396529a183b67e7905c0f70143680915ee5802b3036257b
        const calldata =
          "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669b9f4700000000000000000000000000000000000000000000000000000000000000030b000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000004fdf8403a58c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000ff436c9cd1052265be510e00a661f432c539080000000000000000000000000000000000000000000000000004fdf8403a58c80000000000000000000000000000000000000000000ac736c3566d2e76b2c5978300000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20027103ffeea07a27fab7ad1df5297fa75e77a43cb5790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f0279540eed3fb666e8aaa4571a47e7478b6e9d000000000000000000000000000000000000000000000000000ce8a624ca9800";

        expect(getCommandsAndTokensFromUniswapCalldata(calldata, 1)).toEqual([
          ["WRAP_ETH", ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"]],
          [
            "V3_SWAP_EXACT_IN",
            [
              "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
              "0x3ffeea07a27fab7ad1df5297fa75e77a43cb5790",
            ],
          ],
          [undefined, []],
        ]);
      });

      it("should return an empty array for an invalid selector", () => {
        const calldata =
          "0x123456000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669b9ec100000000000000000000000000000000000000000000000000000000000000030a010c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000016000000000000000000000000055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000066c32b0d0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000669b9ec100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000410d756f55acf289e9754faf91bba0a704b5c7c0aa4b1dfd551115ccbe4c7f290234e1a14265e1da0bc872a23627d997fe37a689c290d519f7b8c9bdde1b79108e1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000030ba49cbff5a00000000000000000000000000000000000000000000000089677c957272141800000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000030ba49cbff5a000";
        expect(getCommandsAndTokensFromUniswapCalldata(calldata, 1)).toEqual([]);
      });
    });

    describe("loadInfosForUniswap", () => {
      it("should return ERC20 & plugin descriptors for a valid Uniswap transaction", async () => {
        // see tx 0x88a065f47c82545b0620378d6cb2231713464dd58c0a2b8d6e9485740807b573
        const calldata =
          "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669bad2800000000000000000000000000000000000000000000000000000000000000040a08060c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000bdaa645097ef80f9d475f341d0d107a45b3a000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000687ce06c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000687ce06c00000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004142794d71565541a435d1bc910db84510c4d31ad35ff8c046222b2c232fcd99a6256a8f1abe7d42a44f3d92d65f9232db76df2fbef3dfa76eca64b034ed4df5321c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000001bffcca36d953d12266cb000000000000000000000000000000000000000000000000027c3dea3f90dafd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000bdaa645097ef80f9d475f341d0d107a45b3a000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006eb940753b4b52fbec8d33c418133fdb0d4405e6000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000001dc813a4ee4410f144ae2122051c1cfc436f24a00000000000000000000000000000000000000000000000000275e122c92b911e";

        const transaction: Transaction = {
          to: UNISWAP_UNIVERSAL_ROUTER_ADDRESS,
          data: calldata,
          gasLimit: BigNumber.from(21_000),
          nonce: 0,
          value: BigNumber.from(0),
          chainId: 1,
        };

        const res = await loadInfosForUniswap(transaction, 1, { staticERC20Signatures: { 1: SignatureCALEth } });
        expect(res).toEqual({
          pluginData: Buffer.from(
            "07556e69737761703fc91a3afd70395cd496c647d5a6cc9d4b2b7fad3593564c3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935",
            "hex",
          ),
          tokenDescriptors: [
            Buffer.from(
              "0457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe",
              "hex",
            ),
          ],
        });
      });

      it("should return an empty object for an unsupported Uniswap transaction", async () => {
        const transaction: Transaction = {
          to: AddressZero,
          data: "0x",
          gasLimit: BigNumber.from(21_000),
          nonce: 0,
          value: BigNumber.from(0),
          chainId: 1,
        };

        const res = await loadInfosForUniswap(transaction, 1);
        expect(res).toEqual({});
      });
    });
  });
});
