🏠

1 pragma

ソースコードの中からコンパイラーに何か伝えたいときに pragma を使う。Solidity の場合はどの言語仕様にするかを設定するのに書く。

pragma solidity ^0.7.0;

2 state variables

コントラクト内に記述された変数、ブロックチェーン上に書き込まれる。Ethereum 上の全てのノードにこの値が(合意形成された状態で)書かれると思ってください。当然お金がかかります。これをガス代と言います。

3 structs

コントラクト内に書いてもいいのかえ?

4 public/private/internal/external

コントラクト内の変数で、 public をつけたものは、他のコントラクトから読むことができる。(注意:書き込みはできない)

関数も同じように public をつけると外からアクセスできるようになるが、何も書かなければ、デフォルトで public となる。よって他のコントラクトからアクセスさせたくないものに関しては、 private をつける。その際、慣習的には _ (アンダースコア)をつける。

function _foo () private {}

ただし、 private にすると、継承されたコントラクトもその関数にアクセスできない。継承先も扱えるようにするには internal にする。

4.1 いつ private にする?

例えば、お金が動く時とか、その NFT トークンを移動する場合はそのコントラクト以外その操作をしたくないので、 private とすることがありそう。

5 引数

関数定義するときに、参照型のデータは memory をつけてメモリに保存することを明示する必要がある。参照型は、 arrays, structs, mappings, strings 。数字以外と覚えておくと良いかも?

function foo (string memory _text) public {}

6 戻値

公開範囲の後に戻り値の情報が入る。コントラクト内の値を読んで何かする場合は view, コントラクトの中身に全く影響させなければ pure となる。

function _getLength() private view returns (uint){}

function _tashizan(uint _a, uint _b) private pure returns (uint){}

returns の後が不思議な括弧で括られているのは、戻り値はいくらでも増やせるから。

function _nextEvent()private view returns(string memory name, uint time);

7 ハッシュ関数 keccak256

Ethereum 内部で使われているハッシュ関数の keccak を使うとランダムな数字が出せる。ただし、その際の引数は byte なので、 abi.encodePacked() 関数を使う。

8 Events

外部(例えばフロントエンド)にブロックチェーン上で起きたこと知らせるために、 event を使う。クラスのように宣言して、関数内で emit (点灯の意味)をする。

event NumberIncreased(uint number);

function tashizan (uint _a, uint _b) private pure {
    uint result = _a + _b;
    emit NumberIncreased(result);
}

このときに、 memory とかいらない。

また、 event を定義したときに、 indexed を追加すると、あとでフロントエンドで使うときに利用することができる。

9 mapping

キー・バリューペアの辞書型は mappings といって、定義は以下のようにする。

mapping (uint => string) gakusekiToName public;

10 require

コントラクト上で、ある状態であることを保証したい場合は require を使う。

function warizan(int _a, int _b) private returns (uint) {
    require(_b > 0);
    return _a / _b;
}

11 継承 (OpenZeppelin の Ownable) と modifiers

コントラクトの所有者(のみが実行できる関数が必要な場合)のユーザーアクセス権管理によく使われるコントラクトが公開されている。

これを import した状態で、継承し is することで、最初にデプロイした人が owner となる。このオーナー権限のみで実行したい関数に関しては、関数のシグネチャーに onlyOwner を追加する。これを modifiers といい、その関数を走らせる前に、チェックしたい項目を明示するものだ。

この OpenZeppelin のコントラクト集は読むと色々勉強になる。特にセキュリティに関して抜け漏れがないかの基礎知識を上げるためにも。

11.1 Modifiers

modifier oldEnough(uint _age, Person storage _person){
    require(_person.age >= _age);
    _;
}

function driveCar(Location memory _location, Person storage _person) oldEnough{
}

複数ある場合は列挙すればいいらしい。 _; と言っても難しいものでもなく、の部分にそれ以降の関数を代入すればいい。

11.2 これ以外の OpenZeppelin 関数

スマートコントラクト基本的に危険で、思わぬところにセキュリティホールがあることがある。特に、経験が浅い1ときはすでにある「うまくいっている」コントラクトを継承するなり流用したほうが良い。かつ、同じ形式で書かれたコントラクトは他の外部に書かれたコントラクトから使われ安く、このようにして自然と「標準化」が行われる。

11.2.1 SafeMath

計算するときに使える。オーバーフロー・アンダーフローの面倒を見てくれる。 library の使い方は次次項にて。

11.2.2 ERC721

NFT 作るときに使える。

12 modifiers の中で最も重要とも言える payable

分散台帳上でやっているので、お金のやりとりが言語に組み込まれているのが一番特徴です。お金の移動を受け付ける modifier が payable でこれによって、人やコントラクトがこのコントラクトにお金(eth)を送金できるようになる。

ただし、この値の量を確認するものではないので、 require によってその値が足りてるかを検証するのは必要です。

12.1 引き出し先を考える必要あり

もう一つは、仮にこのコントラクトにお金が入った場合に、引き出す方法がないと永久に止まっただけなので、Owner とセットであることは、必須そう。

13 external view と pure 関数

コントラクト外から呼ばれて、ブロックチェーン上の値を変更しないものに関してはガス代がかからない。(一番近くのノードに問い合わせればいいだけだし、その値の変更に対して全体の合意を取る必要がない)

このことから DB として使うときはできるだけ平べったい構造(git みたいに)にして、必要に応じてデータを扱いやすい形に変換して使うことが望ましい。

