@ledgerhq/hw-app-eth 7.0.0-next.0 → 7.0.0-nightly.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/.unimportedrc.json +35 -3
- package/CHANGELOG.md +21 -5
- package/README.md +2 -1
- package/lib/Eth.d.ts.map +1 -1
- package/lib/Eth.js +1 -0
- package/lib/Eth.js.map +1 -1
- package/lib/modules/Uniswap/constants.d.ts +24 -0
- package/lib/modules/Uniswap/constants.d.ts.map +1 -0
- package/lib/modules/Uniswap/constants.js +59 -0
- package/lib/modules/Uniswap/constants.js.map +1 -0
- package/lib/modules/Uniswap/decoders.d.ts +3 -0
- package/lib/modules/Uniswap/decoders.d.ts.map +1 -0
- package/lib/modules/Uniswap/decoders.js +46 -0
- package/lib/modules/Uniswap/decoders.js.map +1 -0
- package/lib/modules/Uniswap/index.d.ts +44 -0
- package/lib/modules/Uniswap/index.d.ts.map +1 -0
- package/lib/modules/Uniswap/index.js +143 -0
- package/lib/modules/Uniswap/index.js.map +1 -0
- package/lib/modules/Uniswap/types.d.ts +3 -0
- package/lib/modules/Uniswap/types.d.ts.map +1 -0
- package/lib/modules/Uniswap/types.js +3 -0
- package/lib/modules/Uniswap/types.js.map +1 -0
- package/lib/services/ledger/index.d.ts.map +1 -1
- package/lib/services/ledger/index.js +20 -5
- package/lib/services/ledger/index.js.map +1 -1
- package/lib/services/types.d.ts +1 -0
- package/lib/services/types.d.ts.map +1 -1
- package/lib/utils.js +1 -1
- package/lib/utils.js.map +1 -1
- package/lib-es/Eth.d.ts.map +1 -1
- package/lib-es/Eth.js +1 -0
- package/lib-es/Eth.js.map +1 -1
- package/lib-es/modules/Uniswap/constants.d.ts +24 -0
- package/lib-es/modules/Uniswap/constants.d.ts.map +1 -0
- package/lib-es/modules/Uniswap/constants.js +56 -0
- package/lib-es/modules/Uniswap/constants.js.map +1 -0
- package/lib-es/modules/Uniswap/decoders.d.ts +3 -0
- package/lib-es/modules/Uniswap/decoders.d.ts.map +1 -0
- package/lib-es/modules/Uniswap/decoders.js +43 -0
- package/lib-es/modules/Uniswap/decoders.js.map +1 -0
- package/lib-es/modules/Uniswap/index.d.ts +44 -0
- package/lib-es/modules/Uniswap/index.d.ts.map +1 -0
- package/lib-es/modules/Uniswap/index.js +137 -0
- package/lib-es/modules/Uniswap/index.js.map +1 -0
- package/lib-es/modules/Uniswap/types.d.ts +3 -0
- package/lib-es/modules/Uniswap/types.d.ts.map +1 -0
- package/lib-es/modules/Uniswap/types.js +2 -0
- package/lib-es/modules/Uniswap/types.js.map +1 -0
- package/lib-es/services/ledger/index.d.ts.map +1 -1
- package/lib-es/services/ledger/index.js +20 -5
- package/lib-es/services/ledger/index.js.map +1 -1
- package/lib-es/services/types.d.ts +1 -0
- package/lib-es/services/types.d.ts.map +1 -1
- package/lib-es/utils.js +1 -1
- package/lib-es/utils.js.map +1 -1
- package/package.json +10 -5
- package/src/Eth.ts +1 -0
- package/src/modules/Uniswap/constants.ts +60 -0
- package/src/modules/Uniswap/decoders.ts +62 -0
- package/src/modules/Uniswap/index.ts +168 -0
- package/src/modules/Uniswap/types.ts +14 -0
- package/src/services/ledger/index.ts +22 -5
- package/src/services/types.ts +2 -0
- package/src/utils.ts +1 -1
- package/tests/Eth.unit.test.ts +65 -3
- package/tests/Uniswap/decoders.unit.test.ts +101 -0
- package/tests/Uniswap/index.unit.test.ts +188 -0
- package/tests/fixtures/utils.ts +8 -1
- package/tests/ledgerService.unit.test.ts +113 -19
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { log } from "@ledgerhq/logs";
|
|
2
|
+
import { utils, Transaction } from "ethers";
|
|
3
|
+
import { byContractAddressAndChainId, findERC20SignaturesInfo } from "../../services/ledger/erc20";
|
|
4
|
+
import { LoadConfig } from "../../services/types";
|
|
5
|
+
import { UniswapDecoders } from "./decoders";
|
|
6
|
+
import { CommandsAndTokens } from "./types";
|
|
7
|
+
import {
|
|
8
|
+
SWAP_COMMANDS,
|
|
9
|
+
UNISWAP_EXECUTE_SELECTOR,
|
|
10
|
+
UNISWAP_UNIVERSAL_ROUTER_ADDRESS,
|
|
11
|
+
UNISWAP_COMMANDS,
|
|
12
|
+
} from "./constants";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @ignore for external documentation
|
|
16
|
+
*
|
|
17
|
+
* Indicates if the given calldata is supported by the Uniswap plugin
|
|
18
|
+
* applying some basic checks and testing some assumptions
|
|
19
|
+
* specific to the Uniswap plugin internals
|
|
20
|
+
*
|
|
21
|
+
* @param {`0x${string}`} calldata
|
|
22
|
+
* @param {string | undefined} to
|
|
23
|
+
* @param {number} chainId
|
|
24
|
+
* @param {CommandsAndTokens} commandsAndTokens
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
export const isSupported = (
|
|
28
|
+
calldata: `0x${string}`,
|
|
29
|
+
to: string | undefined,
|
|
30
|
+
chainId: number,
|
|
31
|
+
commandsAndTokens: CommandsAndTokens,
|
|
32
|
+
): boolean => {
|
|
33
|
+
const selector = calldata.slice(0, 10);
|
|
34
|
+
const contractAddress = to?.toLowerCase();
|
|
35
|
+
if (
|
|
36
|
+
selector !== UNISWAP_EXECUTE_SELECTOR ||
|
|
37
|
+
contractAddress !== UNISWAP_UNIVERSAL_ROUTER_ADDRESS ||
|
|
38
|
+
!commandsAndTokens.length
|
|
39
|
+
) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let endingAsset;
|
|
44
|
+
for (let i = 0; i < commandsAndTokens.length; i++) {
|
|
45
|
+
const [command, tokens] = commandsAndTokens[i];
|
|
46
|
+
|
|
47
|
+
if (!command) return false;
|
|
48
|
+
if (!SWAP_COMMANDS.includes(command)) continue;
|
|
49
|
+
|
|
50
|
+
const poolVersion = command.slice(0, 2) as "V2" | "V3";
|
|
51
|
+
if (
|
|
52
|
+
endingAsset &&
|
|
53
|
+
// Chained swaps should work as a pipe regarding the traded assets:
|
|
54
|
+
// The last asset of swap 1 should be the first asset of swap 2
|
|
55
|
+
// and the same pool version should be used for both swaps
|
|
56
|
+
(endingAsset.asset !== tokens[0] || endingAsset.poolVersion !== poolVersion)
|
|
57
|
+
) {
|
|
58
|
+
return false;
|
|
59
|
+
} else {
|
|
60
|
+
endingAsset = {
|
|
61
|
+
poolVersion,
|
|
62
|
+
asset: tokens[tokens.length - 1],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @ignore for external documentation
|
|
72
|
+
*
|
|
73
|
+
* Provides a list of commands and associated tokens for a given Uniswap calldata
|
|
74
|
+
*
|
|
75
|
+
* @param {`0x${string}`} calldata
|
|
76
|
+
* @param {number} chainId
|
|
77
|
+
* @returns {CommandsAndTokens}
|
|
78
|
+
*/
|
|
79
|
+
export const getCommandsAndTokensFromUniswapCalldata = (
|
|
80
|
+
calldata: `0x${string}`,
|
|
81
|
+
chainId: number,
|
|
82
|
+
): CommandsAndTokens => {
|
|
83
|
+
try {
|
|
84
|
+
const [commands, inputs] = new utils.Interface([
|
|
85
|
+
"function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable",
|
|
86
|
+
]).decodeFunctionData("execute", calldata) as [`0x${string}`, `0x${string}`[]];
|
|
87
|
+
|
|
88
|
+
const commandsBuffer = Buffer.from(commands.slice(2), "hex");
|
|
89
|
+
return commandsBuffer.reduce((acc, curr, i) => {
|
|
90
|
+
const commandName = UNISWAP_COMMANDS[`0x${curr.toString(16).padStart(2, "0")}`];
|
|
91
|
+
if (!commandName) return [...acc, [undefined, []]];
|
|
92
|
+
|
|
93
|
+
const commandDecoder = UniswapDecoders[commandName];
|
|
94
|
+
return [...acc, [commandName, commandDecoder(inputs[i], chainId)]];
|
|
95
|
+
}, [] as CommandsAndTokens);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
log("Uniswap", "Error decoding Uniswap calldata", e);
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @ignore for external documentation
|
|
104
|
+
*
|
|
105
|
+
* Returns the necessary APDUs to load the Uniswap plugin
|
|
106
|
+
* and the token descriptors for a transaction
|
|
107
|
+
*
|
|
108
|
+
* @param {Transaction} transaction
|
|
109
|
+
* @param {number} chainId
|
|
110
|
+
* @param {LoadConfig} userConfig
|
|
111
|
+
* @returns {Promise<{ pluginData?: Buffer; tokenDescriptors?: Buffer[] }>}
|
|
112
|
+
*/
|
|
113
|
+
export const loadInfosForUniswap = async (
|
|
114
|
+
transaction: Transaction,
|
|
115
|
+
chainId: number,
|
|
116
|
+
userConfig?: LoadConfig,
|
|
117
|
+
): Promise<{ pluginData?: Buffer; tokenDescriptors?: Buffer[] }> => {
|
|
118
|
+
const selector = transaction.data.slice(0, 10) as `0x${string}`;
|
|
119
|
+
const commandsAndTokens = getCommandsAndTokensFromUniswapCalldata(
|
|
120
|
+
transaction.data as `0x${string}`,
|
|
121
|
+
chainId,
|
|
122
|
+
);
|
|
123
|
+
if (!isSupported(selector, transaction.to, chainId, commandsAndTokens)) {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const tokenDescriptorsPromises = Promise.all(
|
|
128
|
+
commandsAndTokens.flatMap(([, tokens]) =>
|
|
129
|
+
tokens.map(async token => {
|
|
130
|
+
const erc20SignaturesBlob = await findERC20SignaturesInfo(userConfig || {}, chainId);
|
|
131
|
+
return byContractAddressAndChainId(token, chainId, erc20SignaturesBlob)?.data;
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
const tokenDescriptors = await tokenDescriptorsPromises.then(descriptors =>
|
|
136
|
+
descriptors.filter((descriptor): descriptor is Buffer => !!descriptor),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const pluginName = "Uniswap";
|
|
140
|
+
// 1 byte for the length of the plugin name
|
|
141
|
+
const lengthBuff = Buffer.alloc(1);
|
|
142
|
+
lengthBuff.writeUIntBE(pluginName.length, 0, 1);
|
|
143
|
+
// dynamic length bytes for the plugin name
|
|
144
|
+
const pluginNameBuff = Buffer.from(pluginName);
|
|
145
|
+
// 20 bytes for the contract address
|
|
146
|
+
const contractAddressBuff = Buffer.from(UNISWAP_UNIVERSAL_ROUTER_ADDRESS.slice(2), "hex");
|
|
147
|
+
// 4 bytes for the selector
|
|
148
|
+
const selectorBuff = Buffer.from(UNISWAP_EXECUTE_SELECTOR.slice(2), "hex");
|
|
149
|
+
// 70 bytes for the signature
|
|
150
|
+
const signature = Buffer.from(
|
|
151
|
+
// Signature is hardcoded as it would create issues by being in the CAL ethereum.json file
|
|
152
|
+
"3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935",
|
|
153
|
+
"hex",
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const pluginData = Buffer.concat([
|
|
157
|
+
lengthBuff,
|
|
158
|
+
pluginNameBuff,
|
|
159
|
+
contractAddressBuff,
|
|
160
|
+
selectorBuff,
|
|
161
|
+
signature,
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
pluginData,
|
|
166
|
+
tokenDescriptors,
|
|
167
|
+
};
|
|
168
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type UniswapSupportedCommand =
|
|
2
|
+
| "V2_SWAP_EXACT_IN"
|
|
3
|
+
| "V2_SWAP_EXACT_OUT"
|
|
4
|
+
| "V3_SWAP_EXACT_IN"
|
|
5
|
+
| "V3_SWAP_EXACT_OUT"
|
|
6
|
+
| "WRAP_ETH"
|
|
7
|
+
| "UNWRAP_ETH"
|
|
8
|
+
| "PERMIT2_PERMIT"
|
|
9
|
+
| "PERMIT2_TRANSFER_FROM"
|
|
10
|
+
| "PERMIT2_PERMIT_BATCH"
|
|
11
|
+
| "PERMIT2_TRANSFER_FROM_BATCH"
|
|
12
|
+
| "PAY_PORTION";
|
|
13
|
+
|
|
14
|
+
export type CommandsAndTokens = [UniswapSupportedCommand | undefined, `0x${string}`[]][];
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
import { utils } from "ethers";
|
|
2
2
|
import { log } from "@ledgerhq/logs";
|
|
3
|
-
import { Interface } from "@ethersproject/abi";
|
|
4
3
|
import {
|
|
5
4
|
signDomainResolution,
|
|
6
5
|
signAddressResolution,
|
|
7
6
|
} from "@ledgerhq/domain-service/signers/index";
|
|
8
7
|
import { LedgerEthTransactionResolution, LedgerEthTransactionService, LoadConfig } from "../types";
|
|
8
|
+
import { decodeTxInfo, tokenSelectors, nftSelectors, mergeResolutions } from "../../utils";
|
|
9
|
+
import { UNISWAP_UNIVERSAL_ROUTER_ADDRESS } from "../../modules/Uniswap/constants";
|
|
9
10
|
import { byContractAddressAndChainId, findERC20SignaturesInfo } from "./erc20";
|
|
11
|
+
import { loadInfosForUniswap } from "../../modules/Uniswap";
|
|
10
12
|
import { loadInfosForContractMethod } from "./contracts";
|
|
11
13
|
import { getNFTInfo, loadNftPlugin } from "./nfts";
|
|
12
|
-
import { decodeTxInfo, tokenSelectors, nftSelectors, mergeResolutions } from "../../utils";
|
|
13
14
|
|
|
14
15
|
type PotentialResolutions = {
|
|
15
16
|
token: boolean | undefined;
|
|
16
17
|
nft: boolean | undefined;
|
|
17
18
|
externalPlugins: boolean | undefined;
|
|
19
|
+
uniswapV3: boolean | undefined;
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -112,7 +114,9 @@ const loadNanoAppPlugins = async (
|
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
|
|
117
|
+
// Uniswap has its own way of working, so we need to handle it separately
|
|
118
|
+
// This will prevent an error if we add Uniswap to the CAL service
|
|
119
|
+
if (shouldResolve.externalPlugins && contractAddress !== UNISWAP_UNIVERSAL_ROUTER_ADDRESS) {
|
|
116
120
|
const contractMethodInfos = await loadInfosForContractMethod(
|
|
117
121
|
contractAddress,
|
|
118
122
|
selector,
|
|
@@ -129,7 +133,7 @@ const loadNanoAppPlugins = async (
|
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
if (erc20OfInterest && erc20OfInterest.length && abi) {
|
|
132
|
-
const contract = new Interface(abi);
|
|
136
|
+
const contract = new utils.Interface(abi);
|
|
133
137
|
const args = contract.parseTransaction(decodedTx).args;
|
|
134
138
|
|
|
135
139
|
for (const path of erc20OfInterest) {
|
|
@@ -148,6 +152,7 @@ const loadNanoAppPlugins = async (
|
|
|
148
152
|
nft: false,
|
|
149
153
|
externalPlugins: false,
|
|
150
154
|
token: true, // enforcing resolution of tokens for external plugins that need info on assets (e.g. for a swap)
|
|
155
|
+
uniswapV3: false,
|
|
151
156
|
},
|
|
152
157
|
);
|
|
153
158
|
resolution = mergeResolutions([resolution, externalPluginResolution]);
|
|
@@ -158,6 +163,17 @@ const loadNanoAppPlugins = async (
|
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
|
|
166
|
+
if (shouldResolve.uniswapV3) {
|
|
167
|
+
const { pluginData, tokenDescriptors } = await loadInfosForUniswap(decodedTx, chainIdTruncated);
|
|
168
|
+
if (pluginData && tokenDescriptors) {
|
|
169
|
+
resolution.externalPlugin.push({
|
|
170
|
+
payload: pluginData.toString("hex"),
|
|
171
|
+
signature: "",
|
|
172
|
+
});
|
|
173
|
+
resolution.erc20Tokens.push(...tokenDescriptors.map(d => d.toString("hex")));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
161
177
|
return resolution;
|
|
162
178
|
};
|
|
163
179
|
|
|
@@ -185,6 +201,7 @@ const resolveTransaction: LedgerEthTransactionService["resolveTransaction"] = as
|
|
|
185
201
|
token: resolutionConfig.erc20 && tokenSelectors.includes(selector),
|
|
186
202
|
nft: resolutionConfig.nft && nftSelectors.includes(selector),
|
|
187
203
|
externalPlugins: resolutionConfig.externalPlugins,
|
|
204
|
+
uniswapV3: resolutionConfig.uniswapV3,
|
|
188
205
|
};
|
|
189
206
|
|
|
190
207
|
const pluginsResolution = await loadNanoAppPlugins(
|
package/src/services/types.ts
CHANGED
|
@@ -44,6 +44,8 @@ export type ResolutionConfig = {
|
|
|
44
44
|
erc20?: boolean;
|
|
45
45
|
// List of trusted names (ENS for now) to clear sign
|
|
46
46
|
domains?: DomainDescriptor[];
|
|
47
|
+
// activate uniswap v3 plugin resolution
|
|
48
|
+
uniswapV3?: boolean;
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
export type LedgerEthTransactionService = {
|
package/src/utils.ts
CHANGED
|
@@ -72,7 +72,7 @@ export const decodeTxInfo = (rawTx: Buffer) => {
|
|
|
72
72
|
data: rlpDecoded[5],
|
|
73
73
|
to: rlpDecoded[3],
|
|
74
74
|
// Default to 1 for non EIP 155 txs
|
|
75
|
-
chainId: rlpTx.length > 6 ? rlpTx[6] : Buffer.from("
|
|
75
|
+
chainId: rlpTx.length > 6 ? rlpTx[6] : Buffer.from("01", "hex"),
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
package/tests/Eth.unit.test.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import nock from "nock";
|
|
1
2
|
import axios from "axios";
|
|
2
3
|
import { fail } from "assert";
|
|
3
4
|
import { ethers } from "ethers";
|
|
@@ -11,10 +12,11 @@ import ERC1155_ABI from "./fixtures/ABI/ERC1155.json";
|
|
|
11
12
|
import PARASWAP_ABI from "./fixtures/ABI/PARASWAP.json";
|
|
12
13
|
import { ResolutionConfig } from "../src/services/types";
|
|
13
14
|
import ParaswapJSON from "./fixtures/REST/Paraswap-Plugin.json";
|
|
15
|
+
import { transactionContracts, transactionData } from "./fixtures/utils";
|
|
14
16
|
import { byContractAddressAndChainId } from "../src/services/ledger/erc20";
|
|
15
17
|
import { ERC1155_CLEAR_SIGNED_SELECTORS, ERC721_CLEAR_SIGNED_SELECTORS } from "../src/utils";
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
nock.disableNetConnect();
|
|
18
20
|
|
|
19
21
|
describe("Eth app biding", () => {
|
|
20
22
|
describe("clearSignTransaction", () => {
|
|
@@ -293,7 +295,7 @@ describe("Eth app biding", () => {
|
|
|
293
295
|
"44'/60'/0'/0/0",
|
|
294
296
|
ethers.utils
|
|
295
297
|
.serializeTransaction({
|
|
296
|
-
to:
|
|
298
|
+
to: transactionContracts.paraswap,
|
|
297
299
|
value: ethers.BigNumber.from("0"),
|
|
298
300
|
gasLimit: ethers.BigNumber.from("298891"),
|
|
299
301
|
maxPriorityFeePerGas: ethers.BigNumber.from("1150000000"),
|
|
@@ -322,7 +324,7 @@ describe("Eth app biding", () => {
|
|
|
322
324
|
type: 2,
|
|
323
325
|
})
|
|
324
326
|
.substring(2),
|
|
325
|
-
{ erc20: true, externalPlugins: true, nft: true },
|
|
327
|
+
{ erc20: true, externalPlugins: true, nft: true, uniswapV3: true },
|
|
326
328
|
true,
|
|
327
329
|
);
|
|
328
330
|
expect(result).toEqual({
|
|
@@ -333,6 +335,66 @@ describe("Eth app biding", () => {
|
|
|
333
335
|
expect(spy).toHaveBeenCalledTimes(3); // 1 time plugin json file + 2 times CAL signatures <-- FIXME 1 time should be enough
|
|
334
336
|
});
|
|
335
337
|
|
|
338
|
+
it("should clear sign the Uniswap Universal Router transaction", async () => {
|
|
339
|
+
const spy = jest.spyOn(axios, "get");
|
|
340
|
+
spy.mockImplementation(async url => {
|
|
341
|
+
if (url?.includes("erc20-signatures")) {
|
|
342
|
+
return { data: CAL_ETH } as any;
|
|
343
|
+
}
|
|
344
|
+
return Promise.reject({ response: { status: 404 } }) as any;
|
|
345
|
+
});
|
|
346
|
+
const transport = await openTransportReplayer(
|
|
347
|
+
RecordStore.fromString(`
|
|
348
|
+
=> e01200006607556e69737761703fc91a3afd70395cd496c647d5a6cc9d4b2b7fad3593564c3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935
|
|
349
|
+
<= 9000
|
|
350
|
+
=> e00a0000680457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe
|
|
351
|
+
<= 009000
|
|
352
|
+
=> e00a0000680457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe
|
|
353
|
+
<= 019000
|
|
354
|
+
=> e004000096058000002c8000003c80000000000000000000000002f9044f018084448b9b8085143fc44a5883048f8b943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b904243593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000
|
|
355
|
+
<= 9000
|
|
356
|
+
=> e00480009600000000000000000000000000669ba25a00000000000000000000000000000000000000000000000000000000000000030a010c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000
|
|
357
|
+
<= 9000
|
|
358
|
+
=> e0048000960000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000016000000000000000000000000055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000ffffffffffffffffffffffffffffff
|
|
359
|
+
<= 9000
|
|
360
|
+
=> e004800096ffffffffff0000000000000000000000000000000000000000000000000000000066c32ea60000000000000000000000000000000000000000000000000000000000000006000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000669ba25a0000000000000000000000000000000000
|
|
361
|
+
<= 9000
|
|
362
|
+
=> e0048000960000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000413cbf00ab90b6d1b17401cbf49e00c40f98bcb3f39461ca65e26009f9e9f77029279a4587efa2d792ea61ede56e0fbd7c1305007bc59d09bc60eaba46efa23edd1c0000000000000000000000000000000000000000000000000000000000000000000000000000
|
|
363
|
+
<= 9000
|
|
364
|
+
=> e0048000960000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000233a3559d9da00000000000000000000000000000000000000000000000062e76d8ff4b926e80000000000000000000000000000000000000000000000000000000000000
|
|
365
|
+
<= 9000
|
|
366
|
+
=> e0048000960000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e00000000000000000000000000000000000000000000000000000000000000000000000000000000
|
|
367
|
+
<= 9000
|
|
368
|
+
=> e00480004e0000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000233a3559d9da000c0
|
|
369
|
+
<= 00a42d6ead5539eb1ac552b65b46df4e8fd87c37da54a280d33dc7a78992b56bb02244776870b31cfb50ea71c0ea31a30e6e513bd9766f74d61dc52b523ac256879000
|
|
370
|
+
`),
|
|
371
|
+
);
|
|
372
|
+
const eth = new Eth(transport);
|
|
373
|
+
const result = await eth.clearSignTransaction(
|
|
374
|
+
"44'/60'/0'/0/0",
|
|
375
|
+
ethers.utils
|
|
376
|
+
.serializeTransaction({
|
|
377
|
+
to: transactionContracts.uniswapUniversaRouter,
|
|
378
|
+
value: ethers.BigNumber.from("0"),
|
|
379
|
+
gasLimit: ethers.BigNumber.from("298891"),
|
|
380
|
+
maxPriorityFeePerGas: ethers.BigNumber.from("1150000000"),
|
|
381
|
+
maxFeePerGas: ethers.BigNumber.from("86969174616"),
|
|
382
|
+
data: transactionData.uniswap["permit2>swap-out-v3>unwrap"],
|
|
383
|
+
chainId: 1,
|
|
384
|
+
nonce: 0,
|
|
385
|
+
type: 2,
|
|
386
|
+
})
|
|
387
|
+
.substring(2),
|
|
388
|
+
{ erc20: true, externalPlugins: true, nft: true, uniswapV3: true },
|
|
389
|
+
true,
|
|
390
|
+
);
|
|
391
|
+
expect(result).toEqual({
|
|
392
|
+
v: "00",
|
|
393
|
+
r: "a42d6ead5539eb1ac552b65b46df4e8fd87c37da54a280d33dc7a78992b56bb0",
|
|
394
|
+
s: "2244776870b31cfb50ea71c0ea31a30e6e513bd9766f74d61dc52b523ac25687",
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
336
398
|
it("should throw in case of error with strict mode", async () => {
|
|
337
399
|
const err = new Error("strictModeCatchThis");
|
|
338
400
|
jest.spyOn(ledgerService, "resolveTransaction").mockRejectedValue(err);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { WETH_PER_CHAIN_ID } from "../../src/modules/Uniswap/constants";
|
|
2
|
+
import { UniswapDecoders } from "../../src/modules/Uniswap/decoders";
|
|
3
|
+
|
|
4
|
+
describe("Uniswap", () => {
|
|
5
|
+
describe("decoders", () => {
|
|
6
|
+
it("should decode a V2_SWAP_EXACT_IN command input", () => {
|
|
7
|
+
// see tx 0x9d032ce73fb2c10432186087dae1b77f05fdd125982bfd04dfa47a23229dcd7f
|
|
8
|
+
const input =
|
|
9
|
+
"0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000008ef059372e0e255c6f18185b00000000000000000000000000000000000000000000000002520ee13087c56000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000069ee720c120ec7c9c52a625c04414459b3185f23000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
|
|
10
|
+
expect(UniswapDecoders["V2_SWAP_EXACT_IN"](input, 1)).toEqual([
|
|
11
|
+
"0x69ee720c120ec7c9c52a625c04414459b3185f23", // PEEZY
|
|
12
|
+
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH
|
|
13
|
+
]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should decode a V2_SWAP_EXACT_OUT command input", () => {
|
|
17
|
+
// see tx 0x6fcd1f97b5c7d365b7b3c3d426c7039f45f40bdb4e7f6ac7b7fbe45c7ffe2846
|
|
18
|
+
const input =
|
|
19
|
+
"0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000060a24181e400000000000000000000000000000000000000000000000000000d0853f8cc9607b000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000036b25341b2ff1bbc1b019b041ec7423a6cb21969000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
|
|
20
|
+
expect(UniswapDecoders["V2_SWAP_EXACT_OUT"](input, 1)).toEqual([
|
|
21
|
+
"0x36b25341b2ff1bbc1b019b041ec7423a6cb21969", // LAMBEAU
|
|
22
|
+
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should decode a V3_SWAP_EXACT_IN command input", () => {
|
|
27
|
+
// see tx 0xf70e9fd3397af35a01d99ec33eb2652baf9de4cbe2bc042ddfb693dacb2184f2
|
|
28
|
+
const input =
|
|
29
|
+
"0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000274d48fed3e800000000000000000000000000000000000000000000000064e8c5a11639f40000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000000000000000000000";
|
|
30
|
+
expect(UniswapDecoders["V3_SWAP_EXACT_IN"](input, 1)).toEqual([
|
|
31
|
+
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH
|
|
32
|
+
"0x55747be9f9f5beb232ad59fe7af013b81d95fd5e", // TBO
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should decode a V3_SWAP_EXACT_OUT command input", () => {
|
|
37
|
+
// see tx 0x12522daaa016979bf86c8732e2d86b975629c99ddacc86ca935d84a48a00988f
|
|
38
|
+
const input =
|
|
39
|
+
"0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000233a3559d9da00000000000000000000000000000000000000000000000062e76d8ff4b926e800000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000000000000000000000";
|
|
40
|
+
expect(UniswapDecoders["V3_SWAP_EXACT_OUT"](input, 1)).toEqual([
|
|
41
|
+
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH
|
|
42
|
+
"0x55747be9f9f5beb232ad59fe7af013b81d95fd5e", // TBO
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should decode a WRAP_ETH command input", () => {
|
|
47
|
+
// see tx 0xf9b0b54a822bae7df4826016661eb213ec934147b7a226ecf88c49cf6ba3ef0d
|
|
48
|
+
const input =
|
|
49
|
+
"0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000032a04cac0ca0000";
|
|
50
|
+
|
|
51
|
+
const supportedChainIds = Object.keys(WETH_PER_CHAIN_ID);
|
|
52
|
+
|
|
53
|
+
for (const chainId of supportedChainIds) {
|
|
54
|
+
const expectedAddress = WETH_PER_CHAIN_ID[chainId];
|
|
55
|
+
expect(UniswapDecoders["WRAP_ETH"](input, chainId)).toEqual([
|
|
56
|
+
expectedAddress instanceof Error ? undefined : expectedAddress.toLowerCase(),
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should decode a UNWRAP_ETH command input", () => {
|
|
62
|
+
// see tx 0xd2a99d50c3c2bb70040ec94e10aef6d558f8babc92474cfcc2a1aa29da9afdc5
|
|
63
|
+
const input =
|
|
64
|
+
"0x0000000000000000000000009580d04543fb19860925c649bb3a7741d7ec75d9000000000000000000000000000000000000000000000000011f162a2bc89bc4";
|
|
65
|
+
|
|
66
|
+
const supportedChainIds = Object.keys(WETH_PER_CHAIN_ID);
|
|
67
|
+
|
|
68
|
+
for (const chainId of supportedChainIds) {
|
|
69
|
+
const expectedAddress = WETH_PER_CHAIN_ID[chainId];
|
|
70
|
+
expect(UniswapDecoders["UNWRAP_ETH"](input, chainId)).toEqual([
|
|
71
|
+
expectedAddress instanceof Error ? undefined : expectedAddress.toLowerCase(),
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should return an empty array for other supported commands", () => {
|
|
77
|
+
const input =
|
|
78
|
+
"0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000204fce5e3e2502611000000000000000000000000000000000000000000000000000000001f942ac9a54ae2700000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000003ffeea07a27fab7ad1df5297fa75e77a43cb5790000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000ccae1bc46fb018dd396ed4c45565d4cb9d41098";
|
|
79
|
+
const otherCommandDecoders = Object.entries(UniswapDecoders)
|
|
80
|
+
.map(([command, decoder]) => {
|
|
81
|
+
if (
|
|
82
|
+
![
|
|
83
|
+
"V2_SWAP_EXACT_IN",
|
|
84
|
+
"V2_SWAP_EXACT_OUT",
|
|
85
|
+
"V3_SWAP_EXACT_IN",
|
|
86
|
+
"V3_SWAP_EXACT_OUT",
|
|
87
|
+
"WRAP_ETH",
|
|
88
|
+
"UNWRAP_ETH",
|
|
89
|
+
].includes(command)
|
|
90
|
+
) {
|
|
91
|
+
return decoder;
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
|
|
96
|
+
for (const decoder of otherCommandDecoders) {
|
|
97
|
+
expect(decoder!(input, 1)).toEqual([]);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|