0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ブロックチェーン開発言語 - Solidity

Last updated at Posted at 2023-09-18

Solidityとは

  • Ethereum(イーサリアム)の共同設立者のギャビン・ウッドによって開発されたプログラミング言語です。
    • Ethereumとはブロックチェーンプラットフォームです。時価総額2位(2020年2月時点)の仮想通貨

特徴は

  • 初心者でも扱いやすい
  • オブジェクト指向のプログラミング言語
  • スマートコントラクト作成機能を備えている
    • スマートコントラクトとは決まった条件下での契約の自動執行を指します。
    • 例:自動販売機の自動執行される売買契約

環境構築

必要なツール

  • Node.js
  • Ganache(ガナッシュ)
    • ローカル開発時に Ethereum プライベートブロックチェーンを可視化するツール
  • Truffle
    • Ethereum アプリケーション開発におけるフレームワーク
  • MetaMask
    • イーサリアムブロックチェーンに対応した仮想通貨ウォレット

前提条件

  • Node.jsはインストール済みであること
  • MetaMaskについては触れません。

Truffleのインストール

  • コマンドは以下
$ npm install -g truffle
  • インストール後、バージョン確認
  • 2023/09/18時点でのバージョンは 5.11.5 でした
$ truffle version
Truffle v5.11.5 (core: 5.11.5)
Ganache v7.9.1
Solidity v0.5.16 (solc-js)
Node v18.17.1
Web3.js v1.10.0

Ganacheのインストール

  • 以下公式サイトにアクセスし、インストーラをダウンロード
    https://trufflesuite.com/ganache/
    image.png

  • インストールを進めていきます
    image.png

  • ここではクイックスタートを試します
    image.png

  • インストール済みのEthereum ワークスペースが表示されていることを確認。
    image.png

クイックスタート

プロジェクトの作成

% truffle unbox metacoin 
This directory is non-empty...
? Proceed anyway? (Y/n) 
Starting unbox...
=================

? Proceed anyway? Yes
✓ Preparing to download box
✓ Downloading
✓ Cleaning up temporary files
✓ Setting up box

Unbox successful, sweet!

Commands:

  Compile contracts: truffle compile
  Migrate contracts: truffle migrate
  Test contracts:    truffle test
  • 以下ディレクトリ構成でプロジェクトが作成されます。
% tree
.
├── LICENSE
├── contracts
│   ├── ConvertLib.sol
│   └── MetaCoin.sol
├── migrations
│   └── 1_deploy_contracts.js
├── quick-start-metacoin
├── test
│   ├── TestMetaCoin.sol
│   └── metacoin.js
└── truffle-config.js

5 directories, 7 files

contracts/MetaCoin.sol

  • MetaCoin.solというコントラクタが作成されています。
  • 見るべきポイントとしてsendCoin関数を抑えておく
  • sendCoin関数:
    • 他のアドレスにコインを送信。
    • 送金完了時、送信元の残高から送金額を減少させる。送信先の残高に加算する
    • Transferイベントを発火。トランザクションの詳細を記録
// SPDX-License-Identifier: MIT
// Tells the Solidity compiler to compile only from v0.8.13 to v0.9.0
pragma solidity ^0.8.13;

import "./ConvertLib.sol";

// This is just a simple example of a coin-like contract.
// It is not ERC20 compatible and cannot be expected to talk to other
// coin/token contracts.

contract MetaCoin {
	mapping (address => uint) balances;

	event Transfer(address indexed _from, address indexed _to, uint256 _value);

	constructor() {
		balances[tx.origin] = 10000;
	}

	function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
		if (balances[msg.sender] < amount) return false;
		balances[msg.sender] -= amount;
		balances[receiver] += amount;
		emit Transfer(msg.sender, receiver, amount);
		return true;
	}

	function getBalanceInEth(address addr) public view returns(uint){
		return ConvertLib.convert(getBalance(addr),2);
	}

	function getBalance(address addr) public view returns(uint) {
		return balances[addr];
	}
}

contracts/ConvertLib.sol

  • MetaCoin.solにてアドレス残高取得ロジックを本コントラクトのconvert関数で実現
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

// A library is like a contract with reusable code, which can be called by other contracts.
// Deploying common code can reduce gas costs.
library ConvertLib{
	function convert(uint amount, uint conversionRate) public pure returns (uint convertedAmount)
	{
		return amount * conversionRate;
	}
}

migrations/1_deploy_contracts.js

  • デプロイスクリプト
  • ConvertLib、MetaCoinコントラクトをデプロイする
const ConvertLib = artifacts.require("ConvertLib");
const MetaCoin = artifacts.require("MetaCoin");

module.exports = function(deployer) {
  deployer.deploy(ConvertLib);
  deployer.link(ConvertLib, MetaCoin);
  deployer.deploy(MetaCoin);
};

test/TestMetaCoin.sol

  • contracts/MetaCoin.sol のテストコントラクト
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

// These files are dynamically created at test time
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";

