Skip to content

Commit

Permalink
Add prettier setup (#88)
Browse files Browse the repository at this point in the history
Signed-off-by: Luis Mastrangelo <luis@swirldslabs.com>
  • Loading branch information
acuarica authored Oct 30, 2024
1 parent e8b453d commit 69777b4
Show file tree
Hide file tree
Showing 52 changed files with 15,045 additions and 14,428 deletions.
22 changes: 14 additions & 8 deletions .lintstagedrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,25 @@
module.exports = {
// lint-staged appends all matched files at the end of the command
// https://github.com/lint-staged/lint-staged?tab=readme-ov-file#what-commands-are-supported
'@hts-forking/out/HtsSystemContract.sol/HtsSystemContract.json': 'prettier --write',
'*.json': 'prettier --write',

// Override `--tab-width` to indent Markdown list items properly
'*.md': 'prettier --tab-width 2 --write',

// Check `package.json` files have appropriate `Apache-2.0` license and author
// https://github.com/lint-staged/lint-staged?tab=readme-ov-file#example-wrap-filenames-in-single-quotes-and-run-once-per-file
'package.json': files => files.flatMap(file => [
`grep --quiet '"license"\\s*:\\s*"Apache-2.0"' ${file}`,
`grep --quiet '"author"\\s*:\\s*"2024 Hedera Hashgraph, LLC"' ${file}`,
]),
'package.json': files =>
files.flatMap(file => [
`grep --quiet '"license"\\s*:\\s*"Apache-2.0"' ${file}`,
`grep --quiet '"author"\\s*:\\s*"2024 Hedera Hashgraph, LLC"' ${file}`,
]),

// Check Solidity files have appropriate `Apache-2.0` license identifier
// https://github.com/lint-staged/lint-staged?tab=readme-ov-file#example-wrap-filenames-in-single-quotes-and-run-once-per-file
'*.sol': files => files.map(file => `grep --quiet "SPDX-License-Identifier: Apache-2.0" ${file}`),
'*.sol': files =>
files.map(file => `grep --quiet "SPDX-License-Identifier: Apache-2.0" ${file}`),

// Apply linter rules. See `eslint.config.js` for details.
'*.js': 'eslint --fix',
// Apply linter and prettier rules.
// See `eslint.config.js` and `.prettierrc.json` for details.
'*.{js,d.ts}': ['eslint --fix', 'prettier --write'],
};
9 changes: 9 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"printWidth": 100,
"semi": true,
"singleQuote": true,
"arrowParens": "avoid",
"quoteProps": "preserve"
}
10 changes: 5 additions & 5 deletions @hardhat-forking-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ This plugin replaces the default HardHat provider with one specifically designed
It enables the following features

- **Hedera Precompile Support** Assigns the Hedera precompile code to the `0x167` address.
During tests, you'll be able to query Hedera token data as though they are stored on standard blockchains.
Currently, only fungible tokens are supported to a limited degree.
During tests, you'll be able to query Hedera token data as though they are stored on standard blockchains.
Currently, only fungible tokens are supported to a limited degree.
- **Token Proxy Address Storage** Sets up token proxy addresses by directly querying data from the Hedera MirrorNode, giving you access to real-time account balances, allowances, and token information (such as name, symbol, and decimals) in the IERC20 format for fungible tokens.
Please note that only fungible tokens are supported at this time.
Please note that only fungible tokens are supported at this time.

## Installation

Expand Down Expand Up @@ -45,14 +45,14 @@ Each time the `eth_call` method is invoked, the target address will be checked t
If the function selector in the `eth_call` request corresponds to any of the following functions, an additional operation will be performed, as described below:

| Function | Behavior |
|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `balanceOf(address)` | Sets the balance of the address (downloaded from MirrorNode) into the storage slot from where it will be queried. |
| `allowance(address,address)` | Sets the token spending allowance for the address from the first parameter to the second parameter, after retrieving the data from the MirrorNode, and stores it in the corresponding slot. |

Additionally, by loading the HTS and token code into the EVM, the following methods can be called on the token's address, functioning similarly to how they would on the actual Hedera EVM:

| Function | Behavior |
|-----------------------------------------|-----------------------------------------------------------------------------------------------|
| --------------------------------------- | --------------------------------------------------------------------------------------------- |
| `name()` | Returns the token's name. |
| `symbol()` | Returns the token's symbol. |
| `decimals()` | Returns the token's decimals. |
Expand Down
2 changes: 1 addition & 1 deletion @hardhat-forking-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@
"ethereum-cryptography": "^3.0.0",
"sinon": "^19.0.2"
}
}
}
19 changes: 10 additions & 9 deletions @hardhat-forking-plugin/src/hardhat-addresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,28 @@

const { HDKey } = require('ethereum-cryptography/hdkey');
const { mnemonicToSeedSync } = require('ethereum-cryptography/bip39');
const { privateToAddress } = require("@nomicfoundation/ethereumjs-util");
const { privateToAddress } = require('@nomicfoundation/ethereumjs-util');

