第1回に引き続きSolidity入門基礎編を進めていきます。
前回のはこちら。
Solidity入門基礎編【1】 – CryptoZombiesで学習 –
始めるまえにこちらへアクセスしてください。
https://cryptozombies.io/jp/lesson/2/chapter/1/
チャプター 1は概要なので割愛。
目次
チャプター 2: マッピング(Mapping)とアドレス(Addresses)
マッピングというのは配列の仕切りに名前をつけられるもの。
配列は0,1,2…番目といった数字だが、マッピングはA,B,C…といった名前が付けられるのが特徴的。
例えば、特定のウォレットにいくら格納されているといった情報を格納したい場合、このように書く。
ちなみにaddressはウォレットのアドレスを格納できる型。(アドレスは0x0000….みたいなやつ)
// mapping(型=>値) 変数名 mapping (address => uint) public accountBalance;
pragma solidity ^0.4.19; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; // 一つはゾンビを所有するアドレスをトラッキングし、もう一つはオーナーが持つゾンビをトラッキングする // zombieToOwnerという名前のマッピングを作成 // キーは uintとし(idを基にゾンビを参照・保管)、バリューはaddress mapping (uint=>address) public zombieToOwner; // ownerZombieCountという名前のマッピングを作成 // キーはaddress、バリューはuint mapping (address=>uint) ownerZombieCount; function _createZombie(string _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } }
チャプター 3: Msg.sender
Msg.senderとは関数を呼び出したユーザー(またはスマートコントラクト)の addressが格納されているグローバル変数のこと。
pragma solidity ^0.4.19; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string _name, uint _dna) private { // 新しいゾンビのidを取得 uint id = zombies.push(Zombie(_name, _dna)) - 1; // id下にmsg.senderを格納してzombieToOwnerマッピングを更新 zombieToOwner[id] = msg.sender; // msg.senderが保有しているownerZombieCountを増やす ownerZombieCount[msg.sender]++; // eventを実行 フロントエンドへidと名前、DNAを返す NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } }
チャプター 4: Require
ざっくりいうと、try catchみたいなやつ。function内の先頭に書くことが一般的らしい。
require(条件, 条件に一致しなかった場合のエラー文);
pragma solidity ^0.4.19; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { // ownerZombieCount[msg.sender] が 0であるかを確認 require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } }
チャプター 5: 継承
一つのファイルにコントラクトを複数書くこともできるけど、
見づらい&管理しづらいので別ファイルに分けたりする。
で、分けたファイル内にあるコントラクトをベースとして使いたいときに、継承というのを使う。
pragma solidity ^0.4.19; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } } // ZombieFactory下にZombieFeedingという名前のコントラクトを作成 // ZombieFactoryコントラクトを継承する contract ZombieFeeding is ZombieFactory { }
チャプター 6: Import
チャプター5の継承を行う際に、別ファイルになっている場合、元となるファイルを読み込む必要がある。
ファイル読み込みに必要なのがimportです。
pragma solidity ^0.4.19; // zombiefeeding.solからzombiefactory.sol をインポート import "./zombiefactory.sol"; contract ZombieFeeding is ZombieFactory { }
チャプター 7: ストレージ vs メモリ
pragma solidity ^0.4.19; import "./zombiefactory.sol"; contract ZombieFeeding is ZombieFactory { // feedAndMultiplyという関数を作成 // _zombieId (uint)と _targetDna (uint)の2つのパラメーターを設定 // publicで作成 function feedAndMultiply(uint _zombieId, uint _targetDna) public { // requireステートメントを追加してmsg.senderがこのゾンビのオーナーであるかどうかを確認 require(msg.sender == zombieToOwner[_zombieId]); // myZombieという名前のローカルZombie(storageポインタとする)を関数で宣言 // zombies配列内の_zombieIdインデックスと同じに Zombie storage myZombie = zombies[_zombieId]; } }
チャプター 8: ゾンビ DNA
ここでは新たなゾンビを生み出すため、ゾンビと人間(_targetDna)を掛け合わせる機能をつくります。
実はこのコードには間違いがあるので注意。private関数(_createZombie)を呼んでしまってます。次回修正がはいるのでそのままにします。
pragma solidity ^0.4.19; import "./zombiefactory.sol"; contract ZombieFeeding is ZombieFactory { function feedAndMultiply(uint _zombieId, uint _targetDna) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; // _targetDnaから16桁取り出す dnaModulusには10**16が入っている _targetDna = _targetDna % dnaModulus; // myZombie.dnaと_targetDnaの平均値を出す uint newDna = (myZombie.dna + _targetDna) / 2; // 指示通りの名前と新たなDNAで新たなゾンビを作る関数を呼ぶ _createZombie("NoName", newDna); } }
チャプター 9: 別の関数とビジビリティ
このチャプターではpublic、privateの他にexternal、internalの2種類について学びます。
おさらいも兼ねて。上から下に向かって制限が強くなるイメージ。
public:外部から実行・参照可能、同じコントラクト内の関数、他のコントラクト内の関数から呼べる関数
external:外部から実行・参照可能、他のコントラクト内の関数からのみ呼べる関数(同じコントラクト内の関数からは呼べない)
internal:外部から実行・参照不可、他のコントラクト内の関数、同じコントラクト内の関数からのみ呼べる関数
private:外部から実行・参照不可、同じコントラクト内の関数からのみ呼べる関数(他のコントラクト内の関数からは呼べない)
pragma solidity ^0.4.19; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; // privateをinternalに変更 // これで他のコントラクトから実行できるようになる function _createZombie(string _name, uint _dna) internal { uint id = zombies.push(Zombie(_name, _dna)) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } }
チャプター 10: ゾンビは何を食べるのか?
ここではインタフェースのつくり方を学びます。
他のコントラクト内の関数を呼び出したい場合に使います。
zombiefactory.solや当ファイルに書かれたfunctionを引数と共にコピーして、
関数内へプログラムを書くことなく関数名、パラメータだけをコピーするだけでOK。
pragma solidity ^0.4.19; import "./zombiefactory.sol"; // KittyInterface をここに作成せよ contract KittyInterface { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { function feedAndMultiply(uint _zombieId, uint _targetDna) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; _createZombie("NoName", newDna); } }
チャプター 11: Interfaceを使用する
ここではチャプター10で作成したインターフェイスを使用します。
pragma solidity ^0.4.19; import "./zombiefactory.sol"; contract KittyInterface { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; // `ckAddress`を使用してkittyContractを初期化 ckAddressに入っているアドレスはクリプトキティというトークンのアドレスです KittyInterface kittyContract = KittyInterface(ckAddress); function feedAndMultiply(uint _zombieId, uint _targetDna) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; _createZombie("NoName", newDna); } }
チャプター 12: 複数の返り値の処理
通常、他のプログラム言語では関数(function)で返せる値は1つなんですが(配列にすれば複数返せますが)、Solidityはなんと複数返せます。
pragma solidity ^0.4.19; import "./zombiefactory.sol"; contract KittyInterface { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; KittyInterface kittyContract = KittyInterface(ckAddress); function feedAndMultiply(uint _zombieId, uint _targetDna) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; _createZombie("NoName", newDna); } // feedOnKittyという名前の関数を作成 _zombieId と_kittyIdというどちらも uintのパラメーターを設定 publicを使用 function feedOnKitty(uint _zombieId, uint _kittyId) public { // kittyDnaというuint の変数を宣言 uint kittyDna; // 複数の戻り値のうち、一番最後のgenesだけを取得 (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); // feedAndMultiplyを呼び出し、_zombieId と kittyDnaの両方を渡す feedAndMultiply(_zombieId, kittyDna); } }
チャプター 13: ボーナス: Kitty Genes
ここではif文をつかった条件分岐で、ある特徴をもったゾンビ猫を作ります。
pragma solidity ^0.4.19; import "./zombiefactory.sol"; contract KittyInterface { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; KittyInterface kittyContract = KittyInterface(ckAddress); // _speciesを追加(クリプトキティかを判定するため) function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; // クリプトキティかを判定 if(keccak256(_species) == keccak256("kitty")) { // クリプトキティなら // 最後の2桁を99に変更する // 334455÷100の余りは55。334455 - 55で334400。334400 + 99で334499になる。どんな値でも末尾が99になる。 newDna = newDna - newDna % 100 + 99; } _createZombie("NoName", newDna); } function feedOnKitty(uint _zombieId, uint _kittyId) public { uint kittyDna; (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); // この関数呼び出しを編集せよ feedAndMultiply(_zombieId, kittyDna, "kitty"); } }
0.8x系だとインターフェイスの宣言方法は、contract ではなく interfaceでした。
ここでレッスン2は終了。次回に続きます。
次の講座はこちら。
Solidity入門基礎編【3】 – CryptoZombiesで学習 –