contract TestMetaCoin {

  function testInitialBalanceUsingDeployedContract() public {
    MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());

    uint expected = 10000;

    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

  function testInitialBalanceWithNewMetaCoin() public {
    MetaCoin meta = new MetaCoin();

    uint expected = 10000;

    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

}

test/metacoin.js

  • JavaScriptで書かれたテストプログラム
const MetaCoin = artifacts.require("MetaCoin");

contract('MetaCoin', (accounts) => {
  it('should put 10000 MetaCoin in the first account', async () => {
    const metaCoinInstance = await MetaCoin.deployed();
    const balance = await metaCoinInstance.getBalance.call(accounts[0]);

    assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
  });
  it('should call a function that depends on a linked library', async () => {
    const metaCoinInstance = await MetaCoin.deployed();
    const metaCoinBalance = (await metaCoinInstance.getBalance.call(accounts[0])).toNumber();
    const metaCoinEthBalance = (await metaCoinInstance.getBalanceInEth.call(accounts[0])).toNumber();

    assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, 'Library function returned unexpected function, linkage may be broken');
  });
  it('should send coin correctly', async () => {
    const metaCoinInstance = await MetaCoin.deployed();

    // Setup 2 accounts.
    const accountOne = accounts[0];
    const accountTwo = accounts[1];

    // Get initial balances of first and second account.
    const accountOneStartingBalance = (await metaCoinInstance.getBalance.call(accountOne)).toNumber();
    const accountTwoStartingBalance = (await metaCoinInstance.getBalance.call(accountTwo)).toNumber();

    // Make transaction from first account to second.
    const amount = 10;
    await metaCoinInstance.sendCoin(accountTwo, amount, { from: accountOne });

    // Get balances of first and second account after the transactions.
    const accountOneEndingBalance = (await metaCoinInstance.getBalance.call(accountOne)).toNumber();
    const accountTwoEndingBalance = (await metaCoinInstance.getBalance.call(accountTwo)).toNumber();

    assert.equal(accountOneEndingBalance, accountOneStartingBalance - amount, "Amount wasn't correctly taken from the sender");
    assert.equal(accountTwoEndingBalance, accountTwoStartingBalance + amount, "Amount wasn't correctly sent to the receiver");
  });
});

テストプログラムを実行

  • テストプログラムも用意されているので、テスト実行で動作確認してみます。
  • 以下コマンドを実行
% truffle test

Compiling your contracts...
===========================
✓ Fetching solc version list from solc-bin. Attempt #1
✓ Downloading compiler. Attempt #1.
> Compiling ./contracts/ConvertLib.sol
> Compiling ./contracts/MetaCoin.sol
> Compiling ./test/TestMetaCoin.sol
> Artifacts written to /var/folders/ms/30d25_0x5lv94y_5_pcp9f340000gn/T/test--5064-PHriPnpBOths
> Compiled successfully using:
   - solc: 0.8.13+commit.abaa5c0e.Emscripten.clang


  TestMetaCoin
    ✔ testInitialBalanceUsingDeployedContract
    ✔ testInitialBalanceWithNewMetaCoin

  Contract: MetaCoin
    ✔ should put 10000 MetaCoin in the first account
    ✔ should call a function that depends on a linked library (41ms)
    ✔ should send coin correctly (60ms)


  5 passing (3s)

Ganacheとリンク

Ganacheの NEW WORKSPACE ボタンをクリック

image.png

上記で作成したプロジェクトのtruffle-config.jsを定義し、 START ボタンをクリック

image.png

作成されました

image.png

migrate実行

  • プロジェクトのディレクトリのターミナルで以下コマンド実行
  • コンパイルされたコントラクトがブロックチェーンにデプロイされたことが確認できています。
  • 末尾のサマリーで確認すると以下コントラクトがデプロイされ、コストも明記されています。
    • コントラクト: MetaCoin, ConvertLib
    • コスト: 0.001894367934056634 ETH
% truffle migrate

Compiling your contracts...
===========================
> Compiling ./contracts/ConvertLib.sol
> Compiling ./contracts/MetaCoin.sol
> Artifacts written to /Users/nagaseyutaka/Dev/800-private/14-solidity-study/build/contracts
> Compiled successfully using:
   - solc: 0.8.13+commit.abaa5c0e.Emscripten.clang


Starting migrations...
======================
> Network name:    'ganache'
> Network id:      5777
> Block gas limit: 6721975 (0x6691b7)


