ys memos

Blog

solidity勉強メモ


solidity

2021/10/10


一度イーサリアム上にデプロイしたコントラクトはイミュータブルになる.


ver
pragma solidity ^x.x.x;

contract
contract MyContract {
}

contract MyChildContract is MyContract {
  // MyContractを継承
  // MyContract内のpublic関数にアクセスできる
}

複数継承する場合は,isの後にカンマ区切りで列挙する.


import
import "./mycontract.sol";

if
if (condition) {
  // do
}

for
for (uint i=0; i<10; i++) {
  // 処理
}


ブロックチェーン上に格納される変数.

書き込むためにはガスが必要.ガスとは,ブロックチェーンにデータを書き込むため通行料となるもの.

storage
T storage var;

関数の呼び出し時に準備され,終了と共に破棄される変数.ガス代は不要.

memory
T memory var;


var
uint hoge = 0; // uint8, uint16, uint32, uint256
string str = "myname";

また,アカウントに割り当てられるaddress型もある.

uintの種別について,uintuint256へのエイリアスである.また,基本的にはどのuintXを選んでも,uint256文のスペースが確保されるが,struct内部ではその限りではない.つまり,ガスの節約のため,struct内部のuintは,必要最低限のものを選ぶ.


arr
uint[] arr; // 配列
arr.push(0); // 末尾に追加
uint len = arr.push(1); // push()は追加後の要素数を返す

// memoryの配列生成
uint[] memory arr = new uint[](n); // 要素数を予め決める必要がある

mapping
mapping (address => uint) public accountData; // アドレスにデータを格納できる.

seconds, minutes, hours, days, weeks, yearsという単位が用意されており,uintの足し算で時間を扱うことができる.また,現在時刻はnowで利用可能.また,nowuint256を返すため,時間単位にuint256以外を使用する場合,明示的にキャストする必要がある.

times
if (now >= (lastTime + 1 days + 30 minutes))) {
  // lastTimeから1日30分経過していると入れる
}


func_format
function <function-name>(T1 _<arg1>, T2 _<arg2>) <visibility modifier> <state modifier1> <state modifier2> returns (<RT1> <ret1>, <RT2> <ret2>) {
  // <function-name>: 関数名
  // <T1> <T2>: 引数の型
  // <arg1> <arg2>: 引数
  // <visibility modifier>: 可視性修飾子(optional),public/private/internal/external
  // <state modifier1> <state modifier2>: 状態修飾子(optional),view/pure,自作可能
  // <RT1> <RT2>: 戻り値の型
  // <ret1> <ret2>: 戻り値の変数名(一つであればつけないほうが楽)
}

func
function myfunc(uint _a, string _s) {
  // 引数はアンダースコアから開始するのが通例とのこと
} // これはパブリック関数

明示的に指定しない限りはpublicとして呼び出されるが,以下のように指定できる.

modifier
function _myPrivateFunc(uint _a, uint _s) private {
} // プライベート関数
// アンダースコアから始めるのがよいらしい

function myRetFunc() public returns(string) {
  return "hello";
} // 戻り値を指定

function myInFunc() internal returns (string) {
  // 継承先から呼び出せるprivate
}

function myExFunc() external returns (string) {
  // コントラクト外のみから呼び出せるpublic
}

viewにすると閲覧のみ,pureにすると内部のみで処理といったように,関数内からのコントラクトへのアクセスを制限できる.view関数は,ブロックチェーン上のデータを変更しないため,ガス代が不要となる.

limitation
function myViewFunc() public view returns (string) {
  return "viewing";
} // 読み取り専用(C++でいうconst修飾子)

function myPureFunc() public pure returns (string) {
  return "pure";
} // アプリ内のデータにもアクセス不可能(引数からのみ戻り値を決定)

以下のようにすると,関数修飾子を作ることができる.引数を使うこともできるので,require()を関数内に直書きするのではなく,修飾子にしておくと再利用可能となり,便利.

modifier_make
modifier isOK(uint id) {
  require(id == 123);
  _; // 続行を意味する(?)
}

function callWithIsOK(uint id) isOK(id) {
  // isOKの時に処理可能
}

Eth を受け取ることができる特別な修飾子.Eth の支払いを伴う関数にはこれをつけなくてはならない.

payable
function myPayaFunc() external payable {
  // Ethを受け取ることができる
  // コントラクトにEthがどのくらい送られてきたかは`msg.value`で参照可能
}

上記でコントラクトが Eth を受け取ったが,これだけではコントラクト内に Eth がとどまってしまうため,コントラクトからどこかに移動する必要がある.

transfer_eth
owner.transfer(this.balance); // コントラクトオーナーのアドレスに残高すべてを送金
msg.sender.transfer(0.1 ether);
seller.transfer(msg.value);
// this.balanceはコントラクト内のEth残高の総量を指す.
// 0.1 ether のようにEthを指定できる
// msg.valueは上述の通りの意味を持ち,コントラクトを介してEthをやり取り可能

call_multiply
function myMultiFunc(uint _a, uint _b) private returns(uint a, uint b) {
  return (_a*2, _b*2);
}

uint a;
uint b;
(a, b) = myMultiFunc(1, 2);
(, b) = myMultiFunc(1, 2);

ブロックチェーン上の動きを,コントラクトがアプリのフロントエンドに伝えることができる.