mapping (uid => uid[]) みたいな場合、店が商品を複数持っている状態がイメージしやすいかな。この場合、「交換」という行為はすごくコストが高い。空いた uid[] の部分を繰り上げる分だけ DB に書き込みが発生するから。

だったらまだ、 productToOwner[uid] として、その値を変更するだけの方が安く、そのオーナーが持っている品物のリストが必要であれば、この変数を振り分けてそのデータを都度都度作れば良い。いわばローカル演算最適化をしたほうがいいってこと。でもここで、疑問なのが、だとしたら、ローカルノードではなくて、クライアント側に落としてから料理すればよさそうなものだけど、それは扱うデータの量(通信量がかかったり)や Solidity のできる計算次第なのだろう。(pure だとしても、複雑な行列計算はその場でしなくてもいいだろうよ)

14 フロントエンド連携

コントラクトが無事デプロイされたらフロントエンドで使わないといけない。ライブラリとしては web3.js 1ってのが Ethereum ノードとのつなぎ役で、ロードされたときに下記でノードと交信するためのオブジェクトを取得する。

      window.addEventListener('load', function() {
        if (typeof web3 !== 'undefined') {
            // ここはグローバルに定義する。
            web3js = new Web3(web3.currentProvider);
        } else {
            // meta mask インストールしてください的なことをユーザーに知らせる。
        }
        startApp()
      })

ノードと交信ができたところで、扱いたいコントラクトを指定するには、1.そのアドレスと(デプロイ時に決まる)、2.コンパイルした ABI(アプリケーション・バイナリ・インターフェース)を使う。

  1. (コントラクトのアドレス)は言わずもがな、ブロックチェーンネットワークの中でどのアドレスに記述されているかの場所の指定である。
  2. (ABI)はそのコントラクトがどんな機能があるかが記述されているかがまとめられていて、これによりフロントエンドで扱えるようになる。

ABI がどこかしこでインポートされている事を前提に(例えば、 <script> タグで)、下記によってコントラクトが取得でき、使うことができる。

const startApp = () => {
    const address = "CONTRACT_ADDRESS";
    contract = new web3js.eth.Contract(abi, address);
}

このコントラクトの使い方は大きく三つある。

  1. contract.methods.foo().call() : foo() はコントラクトの中のシグネチャと一緒。引数も必要であれば入れる。この call はブロックチェーン上で変更の必要がないもの、言い換えると、手近にあったノードに問い合わせれば終わるものである。具体的には、 external view か ~pure~これには、meta mask 等のウェレットの承認は不要。
  2. contract.methods.foo().send({from: userAddress}) : send はブロックチェーン上で変更が出てくる操作であり、ネットワーク全体の合意が必要になる。当然遅いので、その時間差を想定した処理が必要になってくる。また eth の送金が必要な場合は関数の引数に send({from: userAddress, value: amount}) とする。ここでの単位は wei といって 1eth = 10^18 wei である。~web3.js~ に使いやすい関数が用意されている。
  3. contract.events.event() : イベントへのサブスク。該当するイベントが emit された場合に、この関数が実行される。基本的な使われ方は以下:

    // subscribe to event
    contract.events.event()
    .on("data", function (event){
        let result = event.returnValues;
    }).on("error", console.error);
    

    コントラクトでイベント定義するときに、 indexed をつけておくと、このイベント発火のタイミングをフィルタリングすることができる。

    event ItemSold(address indexed _item, uint quantity);
    

    としたとき、

    contract.events.ItemSold({filter:{_item: myItemAddress}})
    .on("data", function (event){
        let result = event.returnValues;
    }).on("error", console.error);
    

    このイベントの発火の履歴は, contract.getPastEvents("Contract", {fromBlock:0, toBlock:"latest"}) で取得することができ、これも Promise を返すので、 .then() で数珠繋ぎする。

    実は、この event を使うことで、ガス代の節約にもなる、全体の履歴から現時点の状態を復元でき、値を格納するよりも event を emit した方が安い。

    1 世の中の人気的に web3.js よりも ethers.js に移行しているらしいので、実際に何か作ってみるときはそっちを使うつもりです。が、基本的に、 metamask と繋げる、コントラクトの ABI を使って、コントラクトを叩ければいいので、役割が明確な分そんなにややこしくないと 願う 。

15 Oracle: コントラクトから外部の情報にアクセスする

コントラクトそのものは、外部から情報を得ることができない。それを可能にする仕組み(?と言うか主体)を Oracle と言う。外部から情報を持ってくる、預言者と言うことですね。セキュアな乱数とかも本当は Oracle がいいとか聞いたことがある。天気とかね。ブロックチェーン関係ないけど、IETF のチェア決めるのもオラクルっぽいよね。

EthPriceOracleOverview.png

Figure 1: 外部の情報にアクセスする仕組み

みる限り左から 2 番目と 3 番目は Contract となっているのがわかる。左から 3 番目と 4 番目の間がブロックチェーンの内部と外部の境界だ。

16 Hardhat で何か作ってみる

とてもじゃないけど、この延長で書ける項目でなかったのでHardhatと言う別記事にまとめることにした。

Footnotes:

1

経験が豊かだからこそ、他力本願というものある。例えば、自分で人のパスワードを隠匿するアルゴリズムを作るほど馬鹿なことはない。

Date: 2021-08-26 10:58

Author: Yasushi Sakai

Created: 2021-09-08 Wed 15:11