1_deploy_contracts.js
=====================

   Deploying 'ConvertLib'
   ----------------------
   > transaction hash:    0xe4bac39d47eab771be075d84fea3ed51d8bd2209a890ddb61468220dc6dbcac9
   > Blocks: 0            Seconds: 0
   > contract address:    0xbAd967CF31a5Eb809D1c3df7E74d62EaF254c84f
   > block number:        1
   > block timestamp:     1695026534
   > account:             0x0433811BAd4c494f8EeAB84886F2d1d85c80fAdA
   > balance:             99.999468208
   > gas used:            157568 (0x26780)
   > gas price:           3.375 gwei
   > value sent:          0 ETH
   > total cost:          0.000531792 ETH


   Linking
   -------
   * Contract: MetaCoin <--> Library: ConvertLib (at address: 0xbAd967CF31a5Eb809D1c3df7E74d62EaF254c84f)

   Deploying 'MetaCoin'
   --------------------
   > transaction hash:    0x54e9c9def1b25dd16c8cda0666324bd86032ccb18aa9559ca3e5a18351fb9c4b
   > Blocks: 0            Seconds: 0
   > contract address:    0x1e013b453Ed87691545F1173ba7D97F1e05d023c
   > block number:        2
   > block timestamp:     1695026534
   > account:             0x0433811BAd4c494f8EeAB84886F2d1d85c80fAdA
   > balance:             99.998105632065943366
   > gas used:            416594 (0x65b52)
   > gas price:           3.270752661 gwei
   > value sent:          0 ETH
   > total cost:          0.001362575934056634 ETH

   > Saving artifacts
   -------------------------------------
   > Total cost:     0.001894367934056634 ETH

Summary
=======
> Total deployments:   2
> Final cost:          0.001894367934056634 ETH

Ganacheで確認

  • BLOCKSタブ
    image.png

  • TRANSACTIONSタブ
    image.png

  • CONTRACTSタブ
    image.png

コントラクトのやり取り

  • コマンドでコントラクトを操作します
  • 以下コマンドでTruffleコンソールに移行
% truffle console
truffle(ganache)> 

image.png

やり取り用のアカウントを用意

  • instance: プロジェクトで作成したMetaCoinアカウント
  • accounts: Truffle の組み込みブロックチェーンアカウント
truffle(ganache)> let instance = await MetaCoin.deployed()
undefined
truffle(ganache)> let accounts = await web3.eth.getAccounts()
undefined

プロジェクトで作成したMetaCoinアカウントの残高を確認

  • 10000メタコイン、20000ETH
truffle(ganache)> let balance = await instance.getBalance(accounts[0])
undefined
truffle(ganache)> balance.toNumber()
10000
truffle(ganache)> let ether = await instance.getBalanceInEth(accounts[0])
undefined
truffle(ganache)> ether.toNumber()
20000

メタコインの転送

  • [プロジェクトで作成したMetaCoinアカウント]から任意の[Truffle の組み込みブロックチェーンアカウント]にメタコイン500を転送
truffle(ganache)> instance.sendCoin(accounts[1], 500)
{
  tx: '0xdde7e6cc2c2027514481ffe21de1b029b74aca6f98c4c82d29407c2fa13bc3f2',
  receipt: {
    transactionHash: '0xdde7e6cc2c2027514481ffe21de1b029b74aca6f98c4c82d29407c2fa13bc3f2',
    transactionIndex: 0,
    blockNumber: 3,
    blockHash: '0x75677027c9729f16ca8cc23ad5e293b9da2e294c5ab7499734fa4dca120f5a8b',
    from: '0x0433811bad4c494f8eeab84886f2d1d85c80fada',
    to: '0x1e013b453ed87691545f1173ba7d97f1e05d023c',
    cumulativeGasUsed: 52297,
    gasUsed: 52297,
    contractAddress: null,
    logs: [ [Object] ],
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000010000000008000000400000018000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000200000000000000000002020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    status: true,
    effectiveGasPrice: 3186350418,
    type: '0x2',
    rawLogs: [ [Object] ]
  },
  logs: [
    {
      address: '0x1e013b453Ed87691545F1173ba7D97F1e05d023c',
      blockHash: '0x75677027c9729f16ca8cc23ad5e293b9da2e294c5ab7499734fa4dca120f5a8b',
      blockNumber: 3,
      logIndex: 0,
      removed: false,
      transactionHash: '0xdde7e6cc2c2027514481ffe21de1b029b74aca6f98c4c82d29407c2fa13bc3f2',
      transactionIndex: 0,
      id: 'log_796221d5',
      event: 'Transfer',
      args: [Result]
    }
  ]
}

受取側アカウントの残高を確認

truffle(ganache)> let received = await instance.getBalance(accounts[1])
undefined
truffle(ganache)> received.toNumber()
500
truffle(ganache)> let newBalance = await instance.getBalance(accounts[0])
undefined
truffle(ganache)> newBalance.toNumber()
9500

送信側アカウントの残高を確認

truffle(ganache)> let newBalance = await instance.getBalance(accounts[0])
undefined
truffle(ganache)> newBalance.toNumber()
9500

まとめ

  • SolidityはJavaScriptに似ています。難しい言語では無い。
  • Truffleのチュートリアルが充実しているので、学習コストが低いイメージです。
  • Ganacheで可視化できるので分かりやすい。

参考サイト

TRUFFLE SUITE
Truffleを使ってSolidityの開発環境を準備する方法

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?