👨‍🎓

【Solidity】スマートコントラクトを作成する上で学んだこと、個人的な学習をメモしていく【CryptoZombie】

2022/06/24に公開

今回solidityを学習する上で用いたサイトは以下のサイトである。
https://cryptozombies.io/jp/course/
日本語で始めることのできる学習サイトで、説明もわかりやすい。そして無料でチャレンジできるので是非試してみると良いと思う。

用語集

スマートコントラクトとは

  • イーサリアムチェーン上に乗っかるアプリみたいなもの
  • アプリとはいえ目に見えないサーバーサイドみたいな感じ

ガス代

  • イーサリアムの使用料のこと
  • この使用料のことをガス代という
  • 無料で提供すると、無駄な処理を実行しまくるユーザーがいると処理を詰まらせてしまうかもしれない。

変数宣言

uint hogehoge = 0;
string fugafuga = "aaa";
struct User {
  uint uid;
  string name;
}

ガス代を節約する変数宣言

// このuintをuint8やuint16にしても節約にはならない
uint hogehoge = 0;
// structの中はガス代の節約になるらしい
struct User {
  uint32 uid;
  string name;
}

for文

for (uint i = 1; i <= 10; i++) {
}

よくある書き方

if文

if (true){
}

よくある書き方

関数宣言

function _doGetUserName(uint _userId) private view returns (string) {
 // ここでユーザーのidが取得される
 return userId;
}
  • アンダースコア(_)
    • private変数に用いるのが通例らしい
  • private, public, internal, extarnal
    • publicは誰でもアクセス可、privateはファイル内でアクセス可
    • internalは、継承したコントラクトからも呼び出せるようになる
    • externalは、コントラクトの外からだけ呼ぶ用の関数
      • webフロント側から呼び出す時に使いがち
  • view
    • 修飾子(後述)
  • returns (戻り値の型)
    • returnと間違いそうだけど・・・

セキュリティ

これはsolidityに限らないけど、solidityでは特に
publicexternal は誰でもアクセスできることを意識する必要がある

戻り値は複数ができる

function getMultiValue() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

// スプリット演算子みたいな感じで
function getLastReturnValue() external {
  uint third;
  (,,third) = getMultiValue();
}

関数修飾子

modify a(){
 //諸々の処理...
 _;
}
function b() public a {
 //諸々の処理...
}

上の時に『a』のことを 関数修飾子 という
bを実行する前に、aを実行するみたいな感じ

View関数

外部から呼び出す際にガス代が不要な関数。

  • view関数は呼び出しをしてもブロックチェーン上で何も変更を行わない
  • 読み取り処理に関しては external view をどんどん使う
    • ※ただしview関数をview関数以外の関数から呼び出しされる場合にはガス代発生する

payable

イーサリアムを受け取ることのできる関数

contract StoreContract {
  function buy() external payable {
    require(msg.value == 0.1 ether);
    transferThing(msg.sender);
  }
}
  • msg.value
    • コントラクトに送られたether
    • etherは単位

withdraw

コントラクトに溜まったetherを引き出す関数

contract Pay is Ownable {
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }
}
  • this.balanceはコントラクトに溜まっている残高を取得する
  • (address).transfer(value)をすると、addressvalue を送金する

イベント

サービスのフロントエンドへ情報を伝達する際に使う

イベント宣言

event AddEvent(uint x, uint y, uint result);

イベント呼び出し

スマコンから、アプリ側へ情報を実際に伝達する

function add(uint _x, uint _y) public {
  uint result = _x + _y;
  AddEvent(_x, _y, result);
  return result;
}

マッピング

宣言

// ItemのIDをもとに、ユーザーのアカウント(アドレス)を参照・格納する
mapping (uint => address) public itemIdToOwner;
// アイテムのIDをもとに、アイテムの名前を参照・格納する
mapping (uint => string) itemIdToName;

読み出し

function hogehoge(uint _itemId) {
 itemIdToOwner[_itemId] = msg.sender;
}
function fugaguga(uint _itemId, string _newName) {
 itemIdToName[_itemId] = _newName;
}

継承

contract Devil {
	...
}

contract Person is Devil {
	...
}

Devilで提供されるpublic等の関数をそのまま使えるようになる。
よくやる雰囲気と同じ

Interface

他のコントラクト間でやりとりする際に使う。よくあるinterfaceのニュアンスは同じ

contract ItemInterface {
  function getItemId(address _myAddress) public view returns (uint);
}
contract HogeContract {
  address userAddress = ....; 
  ItemInterface itemContract = ItemInterface(userAddress);

  function doHoge() public {
    uint _itemId = itemContract.getItemId(msg.sender);
    // _itemIdを使って処理
    ...
  }
}

ストレージとメモリ

  • solidityが変数を保持するのは、 storagememory
    • storageを使うのは、基本的に本当に必要な時のみにするべき。
      • 理由は永遠に格納され、使用するガス代が高い。
        - 関数内で使われる変数は基本的にmemory、状態変数は基本的にstorage

その他 覚えておくといいこと

keccak256

文字列を256ビットの16進数にマッピングする。
イーサリアムに組み込まれているSHA3の1つ

keccak256("hogehoge")

keccak256を用いた乱数生成

//1~1000の乱数生成
uint nonce = 0;
uint random = uint(keccak256(now, msg.sender, nonce)) % 1000;
nonce++;
  • この乱数生成はとりあえずの乱数生成
  • 安全な乱数生成は別の方法(oracle system)を行う

msg.sender

その関数を呼び出したユーザーのアドレスを示す、グローバル変数のこと

require

条件を満たさない場合、以降のソースが実行されなくなる

require(keccak256("aaa") == keccak256(_userId));

OpenZeppelin

  • solidityのライブラリ
  • コミュニティで検証を得られているとりあえず使っとけば良いライブラリ
contract Ownable {
  address public owner;
  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  function Ownable() public {
    owner = msg.sender;
  }

  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }
}

Ownableを継承して使おう

contract MyContract is Ownable {
	...
}

function Ownable()

  • コントラクトが作成された時に1度だけ実行
  • ownerを作成したユーザーのmsg.senderで設定する

modifier onlyOwner

  • 関数修飾子として使う。
  • 関数修飾子として使うことで、オーナー権限を持っている場合のみ実行することができる

transferOwnership

  • 新しいオーナーを決定することができる

時間

solidityにはjavascriptでいうDate.now()や.getMonth()みたいな、時間をいい感じに取得してくれる単位が提供されている

function getDate() private {
 // 今の時間がunixタイムで取得可能
 uint32 time = now;
}
  • seconds、 minutes、 hours、 days、weeks 、yearsが提供
  • 1minutesは60, 1hoursは3600の数字に変換される

Discussion