サイオステクノロジーの菊地啓哉です。今回は、暗号通貨を扱う簡単な SmartContract を Solidity で作ります。
いきなり余談ですが、筆者は Blockchain の勉強を始めようとして、インターネットで検索をした時に、やたらと仮想通貨(暗号資産)がセットで出てきて、「仮想通貨じゃなくて、Blockchain を知りたいんだ」と思った記憶があります。実際のところ、Blockchain でトランザクションを実行するには、ガス代としてネイティブトークン(それぞれのチェーンで固有の特別な暗号資産。Ethereumメインネットであれば、ETH)を支払いますし、SmartContract で暗号資産を扱うこともできるので、Blockchain と暗号資産は切っても切れない関係と言えるでしょう。
対象
- SmartContract、Solidity などに興味のある方
- Java や C# など開発経験が少しでもある方
前回と同じです。
開発する SmartContract の概要
今回は、ユーザのネイティブトークンを預けたり、引き出すことができる Bank Contract を開発します。預ける際の量は、Request の value
で指定します。引き出す際は、引数で量を指定します。
Ethereum では、SmartContract は Ethereum アカウントの一種で、Contract account と書かれています。ユーザが管理するアカウントは Externally-owned account (EOA) と呼ばれます。これらはどちらも、アドレスと ETH の残高を持ちます。
準備
早速、プログラムを作っていきたいですが、その前に少し、Remix上で開発・動作確認する際に便利になるように少し準備します。この準備をすることで、Bank.sol を編集し、 Ctrl+Shift+S でコンパイルと同時にデプロイできます。公式の説明は Compile a contract and run a script on the fly にあります。
最初に作られる workspace や、「Basic」テンプレートを使って作成した workspace などでは作成されているとは思いますが、以下のファイルを用意します。勝手に作られているものを拝借しているので説明は省略します。
scripts/ethers-lib.ts
import { ethers } from 'ethers'
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*/
export const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => {
console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
const contract = await factory.deploy(...args)
// The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed()
return contract
}
そして、Bank.sol をデプロイするためのスクリプトを以下の通り作成します。中身は、先に用意した ethers-lib を使って Bank Contract をデプロイしているだけです。deploy
の第二引数は constructor に渡す引数ですが、今回は引数無しで実装するので、空の配列を渡しています。
scripts/deployBank.ts
import { deploy } from './ethers-lib'
(async () => {
try {
const result = await deploy('Bank', [])
console.log(`address: ${result.address}`)
} catch (e) {
console.log(e.message)
}
})()
これで、準備はできました。
SmartContract の実装
解説は後回しにして、以下の通り、実装します。
contracts/Bank.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/**
* @custom:dev-run-script ./scripts/deployBank.ts
*/
contract Bank{
mapping(address=>uint) public balances;
function deposit() public payable returns(uint){
balances[msg.sender] += msg.value;
return balances[msg.sender];
}
function withdrow(uint _amt)public{
require(balances[msg.sender] >= _amt, "short of deposit");
balances[msg.sender] -= _amt;
payable(msg.sender).transfer(_amt);
}
}
まずはいつも通りライセンスと Solidity のバージョンについて書かれていて、その次にコメントで @custom:dev-run-script
が指定されています。この指定によって、先に準備したデプロイスクリプトが呼ばれるようになります。
Bank Contractの中身に入っていきます。
balances
という状態変数を定義しています。これは、address
を指定して uint
が返ってくるマッピング型で、どのアカウントが、どれだけの量のネイティブトークンを預けているのか管理します。これにより、どのユーザも自分が預けた以上の量のトークン(他アカウントが預けた分)を引き出すことを防ぐことができます。
続いて、deposit
関数です。この関数には、payable
の Modifier が付いています。これは、ユーザが関数の呼び出す時に、value
で指定した量のネイティブトークンを SmartContract に渡すことができます。(参考)
そして、balances
状態変数に、渡された量を加算するのですが、実行者のアドレスは msg.sender
、渡されたトークン量は msg.value
で得られます。(参考)
最後に withdrow
関数ですが、指定した量のネイティブトークンを引き出すことができます。ただし、預けられた量を超えて引き出せないようにチェックを入れています。
動かしてみる
Contract をデプロイしたら、deposit
を実行する為に、次の画像の赤枠部分のように、預けるトークン量を指定します。Gwei の weiはETHの最小単位で、 1 ETH = 1018 wei です。G はギガです。 109 を表すSI接頭語です。なので、1 Gwei = 10-9 ETH ですね。
ここでは2 Gwei を指定し、入力したら、deposit
のボタンを押します。
次の画像は実行後の画面で、アカウントのプルダウンを開いているところです。実行アカウントの所持ETH が、2 Gwei と少し減っていることがわかります。”少し”はガス代の分です。
また、赤枠の部分には、Contract の所持ETH が表示されていますが、2 Gwei になっています。
次の画像は balances
を実行した結果です。引数には、deposit
の実行アカウントのアドレスを指定しています。アカウントの右のアイコンでコピーできるので、ペーストします。
withdraw
を実行します。桁数が多いので、上のbalances
の結果をコピーして、2 Gwei から 3 Gwei に変えて実行すると、チェックにより、エラーとなります。次の画像の選択している部分にエラーメッセージが表示されていますね。
最後に、1 Gwei で withdraw
を実行すると、正常に実行でき、実行アカウントの所持ETH が 1 Gwei 増え、Contract の所持ETH はその分だけ減っていることがわかります。
まとめ
今回はネイティブトークンを扱う簡単な SmartContract を実装してみました。payable
を関数につけることで、SmartContract に対してネイティブトークンを送ることができ、預けられているネイティブトークンを引き出す処理も実装しました。そんなに難しくはないかなと思います。なんか、固定金額でNFTを販売する SmartContract とかなら簡単に作れそうな感じがしますよね。実際、もう少しで作れます。ただ、要件が増えてくると、もっといろいろと作りこんだり、設計を練らないといけなかったりします。
出来上がった SmartContract だとチェック処理を入れているので、このコードのままでは起こせないのですが、1つの SmartContract に対して、預けられたネイティブトークンの総量よりも多く引き出そうとするとエラーになります。また、自分が預けている量を取得するための関数があると使いやすそうだったりしますね。このあたりは、ぜひ、コードをいじって動かしてみてください。
またかきます
またね