Skip to content
This repository has been archived by the owner on Jun 27, 2022. It is now read-only.

Commit

Permalink
setup
Browse files Browse the repository at this point in the history
  • Loading branch information
machard committed May 6, 2021
1 parent 6c6daa8 commit aeba097
Show file tree
Hide file tree
Showing 7 changed files with 531 additions and 116 deletions.
2 changes: 1 addition & 1 deletion packages/cryptoassets/data/dapps/ethereum.js

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions packages/hw-app-eth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ Ledger Hardware Wallet ETH JavaScript bindings.
- [Examples](#examples-6)
- [eth2SetWithdrawalIndex](#eth2setwithdrawalindex)
- [Parameters](#parameters-16)
- [setExternalPlugin](#setexternalplugin)
- [Parameters](#parameters-17)
- [getPluginForContractMethod](#getpluginforcontractmethod)
- [Parameters](#parameters-18)

### byContractAddress

Expand Down Expand Up @@ -364,3 +368,24 @@ It shall be run before the ETH 2 deposit transaction is signed. If not called, t
- `withdrawalIndex` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** index path in the EIP 2334 path m/12381/3600/withdrawalIndex/0

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)>** True if the method was executed successfully

#### setExternalPlugin

Set the name of the plugin that should be used to parse the next transaction

##### Parameters

- `pluginName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** string containing the name of the plugin, must have length between 1 and 30 bytes
- `contractAddress` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
- `selector` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)>** True if the method was executed successfully

### getPluginForContractMethod

Retrieve the name of a plugin compatible with a given contract address and a method selector

#### Parameters

- `contractAddress`
- `selector`
2 changes: 1 addition & 1 deletion packages/hw-app-eth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@ledgerhq/errors": "^5.48.0",
"@ledgerhq/hw-transport": "^5.48.0",
"bignumber.js": "^9.0.1",
"ethereum-tx-decoder": "^3.0.0",
"ethers": "^5.1.4",
"rlp": "^2.2.6"
},
"devDependencies": {
Expand Down
126 changes: 70 additions & 56 deletions packages/hw-app-eth/src/Eth.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { EthAppPleaseEnableContractData } from "@ledgerhq/errors";
import type Transport from "@ledgerhq/hw-transport";
import { BigNumber } from "bignumber.js";
import { encode, decode } from "rlp";
import { decodeTx } from "ethereum-tx-decoder";
import { ethers } from "ethers";
import { byContractAddress } from "./erc20";
import { getPluginForContractMethod } from "./plugins";

Expand Down Expand Up @@ -68,7 +68,6 @@ const remapTransactionRelatedErrors = (e) => {
*/
export default class Eth {
transport: Transport<*>;
contractBindings: Object;

constructor(transport: Transport<*>, scrambleKey: string = "w0w") {
this.transport = transport;
Expand Down Expand Up @@ -168,25 +167,11 @@ export default class Eth {
* @example
* import { byContractAddress } from "@ledgerhq/hw-app-eth/erc20"
* const zrxInfo = byContractAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
* if (zrxInfo) await appEth._provideERC20TokenInformation(zrxInfo)
* if (zrxInfo) await appEth.provideERC20TokenInformation(zrxInfo)
* const signed = await appEth.signTransaction(path, rawTxHex)
*/
_provideERC20TokenInformation({ data }: { data: Buffer }): Promise<boolean> {
return this.transport.send(0xe0, 0x0a, 0x00, 0x00, data).then(
() => true,
(e) => {
if (e && e.statusCode === 0x6d00) {
// this case happen for older version of ETH app, since older app version had the ERC20 data hardcoded, it's fine to assume it worked.
// we return a flag to know if the call was effective or not
return false;
}
throw e;
}
);
}

provideERC20TokenInformation({ data }: { data: Buffer }): Promise<boolean> {
return this._provideERC20TokenInformation({ data });
return _provideERC20TokenInformation.call(this, { data });
}

/**
Expand Down Expand Up @@ -250,19 +235,31 @@ export default class Eth {
offset += chunkSize;
}

let decodedTx = decodeTx("0x" + rawTxHex);
rlpTx = ethers.utils.RLP.decode("0x" + rawTxHex);

const decodedTx = {
data: rlpTx[5],
to: rlpTx[3],
};

if (decodedTx.data.length >= 10) {
const erc20Info = byContractAddress(decodedTx.to);
if (erc20Info) {
this._provideERC20TokenInformation(erc20Info);
_provideERC20TokenInformation.call(this, erc20Info);
}
let selector = decodedTx.data.substring(0, 10);
let { payload, signature } = getPluginForContractMethod(
decodedTx.to,
selector
);
if (payload && signature) {
this._setExternalPlugin(payload, signature);

const selector = decodedTx.data.substring(0, 10);
const plugin = getPluginForContractMethod(decodedTx.to, selector);

if (plugin) {
const { payload, signature /*, erc20OfInterest, abi*/ } = plugin;

// TODO
// use abi and erc20OfInterest to call _provideERC20TokenInformation
// with ethers
//

_setExternalPlugin.call(this, payload, signature);
}
}

Expand Down Expand Up @@ -1035,39 +1032,56 @@ eth.signPersonalMessage("44'/60'/0'/0/0", Buffer.from("test").toString("hex")).t
* @param pluginName string containing the name of the plugin, must have length between 1 and 30 bytes
* @return True if the method was executed successfully
*/
_setExternalPlugin(
payload: string,
signature: string
): Promise<boolean> {
let payloadBuffer = Buffer.from(payload, "hex");
let signatureBuffer = Buffer.from(signature, "hex");
let buffer = Buffer.concat([
payloadBuffer,
signatureBuffer
]);
return this.transport.send(0xe0, 0x12, 0x00, 0x00, buffer).then(
() => true,
(e) => {
if (e && e.statusCode === 0x6a80) {
// this case happen when the plugin name is too short or too long
return false;
} else if (e && e.statusCode === 0x6984) {
// this case happen when the plugin requested is not installed on the device
return false;
} else if (e && e.statusCode === 0x6d00) {
// this case happen when the plugin requested is not installed on the device
return false;
}
throw e;
}
);
}

setExternalPlugin(
pluginName: string,
contractAddress: string,
selector: string
): Promise<boolean> {
return this._setExternalPlugin(pluginName, contractAddress, selector);
return _setExternalPlugin.call(this, pluginName, contractAddress, selector);
}
}

// PRIVATE

function _provideERC20TokenInformation({
data,
}: {
data: Buffer,
}): Promise<boolean> {
return this.transport.send(0xe0, 0x0a, 0x00, 0x00, data).then(
() => true,
(e) => {
if (e && e.statusCode === 0x6d00) {
// this case happen for older version of ETH app, since older app version had the ERC20 data hardcoded, it's fine to assume it worked.
// we return a flag to know if the call was effective or not
return false;
}
throw e;
}
);
}

function _setExternalPlugin(
payload: string,
signature: string
): Promise<boolean> {
let payloadBuffer = Buffer.from(payload, "hex");
let signatureBuffer = Buffer.from(signature, "hex");
let buffer = Buffer.concat([payloadBuffer, signatureBuffer]);
return this.transport.send(0xe0, 0x12, 0x00, 0x00, buffer).then(
() => true,
(e) => {
if (e && e.statusCode === 0x6a80) {
// this case happen when the plugin name is too short or too long
return false;
} else if (e && e.statusCode === 0x6984) {
// this case happen when the plugin requested is not installed on the device
return false;
} else if (e && e.statusCode === 0x6d00) {
// this case happen for older version of ETH app
return false;
}
throw e;
}
);
}
2 changes: 2 additions & 0 deletions packages/hw-app-eth/src/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const getPluginForContractMethod = (contractAddress, selector) => {
return {
payload: contractSelectors[selector]["serialized_data"],
signature: contractSelectors[selector]["signature"],
erc20OfInterest: contractSelectors[selector]["erc20OfInterest"],
abi: contractSelectors[selector]["abi"],
};
}
}
Expand Down
44 changes: 42 additions & 2 deletions script/crypto-assets-importer/importers/ethereum-dapps.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const path = require("path");
const { readFileJSON } = require("../utils");

const mapObject = (obj, fn) => Object.fromEntries(Object.entries(obj).map(fn));

module.exports = {
paths: ["dapps/ethereum"],
output: "data/dapps/ethereum.js",
Expand All @@ -16,6 +18,44 @@ module.exports = {
)
),

loader: ({ folder, id }) =>
readFileJSON(path.join(folder, id, "b2c_signatures.json")),
loader: async ({ folder, id }) => {
const [signatures, bare] = await Promise.all([
readFileJSON(path.join(folder, id, "b2c_signatures.json")),
readFileJSON(path.join(folder, id, "b2c.json")),
]);

const abisList = await Promise.all(
Object.keys(signatures).map((address) =>
readFileJSON(
path.join(folder, id, "abis", `${address}.abi.json`)
).catch(() => {})
)
);

const abis = Object.keys(signatures).reduce(
(acc, address, i) => ({
...acc,
[address]: abisList[i],
}),
{}
);

return bare.contracts.reduce(
(acc, contract) => ({
...acc,
[contract.address]: mapObject(
contract.selectors,
([selector, data]) => [
selector,
{
...signatures[contract.address][selector],
erc20OfInterest: data.erc20OfInterest,
abi: abis[contract.address],
},
]
),
}),
{}
);
},
};
Loading

0 comments on commit aeba097

Please sign in to comment.