1.node.js をインストールする
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
nvm install 18
nvm use 18
nvm alias default 18
npm install npm --global # Upgrade npm to the latest version
2. ヘルメットを取り付ける
2.1 hardhat インストール ディレクトリの作成
mkdir hardhat
cd hardhat
2.2 ヘルメットの取り付け
npm install --save-dev hardhat
ls
# 安装后的目录
# node_modules package-lock.json package.json
2.3 hardhat がインストールされているディレクトリで hardhat を実行する
npx hardhat
hardhat 構成ファイルの作成を選択します
2.4 ハードハット アーキテクチャ
Hardhat は、**タスク (タスク) とプラグイン (プラグイン)** の概念に基づいて設計されています。**Hardhat** の機能のほとんどはプラグインに由来し、開発者は使用するプラグインを自由に選択できます。
2.5 タスク
CLI から Hardhat を実行するたびに、タスクが実行されます。たとえば、npx hardhat compile はコンパイル タスクを実行しています。プロジェクトで現在利用可能なタスクを表示するには、npx hardhat を実行します。npx hardhat help [タスク] を実行すると、任意のタスクを調べることができます。
2.6 プラグイン (プラグイン)
Hardhat は選択するツールを制限しませんが、いくつかのプラグインが組み込まれており、それらはすべてオーバーライドすることもできます。ほとんどの場合、特定のツールを使用する方法は、それをプラグインとして Hardhat に統合することです。
このチュートリアルでは、hardhat-toolbox プラグインを使用します。イーサリアムとやり取りし、それらを介してコントラクトをテストします。それらの使用法は後で説明します。それらをインストールするには、プロジェクト ディレクトリで実行します。
npm install --save-dev @nomicfoundation/hardhat-toolbox
次のコードを hardhar.config.js ファイルに追加します。
require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.18",
};
3. スマート コントラクトの作成、コンパイル、テスト、およびデバッグ
3.1 簡単なスマート コントラクトを作成する
hardhat ディレクトリの下に新しいディレクトリを作成します。
mkdir contracts
cd contracts
Token.sol スマート コントラクトを追加
//SPDX-License-Identifier: UNLICENSED
// Solidity files have to start with this pragma.
// It will be used by the Solidity compiler to validate its version.
pragma solidity ^0.8.9;
// This is the main building block for smart contracts.
contract Token {
// Some string type variables to identify the token.
string public name = "My Hardhat Token";
string public symbol = "MHT";
// The fixed amount of tokens, stored in an unsigned integer type variable.
uint256 public totalSupply = 1000000;
// An address type variable is used to store ethereum accounts.
address public owner;
// A mapping is a key/value map. Here we store each account's balance.
mapping(address => uint256) balances;
// The Transfer event helps off-chain applications understand
// what happens within your contract.
event Transfer(address indexed _from, address indexed _to, uint256 _value);
/**
* Contract initialization.
*/
constructor() {
// The totalSupply is assigned to the transaction sender, which is the
// account that is deploying the contract.
balances[msg.sender] = totalSupply;
owner = msg.sender;
}
/**
* A function to transfer tokens.
*
* The `external` modifier makes a function *only* callable from *outside*
* the contract.
*/
function transfer(address to, uint256 amount) external {
// Check if the transaction sender has enough tokens.
// If `require`'s first argument evaluates to `false` then the
// transaction will revert.
require(balances[msg.sender] >= amount, "Not enough tokens");
// Transfer the amount.
balances[msg.sender] -= amount;
balances[to] += amount;
// Notify off-chain applications of the transfer.
emit Transfer(msg.sender, to, amount);
}
/**
* Read only function to retrieve the token balance of a given account.
*
* The `view` modifier indicates that it doesn't modify the contract's
* state, which allows us to call it without executing a transaction.
*/
function balanceOf(address account) external view returns (uint256) {
return balances[account];
}
}
3.2 コンパイル
npx hardhat compile
3.3 テスト
テスト フォルダーを作成し、テスト ファイル Token.js を作成します。
mkdir test
cd test
const { expect } = require("chai");
describe("Token contract", function () {
it("Deployment should assign the total supply of tokens to the owner", async function () {
const [owner] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const hardhatToken = await Token.deploy();
const ownerBalance = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
});
テストコマンドを実行
npx hardhat test
3.4 スマート コントラクトのデバッグ
Hardhat には、開発用に設計された Ethereum ネットワークである Hardhat Network が組み込まれています。コントラクトをデプロイし、テストを実行し、コードをデバッグできます。これは、Hardhat が接続されているデフォルトのネットワークであるため、動作するように設定する必要はありません。テストを実行するだけで準備完了です。
Solidity で console.log を使用して
Hardhat Network でコントラクトとテストを実行する場合、Solidity コードで console.log() を呼び出して、ログ情報とコントラクト変数を出力できます。使用する前に、コントラクト コードから **Hardhat** console.log をインポートする必要があります。
変更は契約のみ可能
pragma solidity ^0.8.9;
import "hardhat/console.sol";
contract Token {
//...
}
function transfer(address to, uint256 amount) external {
require(balances[msg.sender] >= amount, "Not enough tokens");
console.log(
"Transferring from %s to %s %s tokens",
msg.sender,
to,
amount
);
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
}
テストファイルを変更する
// This is an example test file. Hardhat will run every *.js file in `test/`,
// so feel free to add new ones.
// Hardhat tests are normally written with Mocha and Chai.
// We import Chai to use its asserting functions here.
const { expect } = require("chai");
// We use `loadFixture` to share common setups (or fixtures) between tests.
// Using this simplifies your tests and makes them run faster, by taking
// advantage of Hardhat Network's snapshot functionality.
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
// `describe` is a Mocha function that allows you to organize your tests.
// Having your tests organized makes debugging them easier. All Mocha
// functions are available in the global scope.
//
// `describe` receives the name of a section of your test suite, and a
// callback. The callback must define the tests of that section. This callback
// can't be an async function.
describe("Token contract", function () {
// We define a fixture to reuse the same setup in every test. We use
// loadFixture to run this setup once, snapshot that state, and reset Hardhat
// Network to that snapshot in every test.
async function deployTokenFixture() {
// Get the ContractFactory and Signers here.
const Token = await ethers.getContractFactory("Token");
const [owner, addr1, addr2] = await ethers.getSigners();
// To deploy our contract, we just have to call Token.deploy() and await
// its deployed() method, which happens once its transaction has been
// mined.
const hardhatToken = await Token.deploy();
await hardhatToken.deployed();
// Fixtures can return anything you consider useful for your tests
return { Token, hardhatToken, owner, addr1, addr2 };
}
// You can nest describe calls to create subsections.
describe("Deployment", function () {
// `it` is another Mocha function. This is the one you use to define each
// of your tests. It receives the test name, and a callback function.
//
// If the callback function is async, Mocha will `await` it.
it("Should set the right owner", async function () {
// We use loadFixture to setup our environment, and then assert that
// things went well
const { hardhatToken, owner } = await loadFixture(deployTokenFixture);
// `expect` receives a value and wraps it in an assertion object. These
// objects have a lot of utility methods to assert values.
// This test expects the owner variable stored in the contract to be
// equal to our Signer's owner.
expect(await hardhatToken.owner()).to.equal(owner.address);
});
it("Should assign the total supply of tokens to the owner", async function () {
const { hardhatToken, owner } = await loadFixture(deployTokenFixture);
const ownerBalance = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function () {
const { hardhatToken, owner, addr1, addr2 } = await loadFixture(
deployTokenFixture
);
// Transfer 50 tokens from owner to addr1
await expect(
hardhatToken.transfer(addr1.address, 50)
).to.changeTokenBalances(hardhatToken, [owner, addr1], [-50, 50]);
// Transfer 50 tokens from addr1 to addr2
// We use .connect(signer) to send a transaction from another account
await expect(
hardhatToken.connect(addr1).transfer(addr2.address, 50)
).to.changeTokenBalances(hardhatToken, [addr1, addr2], [-50, 50]);
});
it("Should emit Transfer events", async function () {
const { hardhatToken, owner, addr1, addr2 } = await loadFixture(
deployTokenFixture
);
// Transfer 50 tokens from owner to addr1
await expect(hardhatToken.transfer(addr1.address, 50))
.to.emit(hardhatToken, "Transfer")
.withArgs(owner.address, addr1.address, 50);
// Transfer 50 tokens from addr1 to addr2
// We use .connect(signer) to send a transaction from another account
await expect(hardhatToken.connect(addr1).transfer(addr2.address, 50))
.to.emit(hardhatToken, "Transfer")
.withArgs(addr1.address, addr2.address, 50);
});
it("Should fail if sender doesn't have enough tokens", async function () {
const { hardhatToken, owner, addr1 } = await loadFixture(
deployTokenFixture
);
const initialOwnerBalance = await hardhatToken.balanceOf(owner.address);
// Try to send 1 token from addr1 (0 tokens) to owner.
// `require` will evaluate false and revert the transaction.
await expect(
hardhatToken.connect(addr1).transfer(owner.address, 1)
).to.be.revertedWith("Not enough tokens");
// Owner balance shouldn't have changed.
expect(await hardhatToken.balanceOf(owner.address)).to.equal(
initialOwnerBalance
);
});
});
});
テストを実行
npx hardhat test
出力ログを参照
4. コントラクトをネットワークにデプロイする
4.1 scripts フォルダーの作成と deploy.js ファイルの追加
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
console.log("Account balance:", (await deployer.getBalance()).toString());
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy();
console.log("Token address:", token.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
4.2 hardhat 自身のネットワーク インスタンスへの公開
npx hardhat run scripts/deploy.js
アカウントとのコントラクトの展開: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
アカウント残高: 1000000000000000000000
トークン アドレス: 0x5FbDB2315678afecb367f032d93F642f64180aa3
4.3 Ganache ネットワークへの公開
構成ファイル hardhat.config.js を変更します
require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.18",
networks: {
deployment: {
url: `http://127.0.0.1:7545`,
accounts: ["bc549ba74b85bf9cc89309fc094cd868ff39215570b64271e19be9c1977df279"]
}
}
};
npx hardhat run scripts/deploy.js --network deployment
アカウントとのコントラクトの展開: 0xEb110D13835ff1e9B65320682601634D041dD505
アカウント残高: 98996267410475486878
トークン アドレス: 0x3f4f9B074bfd9B0C4920FD28b374e2B8E7B618f4
ガナッシュがすでに文書化されていることを確認してください
4.4 sepolia テストネットへの公開
構成ファイル hardhat.config.js を変更します
require("@nomicfoundation/hardhat-toolbox");
// Go to https://infura.io, sign up, create a new API key
// in its dashboard, and replace "KEY" with it
const INFURA_API_KEY = "KEY";
// Replace this private key with your Sepolia account private key
// To export your private key from Metamask, open Metamask and
// go to Account Details > Export Private Key
// Beware: NEVER put real Ether into testing accounts
const SEPOLIA_PRIVATE_KEY = "YOUR SEPOLIA PRIVATE KEY";
module.exports = {
solidity: "0.8.9",
networks: {
sepolia: {
url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,
accounts: [SEPOLIA_PRIVATE_KEY]
}
}
};
npx hardhat run scripts/deploy.js --network sepolia
私は試していませんが、興味のある方は試してみてください。
参照文書: