generated from NomicFoundation/hardhat-ts-plugin-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Design behind @medga/hardhat-clone | ||
|
||
@medga/hardhat-clone is built on top of Etherscan, which offers us a ton of verified source code of on-chain smart contracts and their compiler settings. | ||
|
||
## Clone Metadata | ||
|
||
Each cloned contract will generate a `CloneMetadats` stored in `.clone.meta` file in the project root. | ||
Clone metadata contains: | ||
- The contract name | ||
- On-chain address | ||
- Compiler version and settings | ||
- The folder into which the contract is cloned | ||
- The main source file that defines the cloned contract. | ||
- A mapping from the original source name (provided by Etherscan) to the actual file path cloned in the folder. | ||
|
||
`CloneMetadata` is defined in [meta.ts](src/clone/meta.ts). | ||
|
||
The `.clone.meta` file is capable of storing metadata of multiple cloned contracts, i.e., one can clone multiple contracts from on chain. | ||
|
||
## Source Remapping | ||
|
||
Some necessary remapping are performed when dumping source files of cloned contracts. | ||
|
||
- If the source name obtained from Etherscan is an absolute path: | ||
- We remove the leading slash `/` to make it a relative path. | ||
- If the source name include `node_modules`: | ||
- Hardhat has [special logic](https://github.com/MEDGA-eth/hardhat/blob/e79a633f2ab09c2fcd0bf37c5863d9545ebf5c47/packages/hardhat-core/src/utils/source-names.ts#L59-L90) when resolving source files with `node_modules` in their path, so we replace `node_moduels` with `node-modules` in source file path. | ||
|
||
After remapping, we dump the source files into the destination folder, preserving the relative file path structure. | ||
|
||
## Instrumentation in Hardhat's Compilation Pipeline | ||
|
||
@medga/hardhat-clone extends Hardhat project configuration and overrides several subtasks of Hardhat's Compilation Pipeline to compile cloned contracts. | ||
The extension of configuration and overrided subtasks can be found in [config-extensions.ts](src/config-extensions.ts). | ||
|
||
For each cloned contract, its compiler settings are dynamically added into Hardhat project configuration using `solidity.overrides` field as described in the Hardhat [document](https://hardhat.org/hardhat-runner/docs/advanced/multiple-solidity-versions#multiple-solidity-versions). | ||
|
||
The source files of cloned contracts are not in the normal source folder of Hardhat projects (by default `contracts` folder in project root). | ||
@medga/hardhat-clone overrides the `TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS` subtask in compilation pipeline to include source files of cloned contracts. | ||
|
||
As mentioned before, some source files are remapped when we dump files from Etherscan. | ||
In addition, some contracts also have [solc remappings](https://docs.soliditylang.org/en/latest/path-resolution.html#import-remapping) defined in their compiler settings. | ||
To apply these remappings, we override `TASK_COMPILE_GET_REMAPPINGS` subtask to instrument these remappings into the dependency resolution of Hardhat compilation pipeline. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
# @medga/hardhat-clone Usage Tutorial | ||
|
||
This tutorial showcases the basic usage of `@medga/hardhat-clone` by clone [WETH](https://etherscan.io/address/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2#code) contract from Etherscan into a Hardhat project and build a new `MyToken` contract on top of it. | ||
|
||
## Create a Hardhat Project | ||
|
||
```shell | ||
mkdir /tmp/hardhat_project | ||
cd /tmp/hardhat_project | ||
npm init | ||
npm i --save-dev hardhat | ||
npx hardhat init | ||
``` | ||
|
||
These commands create a new folder at `/tmp/hardhat_project` and initialize a Hardhat project in it. | ||
|
||
## Install @medga/hardhat-clone Plugin | ||
|
||
Install the `@medga/hardhat-clone` package: | ||
```shell | ||
npm i --save-dev @medga/hardhat-clone | ||
``` | ||
|
||
Load `@medga/hardhat-clone` plugin in Hardhat configuration file `hardhat.config.js` (or equivalent): | ||
```javascript | ||
require("@nomicfoundation/hardhat-toolbox"); | ||
require("@medga/hardhat-clone"); // <== load `@medga/hardhat-clone` plugin | ||
|
||
/** @type import('hardhat/config').HardhatUserConfig */ | ||
module.exports = { | ||
solidity: "0.8.24", | ||
}; | ||
``` | ||
|
||
## Clone ERC404 Contract | ||
|
||
`@medga/hardhat-clone` provides `hardhat clone` command to clone on-chain verified contracts into a folder inside the Hardhat project. | ||
|
||
```shell | ||
npx hardhat clone --etherscan-api-key <API_KEY> 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 WETH | ||
``` | ||
|
||
You may see such output, indicating the successful clone of ERC404 contract: | ||
```text | ||
Cloning contract at address 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 from Ethereum to WETH | ||
Fetching source code for 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 | ||
Fetching https://api.etherscan.io/api?action=getsourcecode&address=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2&module=contract&apikey=6FTP6N6HH43PTTJ89P9VKKKZWRMV4NH245 | ||
Dumping source tree... | ||
Dumping WETH9.sol to /tmp/p1/WETH | ||
Fetching creation information... | ||
Fetching https://api.etherscan.io/api?action=getcontractcreation&contractaddresses=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2&module=contract&apikey=6FTP6N6HH43PTTJ89P9VKKKZWRMV4NH245 | ||
Constructing CloneMetadata... | ||
Appending to clone metadata file... | ||
Successfully cloned contract to WETH | ||
``` | ||
|
||
The Etherscan API Key `--etherscan-api-key` can be obtained from Etherscan for free: https://info.etherscan.com/api-keys/. | ||
The last positional argument specifies the folder (relative to the root of current Hardhat project) into which the contract will be cloned. | ||
|
||
In this case, we clone ERC404 contract into the `WETH` folder at the root of the project: | ||
```text | ||
❯ tree -L 1 | ||
. | ||
├── contracts | ||
├── WETH | ||
├── hardhat.config.js | ||
├── ignition | ||
├── node_modules | ||
├── package.json | ||
├── package-lock.json | ||
├── README.md | ||
└── test | ||
``` | ||
|
||
## Compile Cloned Contracts | ||
|
||
The key different between `@medga/hardhat-clone` and other similar plugins (e.g., [hardhat-etherscan-contract-cloner](https://www.npmjs.com/package/hardhat-etherscan-contract-cloner)) is that the cloned contracts are fully compilable and can be used by other locally developed contracts as dependencies! | ||
|
||
```shell | ||
npx hardhat compile | ||
``` | ||
|
||
The `hardhat compile` compiles the cloned contract just as other locally developed contracts. | ||
The compiler settings are preserved just as the verified contract on the blockchain. | ||
The compiler settings of cloned contracts are saved in the `.clone.meta` metadata file of project root. | ||
`@medga/hardhat-clone` instrument into the compilation pipeline of Hardhat, read the clone metadata and compile cloned contracts. | ||
|
||
You can see the compiled contracts in the `artifacts` folder: | ||
```text | ||
artifacts | ||
├── build-info | ||
│ ├── 22662767f8b1a09e64d324318d70c2ac.json | ||
│ └── 9eb729007ea6644485358ffff367f824.json | ||
├── contracts | ||
│ └── Lock.sol | ||
└── WETH | ||
└── WETH9.sol | ||
``` | ||
|
||
## Import Cloned Contracts and Develop New Contracts | ||
|
||
The cloned contracts are not isolated. They are just like local contract files and can be imported and used by other contracts. | ||
|
||
Let's create a new Solidity file `MyToken.sol` in `contracts` folder, the source folder of Hardhat project: | ||
```solidity | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.4.19; | ||
import { WETH9 } from "WETH/WETH9.sol"; | ||
contract MyToken is WETH9 { | ||
function getMyBalance() public view returns (uint) { | ||
return balanceOf[msg.sender]; | ||
} | ||
} | ||
``` | ||
|
||
Note that `WETH9` contract is compiled with Solidity version `0.4.19`, so our `MyToken.sol` file should also use `0.4.19`. | ||
Remember to declare `0.4.19` compiler in `hardhat.config.js`. | ||
```js | ||
require("@nomicfoundation/hardhat-toolbox"); | ||
require("@medga/hardhat-clone"); | ||
|
||
/** @type import('hardhat/config').HardhatUserConfig */ | ||
module.exports = { | ||
solidity: { | ||
compilers: [ | ||
{version: "0.8.24"}, | ||
{version: "0.4.19"}, // compiler for MyToken.sol | ||
] | ||
} | ||
}; | ||
``` | ||
`@medga/hardhat-clone` indeed automatically use `0.4.19` compiler version when compiling `WETH9` contract previously. | ||
However, our `MyToken.sol` is out of the cloned source files, so we need to specify compiler version for `MyToken.sol` here. | ||
|
||
`MyToken` contract inherits the cloned `WETH9` contract and define a new function `getMyBalance()` to return the token balance of caller (`msg.sender`). | ||
|
||
Additionally, we create a test file `MyToken.js` in `test` folder for `MyToken` contract: | ||
```javascript | ||
const { | ||
loadFixture, | ||
} = require("@nomicfoundation/hardhat-toolbox/network-helpers"); | ||
const { expect } = require("chai"); | ||
|
||
describe("MyToken", function () { | ||
async function deployMyTokenFixture() { | ||
// Contracts are deployed using the first signer/account by default | ||
const [owner, otherAccount] = await ethers.getSigners(); | ||
|
||
const Contract = await ethers.getContractFactory("MyToken"); | ||
const contract = await Contract.deploy(); | ||
|
||
return { contract, owner }; | ||
} | ||
|
||
it("deposit and get my balance", async function () { | ||
const { contract, owner } = await loadFixture(deployMyTokenFixture); | ||
|
||
await contract.deposit({ value: 1_000_000 }); | ||
expect(await contract.getMyBalance()).to.gt(0); | ||
}); | ||
}); | ||
``` | ||
|
||
The test file defines a test case, which deploy `MyToken` contract, deposit `1000000 wei` into it and check balance. | ||
|
||
Run test: | ||
```shell | ||
npx hardhat test | ||
``` | ||
|
||
All tests passes: | ||
```text | ||
❯ npx hardhat test | ||
Compiled 2 Solidity files successfully (evm targets: unknown evm version for solc version 0.4.19). | ||
MyToken | ||
✔ deposit and get my balance | ||
1 passing (48ms) | ||
``` | ||
|
||
Have fun! | ||
|
||
##### Want to know how @medga/hardhat-clone makes this possible? | ||
|
||
See our technical design: [./DESIGN.md]. | ||
|