フロントエンドをlistening状態にし,ブロックチェーンの動きに対し発火することができる.

event
event MyEvent(uint x, uint result);

function func(uint _x) public {
  uint resut = _x * 2;
  MyEvent(_x, result);
  return result;
}

特定の条件を与え,それを満たさない場合は関数とエラー終了する.

require
function myFunc(uint _x) public returns (uint) {
  require(_x == 0);
  return _x;
}


ハッシュ関数の一種.文字列をハッシュする.余談だが,こいつの発音をカタカナで表すとケチャックみたいになるという説が強いらしい.

keccak_random
keccak256(s);
uint hs = uint(keccak256(s));

こいつを使うと,簡易的なランダム値を生成することもできる.



address型を持ち,関数を呼び出したユーザのaddressへの参照.第三者を騙ってコントラクトを呼び出すためには,秘密鍵を盗むしかない.

扱い方としては,通常の変数としても扱え,mappingの Key としても Val としても使える.


他のブロックチェーン上からデータを読み取ることができる.

以下のように書くと,Solidity がインターフェースだと理解してくれる.

interface
contract HogeInterface {
  getHoge(address _addr) public view returns(uint hoge);
}

インターフェースを作ったら,以下のようにして呼び出すことができる.

call_interface
address addr = 0x...;
HogeInterface hogeContract = HogeInterface(addr);

(フロントエンドで関数を呼び出すのと同じ感じ?)


web3.jsethers.jsなどを使うと,Contract を利用することができる.

Solidity のビルド時に ABI が生成され,そこからコントラクトを持ってくる(?)ことができる.


Application Binary Interface の事で,コントラクトコードへのインタフェースが記されている.

contract_new
myContract = new web3.eth.Contract(app_abi, app_address);

view関数およびpure関数を呼び出すために末尾に付ける.ガス不要.

then(() => {})をつなげる.

call
myContract.methods.myFunc(1).call();

view/pureではない関数を呼び出す,つまり,ユーザへ署名を要求する関数を呼び出す時に使う.これにはガス代も含まれる.こいつには秘密鍵が必要で,Metamask による管理を受けるとのこと.

イベントリスナーを連ねていくが,それが,receipt/errorである.

send
myContract.methods.buyCat(1).send();

myContract.methods.buyCat(1)
.send({from: myAccount})
.on("receipt", (receipt) => {
	// トランザクション成功
})
.on("error", (error) => {
	// トランザクション失敗
})

myContract.methods.buyCat(1)
.send({from: myAccount, gas: 300}) // ガスを指定可能,非指定時はユーザ選択

call()およびsend()は,フロントエンド開発において,JavsScript 側でわかりやすい名前をつけた関数を再定義しておくと,開発が楽になる.


1ether = 10^18weiとなる Ether の最小単位.web3.js では,Ether 単位ではなく Wei 単位で指定する必要がある.

convert_ether_to_wei
web3js.utils.toWei("0.1", "ether");

myContract.methods.buyCat(1)
.send({ from: myAccount, value: web3js.utils.toWei("0.1", "ether")})

フロントエンドで,コントラクトのイベントをサブスクリライブできる.

event
myContract.events.SoldCat()
.on("data", (event) => {
	let cat = event.returnValues;
	console.log('cat was sold, ', cat.id, cat.name);
}) // コントラクト内のデータが変更される度に情報が送られてくる
.on("error", (error) => {
	console.log("Error: ", error);
})

// ERC721において,フィルタしてイベントを受け取る
myContract.events.Transfer({ filter: { _to: myAccount } })
.on("data", (event) => {
	let cat = event.returnValues;
})

イーサリアム上のトークンとは,特定のルールに基づいたスマートコントラクトのこと.

クリプト収集物にはERC721が適しているらしく,用途によって使い分ける必要がある.クリプトキティーズも ERC721 だった


オーバフロー・アンダフローを防ぐ.

safemath
using SafeMath for uint256;
uint i = 1;
i = i.add(1);
i = i.sub(1);
i = i.mul(10);
i = i.div(10);

Solidity はブロックチェーン上のデータであるstorageとローカル(?)上のmemoryの2種類のデータ保存方法がある.後者は通常のプログラミングと一緒であるが,storageは考え方そのものが違う.

通常のプログラミングであれば,本来なら計算量を削減するためにデータ構造やアルゴリズムを組み合わせて,より走査回数や計算回数が減少するようなプログラムを書く.オーダ記法で考えることで,スケールしたときの処理速度の増大を防いだりもする.

しかし,storageを扱うことはコストが高く,特に,storageへの書き込みには莫大なコストがかかる.そのため,走査回数を減らすのではなく,storageへの書き込みができるだけ少なく済むように設計する必要がある.

例えば,配列に ID 0<=id<=100が割り当てられているとする.id=xである配列内の要素を繰り返し探す必要があるという場合のことを考える.配列の全要素を確認してid=xとなる要素を探すと,O(N)となってしまう.

そこで通常,idから配列要素への参照を用意しておき,idの変更が行われる度に参照を書き換えることで,O(logN)O(1)へと計算量を削減することができる.これはいわば,メモリとそのデータ更新を犠牲にして計算量を節約しているとも言える.

しかし,Solidity においてstorageを書き換えることは高コストなため,O(N)の走査時間をとってでも,storageのデータを書き換える回数を抑えるという方法を取ることがある.


関連タグを探す