/**
* Gets the public Ethereum address from the respective private key.
*
*
* @param {Uint8Array} pk the private key to convert.
* @returns {string} the Ethereum address corresponding to the `pk` private key.
*/
const toHexStrAddress = pk => '0x' + Buffer.from(privateToAddress(pk)).toString('hex');
const getEvmAddress = pk => '0x' + Buffer.from(privateToAddress(pk)).toString('hex');

module.exports = {

/**
* Returns the addresses used by the Hardhat network from its `accounts` configuration.
*
* @param {import("hardhat/types").HardhatNetworkAccountsConfig} accounts
*
* @param {import("hardhat/types").HardhatNetworkAccountsConfig} accounts
* @returns {string[]}
*/
getAddresses(accounts) {
if (Array.isArray(accounts)) {
return accounts.map(account => toHexStrAddress(Buffer.from(account.privateKey.replace('0x', ''), 'hex')))
return accounts.map(account =>
getEvmAddress(Buffer.from(account.privateKey.replace('0x', ''), 'hex'))
);
} else {
const seed = mnemonicToSeedSync(accounts.mnemonic, accounts.passphrase);
const masterKey = HDKey.fromMasterSeed(seed);
Expand All @@ -48,11 +49,11 @@ module.exports = {
const derived = masterKey.derive(`${accounts.path}/${i}`);
if (derived.privateKey === null) continue;

const address = toHexStrAddress(derived.privateKey);
const address = getEvmAddress(derived.privateKey);
addresses.push(address);
}

return addresses;
}
},
};
};
48 changes: 22 additions & 26 deletions @hardhat-forking-plugin/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,30 @@ const chains = {
/**
* Extends the Hardhat config to setup hardfork history for Hedera networks and
* determine whether system contract emulation should be activated.
*
*
* ### Hardfork History Setup
*
*
* This avoids the problem `No known hardfork for execution on historical block`
* when running a forked network against a Hedera network.
*
* Note that the Hardhat config will be extended regardless of the selected network.
* This is to simplify the `extendConfig` logic and it does not harm having the extra chains config.
*
*
* We use `shanghai` as the default hardfork because setting this to `cancun` leads to the error
*
*
* ```
* internal error: entered unreachable code: error: Header(ExcessBlobGasNotSet)
* ```
*
*
* which looks like an issue in EDR
* https://github.com/NomicFoundation/edr/blob/8aded8ba38da741b6591a9550ab1f71cd138528e/crates/edr_evm/src/block/builder.rs#L99-L103.
* Nevertheless `cancun` opcodes work fine when running a Hardhat forked network.
*
* https://hardhat.org/hardhat-network/docs/guides/forking-other-networks#using-a-custom-hardfork-history
* https://hardhat.org/hardhat-network/docs/reference#chains
*
*
* ### Configuration of HTS emulation
*
*
* When `chainId` is present in the `forking` property, we try to activate HTS emulation.
* In this step we only setup the config.
* See `extendEnvironment` for its actual activation.
Expand All @@ -70,7 +70,7 @@ extendConfig((config, userConfig) => {
if (hardhatChains.get(chainId) === undefined) {
debug(`Setting hardfork history for chain ${chainId}`);
hardhatChains.set(Number(chainId), {
hardforkHistory: new Map().set('shanghai', 0)
hardforkHistory: new Map().set('shanghai', 0),
});
} else {
debug(`Hardfork history for chain ${chainId} set by the user`);
Expand All @@ -80,11 +80,9 @@ extendConfig((config, userConfig) => {
const forking = userConfig.networks?.hardhat?.forking;
if (forking !== undefined && 'chainId' in forking) {
const chainId = Number(forking.chainId);
const mirrorNodeUrl = chains[/**@type{keyof typeof chains}*/(chainId)];
const mirrorNodeUrl = chains[/**@type{keyof typeof chains}*/ (chainId)];
if (mirrorNodeUrl !== undefined) {
const workerPort = 'workerPort' in forking
? Number(forking.workerPort)
: 1234;
const workerPort = 'workerPort' in forking ? Number(forking.workerPort) : 1234;
const forkingConfig = config.networks.hardhat.forking;
// Should always be true
if (forkingConfig !== undefined) {
Expand All @@ -104,20 +102,20 @@ extendConfig((config, userConfig) => {
* It creates a service `Worker` that intercepts the JSON-RPC calls made to the remote network.
* This is called directly by EDR,
* so we can hook into `eth_getCode` and `eth_getStorageAt` to provide HTS emulation.
*
*
* This needs to be done in the `extendEnvironment` (or `extendConfig`) because it changes the `forking.url`.
* The `forking.url` is then used to create an `EdrProviderWrapper` instance.
* The [`extendProvider`](https://hardhat.org/hardhat-runner/docs/advanced/building-plugins#extending-the-hardhat-provider)
* hook runs after the `EdrProviderWrapper` has been constructed,
* and it cannot be changed directly from the `EdrProviderWrapper` instance.
*
*
* This creates a problem because the `extendEnvironment` callback cannot be `async`.
* Essentially the main issue is "since Hardhat needs to be loadable via `require` call, configuration must be synchronous".
* See the following issues for more details
*
*
* - https://github.com/NomicFoundation/hardhat/issues/3287
* - https://github.com/NomicFoundation/hardhat/issues/2496
*
*
* That's why we need to shift the setting of `chainId` and `workerPort` to the user,
* so we can update the `forking.url` if needed **synchronously**.
*/
Expand All @@ -133,16 +131,14 @@ extendEnvironment(hre => {
worker.on('error', err => console.log(err));
worker.on('exit', code => debug(`Worker exited with code ${code}`));

hre.jsonRPCForwarder = new Promise(
resolve => {
worker.on('message', message => {
if (message.listening) {
debug(`JSON-RPC Forwarder listening on port :${message.port}...`);
resolve(worker);
}
});
}
);
hre.jsonRPCForwarder = new Promise(resolve => {
worker.on('message', message => {
if (message.listening) {
debug(`JSON-RPC Forwarder listening on port :${message.port}...`);
resolve(worker);
}
});
});
worker.unref();
process.on('exit', code => debug(`Main process exited with code ${code}`));
forking.url = `http://127.0.0.1:${forking.workerPort}`;
Expand Down
40 changes: 27 additions & 13 deletions @hardhat-forking-plugin/src/json-rpc-forwarder.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,40 @@ const { getHtsCode, getHtsStorageAt } = require('../../@hts-forking/src');
const { HTSAddress } = require('../../@hts-forking/src/utils');

/** @type {Partial<import('hardhat/types').HardhatNetworkForkingConfig>} */
const { url: forkingUrl, blockNumber, mirrorNodeUrl, workerPort, hardhatAddresses = [] } = workerData;

debug(c.yellow('Starting JSON-RPC Relay Forwarder server on :%d, forking url=%s blockNumber=%d mirror node url=%s'), workerPort, forkingUrl, blockNumber, mirrorNodeUrl);
const {
url: forkingUrl,
blockNumber,
mirrorNodeUrl,
workerPort,
hardhatAddresses = [],
} = workerData;

debug(
c.yellow(
'Starting JSON-RPC Relay Forwarder server on :%d, forking url=%s blockNumber=%d mirror node url=%s'
),
workerPort,
forkingUrl,
blockNumber,
mirrorNodeUrl
);

/**
* Function signature for `eth_*` method handlers.
* The `params` argument comes from the JSON-RPC request,
* so it must to be declared as `unknown[]`.
* Therefore, each method handler should validate each element of `params` they use.
*
*
* When this handler returns `null`
* it means the original call needs to be **forwarded** to the remote JSON-RPC Relay.
*
* @typedef {(params: unknown[], requestIdPrefix: string) => Promise<unknown | null>} EthHandler
* @typedef {(params: unknown[], reqIdPrefix: string) => Promise<unknown | null>} EthHandler
*/

/**
*
*
*/
const eth = {

/** @type {EthHandler} */
eth_getBalance: async ([address, _blockNumber]) => {
assert(typeof address === 'string');
Expand All @@ -71,15 +84,16 @@ const eth = {
},

/** @type {EthHandler} */
eth_getStorageAt: async ([address, slot, blockNumber], requestIdPrefix) => {
eth_getStorageAt: async ([address, slot, blockNumber], reqIdPrefix) => {
assert(mirrorNodeUrl !== undefined);

assert(typeof address === 'string');
assert(typeof slot === 'string');
const mirrorNodeClient = new MirrorNodeClient(mirrorNodeUrl, Number(blockNumber));
const logger = { trace: debug };

// @ts-ignore
return await getHtsStorageAt(address, slot, mirrorNodeClient, { trace: debug }, requestIdPrefix);
// @ts-expect-error: Argument of type 'MirrorNodeClient' is not assignable to parameter of type 'IMirrorNodeClient'.
return await getHtsStorageAt(address, slot, mirrorNodeClient, logger, reqIdPrefix);
},
};

Expand All @@ -97,8 +111,8 @@ const server = http.createServer(function (req, res) {
const { jsonrpc, id, method, params } = JSON.parse(body);
assert(jsonrpc === '2.0', 'Only JSON-RPC 2.0 allowed');

const response = await async function () {
const handler = eth[/**@type{keyof typeof eth}*/(method)];
const response = await (async function () {
const handler = eth[/**@type{keyof typeof eth}*/ (method)];
if (handler !== undefined) {
const result = await handler(params, `[Request ID: ${id}]`);
if (result !== null) {
Expand All @@ -119,7 +133,7 @@ const server = http.createServer(function (req, res) {
}

return await result.text();
}();
})();

res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
Expand Down
Loading

0 comments on commit 69777b4

Please sign in to comment.