SmartContract で暗号通貨を扱う

ETH を預ける SmartContract

サイオステクノロジーの菊地啓哉です。今回は、暗号通貨を扱う簡単な 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 のボタンを押します。

deposit実行時画面
VALUES で送るネイティブトークンの量を指定する

次の画像は実行後の画面で、アカウントのプルダウンを開いているところです。実行アカウントの所持ETH が、2 Gwei と少し減っていることがわかります。”少し”はガス代の分です。

また、赤枠の部分には、Contract の所持ETH が表示されていますが、2 Gwei になっています。

depositの実行後

次の画像は balances を実行した結果です。引数には、deposit の実行アカウントのアドレスを指定しています。アカウントの右のアイコンでコピーできるので、ペーストします。

balances の実行画面

withdraw を実行します。桁数が多いので、上のbalances の結果をコピーして、2 Gwei から 3 Gwei に変えて実行すると、チェックにより、エラーとなります。次の画像の選択している部分にエラーメッセージが表示されていますね。

預けた量よりも多く引き出そうとした図

最後に、1 Gwei で withdraw を実行すると、正常に実行でき、実行アカウントの所持ETH が 1 Gwei 増え、Contract の所持ETH  はその分だけ減っていることがわかります。

預けた量以下を引き出す図

まとめ

今回はネイティブトークンを扱う簡単な SmartContract を実装してみました。payable を関数につけることで、SmartContract に対してネイティブトークンを送ることができ、預けられているネイティブトークンを引き出す処理も実装しました。そんなに難しくはないかなと思います。なんか、固定金額でNFTを販売する SmartContract とかなら簡単に作れそうな感じがしますよね。実際、もう少しで作れます。ただ、要件が増えてくると、もっといろいろと作りこんだり、設計を練らないといけなかったりします。

出来上がった SmartContract だとチェック処理を入れているので、このコードのままでは起こせないのですが、1つの SmartContract に対して、預けられたネイティブトークンの総量よりも多く引き出そうとするとエラーになります。また、自分が預けている量を取得するための関数があると使いやすそうだったりしますね。このあたりは、ぜひ、コードをいじって動かしてみてください。

 

またかきます

またね

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です