ブロックチェーン技術ブログ

ブロックチェーンと英語、数学に関するブログ

Hardhatを使って、EtherWalletと対話してみる

今回は、Hardhatを利用して、Contractをデプロイをして、コンソールでいろいろ試してみたいと思います.
デプロイするコントラクトは、下記のリンクから引用したEtherWalletです.

https://solidity-by-example.org/app/ether-wallet

送金とオーナーによる引き落とし機能を持つシンプルなウォレットです.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

contract EtherWallet {
    address payable public owner;

    constructor() {
        owner = payable(msg.sender);
    }

    receive() external payable {}

    function withdraw(uint _amount) external {
        require(msg.sender == owner, "caller is not owner");
        payable(msg.sender).transfer(_amount);
    }

    function getBalance() external view returns (uint) {
        return address(this).balance;
    }
}

(上記のリンクから引用)

Hardhatのボイラープレートをクローン

以下のリンクから、ボイラープレートをクローンします.
https://github.com/NomicFoundation/hardhat-boilerplate

contracts の下に、EtherWallet.sol を作成し、↑のソースコードをペーストします.

また、npm install を実行して、依存関係のあるモジュールをインストールします.

Hardhat Console

事前に、先頭のアカウントを取得しておきます.

外部アカウントの取得

// 20個のアカウントが用意されている
> const signers = await ethers.getSigners()
> signers.length
20

// 先頭のアカウントを取得する
> const [signer] = await ethers.getSigners()
> signer.address
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'

コントラクトのデプロイ

// ContractFactoryのインスタンスを取得して、デプロイする
> const EtherWallet = await ethers.getContractFactory('EtherWallet')
> const etherWallet = await EtherWallet.deploy()

// デプロイには、時間がかかるので、テストコードを書くときは
// 以下のように、デプロイが完了するまで待つ処理を入れる
// > await etherWallet.deployed();

// コントクラトをデプロイした時に、コントラクトの作成者が
// `getSigners()` の先頭のアカウントであることが確認できる
> etherWallet.signer.address
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'

コントラクトの owner の取得

まず、EtherWalletowner を取得してみます.
EtherWalletconstructor で、owne = msg.sender の代入をしているので、コントラクトの作成者と一致するはずです.

> await etherWallet.owner()
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'

実際に一致することが確認できました.

コントラクトの balance (ETHの残高) の取得

次に、getBalance() を実行して、コントラクトの残高を取得してみましょう.
コントラクト作成時に、ETHを送金していないので、ETHの残高は 0 です.

> await etherWallet.getBalance()
BigNumber { value: "0" }

外部アカウントの残高の取得

外部アカウントの残高は、以下の方法で取得できます.

> await signer.getBalance()
BigNumber { value: "9999999504861250000000" }

EtherWalletにETHを送金

コントラクトアカウントに送金するには、対象のコントラクトに receive もしくは fallback 関数が定義されている必要があります.
EtherWallet には、receive 関数が定義されているので、送金することができます.

// トランザクションのデータを作成する
// to: 送金先のコントラクトのアドレス
// value: 送金するETH (単位: wei)
> const transaction = { 
    to: etherWallet.address,
    value: ethers.utils.parseEther('1', 'ether')
  } 

// 先頭アカウントから、トランザクションを送信する
> await signer.sendTransaction(transaction)
{
  hash: '0x3ae7d8579786f95e5a1d3ca9f0663dcb632a9ade9cc9c76f4f5f07a7e265d9ae',
  type: 2,
  accessList: [],
  blockHash: '0x66d547369f44f4de2d7f73e60310cebc8c89d3df3730efc2b8b705f59c16e5a6',
  blockNumber: 2,
  transactionIndex: 0,
  confirmations: 1,
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  gasPrice: BigNumber { value: "1767550540" },
  maxPriorityFeePerGas: BigNumber { value: "1000000000" },
  maxFeePerGas: BigNumber { value: "2535101080" },
  gasLimit: BigNumber { value: "21055" },
  to: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
  value: BigNumber { value: "1000000000000000000" },
  nonce: 1,
  data: '0x',
  r: '0x7d46f030fd6e1ab1a2728ced6466a87a21176fc606236ad11f25e39b4bed890f',
  s: '0x0b133a974a772cc502fd72004dd4dff9e8ca45e3dfb97faf7de49bab9a36a4a3',
  v: 1,
  creates: null,
  chainId: 1337,
  wait: [Function (anonymous)]
}

返り値は、トランザクションのデータです.

再度、EtherWallet の残高を取得すると、1ETH 増えていることが確認できます.

> await etherWallet.getBalance()
BigNumber { value: "1000000000000000000" }

また、トランザクション送信者の残高から 1ETH 引き落とされています.

> await signer.getBalance()
BigNumber { value: "9998999467645473380300" }

EtherWalletからETHを引き落とす

先頭アカウントから、withdraw を実行して、0.5ETH を引き落としてみます.

> await etherWallet.withdraw(ethers.utils.parseEther('0.5', 'ether'))
{
  hash: '0x1c203e8dbcb89e92b798cd7e26da9264ad3289cdfd04a68298e5d9bb082ff827',
  type: 2,
  accessList: [],
  blockHash: '0x1499403cfcc14f29ada2217dea9c2e946653eaac28844f5b1900bc9a07c9252d',
  blockNumber: 3,
  transactionIndex: 0,
  confirmations: 1,
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  gasPrice: BigNumber { value: "1671741396" },
  maxPriorityFeePerGas: BigNumber { value: "1000000000" },
  maxFeePerGas: BigNumber { value: "2343482792" },
  gasLimit: BigNumber { value: "29021784" },
  to: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
  value: BigNumber { value: "0" },
  nonce: 2,
  data: '0x2e1a7d4d00000000000000000000000000000000000000000000000006f05b59d3b20000',
  r: '0x9033c02bb41f731eead690e9dd3407da3e22777ed69aa975ef0653fd7aed932f',
  s: '0x0bf02b2be806aa9893d5fcd267fa0728152750ef592bbdd73e521ba24e875f26',
  v: 1,
  creates: null,
  chainId: 1337,
  wait: [Function (anonymous)]
}

EtherWallet の残高を取得すると、0.5 ETH に減っていることが確認できます.

> await etherWallet.getBalance()
BigNumber { value: "500000000000000000" }

では、コントラクトの作成者以外で、withdraw を実行したら、どうなるでしょうか?

まず、別の外部アカウントを取得します.

> let [_,signer2] = await ethers.getSigners()
> signer2.address
'0x70997970C51812dc3A010C7d01b50e0d17dc79C8'

コントラクトのメソッドを実行する際に、トランザクションの送信者を別のアカウントにするには、<Contract>.connect を使用します.
以下では、2番目のアカウント(singer2) から、トランザクションを送信します.

EtherWalletwithdraw では、 require を使って、引き落とし可能なアカウントをオーナーのみに制限しています.
実際に、試したところ以下のエラーが発生して、トランザクションに失敗しました.

> await etherWallet.connect(signer2).withdraw(ethers.utils.parseEther('0.5', 'ether'))
Uncaught:
Error: VM Exception while processing transaction: reverted with reason string 'caller is not owner'
    at EtherWallet.withdraw (contracts/EtherWallet.sol:14)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at runNextTicks (node:internal/process/task_queues:64:3)
    at listOnTimeout (node:internal/timers:533:9)
    at processTimers (node:internal/timers:507:7)
    at HardhatNode._mineBlockWithPendingTxs (/Users/mishimawataru/WebstormProjects/hardhat-boilerplate/node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:1805:23)
    at HardhatNode.mineBlock (/Users/mishimawataru/WebstormProjects/hardhat-boilerplate/node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:494:16)
    at EthModule._sendTransactionAndReturnHash (/Users/mishimawataru/WebstormProjects/hardhat-boilerplate/node_modules/hardhat/src/internal/hardhat-network/provider/modules/eth.ts:1522:18)

このページでは、Hardhat Consoleを使用して、EtherWalletの処理をいろいろと試してみました.
次回は、Hardhatを使用したテストコードについてみていきたいと思います.