Contracts¶
Solidityに置けるコントラクトはオブジェクト指向の言語におけるクラスに似ています。コントラクトは永続的な状態変数とそれを変更するファンクションを保持しています。異なるコントラクト上(インスタンス上)のファンクションコールはEVMのファンクションコールとなり、その結果コンテクストが変わり、状態変数にアクセスできなくなります。コントラクトとそのファンクションは呼び出さないと動きません。Ethereumには"cron"の様に特定のイベントで自動的にファンクションを呼び出す機能はありません。
Creating Contracts¶
コントラクトはEthreumトランザクションを通じて"外部から"、もしくはSolidityコントラクトの内側から作成可能です。
Remix の様なIDEはUIの要素を使って、シームレスに生成プロセスを作ります。
Ethreum上にプログラムでコントラクトを作成するにはJavaScript API web3.js を使うのがベストです。 web3.jsは簡単にコントラクトを作るための web3.eth.Contract というファンクションを持っています。
コントラクトが作られた時、constructor ( constructor
キーワードで宣言されるファンクション)が一度だけ実行されます。
コンストラクタはオプションです。1つのコンストラクタだけ使えます。つまりオーバーロードはサポートされていません。
コンストラクタが実行された後、最終的なコントラクトのコードがブロックチェーン上にデプロイされます。このコードは全てのpublic、externalのファンクションを含んでおり、全てのファンクションはファンクションコールを通じてそのコードからアクセスできます。デプロイされたコードはコンストラクタもしくはコンストラクタによって呼ばれたinternalのファンクションだけ含んでいません。
内部的にはコントラクトのコードの後、コンストラクタの引数は ABI encoded されて渡されますが、もし web3.js
を使っているのであればきにする必要はありません。
もしコントラクトが別のコントラクトを作りたい場合、作成されたコントラクトのソースコード(とそのバイナリ)は作成者によって把握されている必要があります。 つまり、cyclic creation dependenciesは出来ないということです。
pragma solidity >=0.4.22 <0.6.0;
contract OwnedToken {
// `TokenCreator` is a contract type that is defined below.
// It is fine to reference it as long as it is not used
// to create a new contract.
TokenCreator creator;
address owner;
bytes32 name;
// This is the constructor which registers the
// creator and the assigned name.
constructor(bytes32 _name) public {
// State variables are accessed via their name
// and not via e.g. `this.owner`. Functions can
// be accessed directly or through `this.f`,
// but the latter provides an external view
// to the function. Especially in the constructor,
// you should not access functions externally,
// because the function does not exist yet.
// See the next section for details.
owner = msg.sender;
// We do an explicit type conversion from `address`
// to `TokenCreator` and assume that the type of
// the calling contract is `TokenCreator`, there is
// no real way to check that.
creator = TokenCreator(msg.sender);
name = _name;
}
function changeName(bytes32 newName) public {
// Only the creator can alter the name --
// the comparison is possible since contracts
// are explicitly convertible to addresses.
if (msg.sender == address(creator))
name = newName;
}
function transfer(address newOwner) public {
// Only the current owner can transfer the token.
if (msg.sender != owner) return;
// We ask the creator contract if the transfer
// should proceed by using a function of the
// `TokenCreator` contract defined below. If
// the call fails (e.g. due to out-of-gas),
// the execution also fails here.
if (creator.isTokenTransferOK(owner, newOwner))
owner = newOwner;
}
}
contract TokenCreator {
function createToken(bytes32 name)
public
returns (OwnedToken tokenAddress)
{
// Create a new `Token` contract and return its address.
// From the JavaScript side, the return type is
// `address`, as this is the closest type available in
// the ABI.
return new OwnedToken(name);
}
function changeName(OwnedToken tokenAddress, bytes32 name) public {
// Again, the external type of `tokenAddress` is
// simply `address`.
tokenAddress.changeName(name);
}
// Perform checks to determine if transferring a token to the
// `OwnedToken` contract should proceed
function isTokenTransferOK(address currentOwner, address newOwner)
public
pure
returns (bool ok)
{
// Check an arbitrary condition to see if transfer should proceed
return keccak256(abi.encodePacked(currentOwner, newOwner))[0] == 0x7f;
}
}
Visibility and Getters¶
Solidityは2しゅるのファンクションコール(実際のEVMコールを作らないinternalのもの("message call"とも呼ばれます)とEVMコールを作るexternalのもの)があるので、ファンクションと状態変数に対して4種類の可視性があります。
ファンクションは external
、public
、internal
、private
のいずれかを指定しなければいけません。状態変数には、external
は使えません。
external
:- externalのファンクションはコントラクトインターフェースの一部です。つまり、他のコントラクトからトランザクションを通じて呼び出すことができます。あるexternalのファンクション
f
はコントラクト内部では呼び出せません(f()
は動きませんが、this.f()
は動作します)。 大きい配列を受け取る時、externalファンクションは場合によってより効率が良くなります。 public
:- externalのファンクションはコントラクトインターフェースの一部で、内部でも呼び出せますし、もしくはメッセージを通じて呼び出せます。publicの状態変数は自動的にgetter(下記参照)を生成します。
internal
:- internalのファンクションと状態変数は
this
を使わずに、コントラクト内部でのみアクセスできます(現在のコントラクトからか、それを継承したコントラクトから)。 private
:- privateのファンクションと状態変数はそれが定義されたコントラクト内のみで可視で、継承したコントラクトでは使えません。
注釈
コントラクト内側は全てブロックチェーン外側から可視です。private
だけが他のコントラクトがアクセスしたり変更したりするのを防いでくれます。しかし、それでもブロックチェーンの外側から可視です。
visibility specifierは状態変数の型の後、パラメータリストとファンクションの返り値リストの間に置かれます。
pragma solidity >=0.4.16 <0.6.0;
contract C {
function f(uint a) private pure returns (uint b) { return a + 1; }
function setData(uint a) internal { data = a; }
uint public data;
}
次の例では、D
はステートストレージ内の data
の値を引き出すのに c.getData()
を呼び出すことができます。しかし、D
は f
を呼べません。コントラクト E
は C
を継承しているため、compute
を呼び出すことができます。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint private data;
function f(uint a) private pure returns(uint b) { return a + 1; }
function setData(uint a) public { data = a; }
function getData() public view returns(uint) { return data; }
function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
// This will not compile
contract D {
function readData() public {
C c = new C();
uint local = c.f(7); // error: member `f` is not visible
c.setData(3);
local = c.getData();
local = c.compute(3, 5); // error: member `compute` is not visible
}
}
contract E is C {
function g() public {
C c = new C();
uint val = compute(3, 5); // access to internal member (from derived to parent contract)
}
}
Getter Functions¶
コンパイラは自動的に public の状態変数のgetterを生成します。下のコントラクトでは、コンパイラは data
という引数を取らず、uint
型の状態変数 data
の値を返します。 状態変数は宣言された時に初期化することができます。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint public data = 42;
}
contract Caller {
C c = new C();
function f() public view returns (uint) {
return c.data();
}
}
getterはexternalの可視性を持っています。ある記号が内部的にアクセス(this.
なし)されたら、それは状態変数と評価されます。もし外部的にアクセス(this.
あり)されたら、それはファンクションと評価されます。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint public data;
function x() public returns (uint) {
data = 3; // internal access
return this.data(); // external access
}
}
もし public
の配列型の状態変数を持っていたら、getterではその配列の1要素しか取り出すことができません。配列全体を返したときにガスが高くならない様にこの仕組みはあります。どの要素を返すか、例えば data(0)
の様に引数を使って指定することができます。もし配列全体を返したい場合は、ファンクションを作る必要があります。例えば:
pragma solidity >=0.4.0 <0.6.0;
contract arrayExample {
// public state variable
uint[] public myArray;
// Getter function generated by the compiler
/*
function myArray(uint i) returns (uint) {
return myArray[i];
}
*/
// function that returns entire array
function getArray() returns (uint[] memory) {
return myArray;
}
}
ここで、配列全体を取り出すために、1回の呼び出しで1つの要素を返す myArray(i)
の代わりに、getArray()
を使うことができます。
次の例はもっと複雑です。
pragma solidity >=0.4.0 <0.6.0;
contract Complex {
struct Data {
uint a;
bytes3 b;
mapping (uint => uint) map;
}
mapping (uint => mapping(bool => Data[])) public data;
}
これは次の形のファンクションを生成します。マッピング用のキーを渡す良い方法がないので、構造体のマッピングは省略します。
function data(uint arg1, bool arg2, uint arg3) public returns (uint a, bytes3 b) {
a = data[arg1][arg2][arg3].a;
b = data[arg1][arg2][arg3].b;
}
Function Modifiers¶
modifierはファンクションの挙動を簡単に変えるのに使うことができます。例えば、ファンクションの実行前に自動的にある条件をチェックできます。modifierは継承可能なコントラクトのプロパティで、継承されたコントラクトによってオーバーライドされるかもしれません。
pragma solidity ^0.5.0;
contract owned {
constructor() public { owner = msg.sender; }
address payable owner;
// This contract only defines a modifier but does not use
// it: it will be used in derived contracts.
// The function body is inserted where the special symbol
// `_;` in the definition of a modifier appears.
// This means that if the owner calls this function, the
// function is executed and otherwise, an exception is
// thrown.
modifier onlyOwner {
require(
msg.sender == owner,
"Only owner can call this function."
);
_;
}
}
contract mortal is owned {
// This contract inherits the `onlyOwner` modifier from
// `owned` and applies it to the `close` function, which
// causes that calls to `close` only have an effect if
// they are made by the stored owner.
function close() public onlyOwner {
selfdestruct(owner);
}
}
contract priced {
// Modifiers can receive arguments:
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) public { price = initialPrice; }
// It is important to also provide the
// `payable` keyword here, otherwise the function will
// automatically reject all Ether sent to it.
function register() public payable costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint _price) public onlyOwner {
price = _price;
}
}
contract Mutex {
bool locked;
modifier noReentrancy() {
require(
!locked,
"Reentrant call."
);
locked = true;
_;
locked = false;
}
/// This function is protected by a mutex, which means that
/// reentrant calls from within `msg.sender.call` cannot call `f` again.
/// The `return 7` statement assigns 7 to the return value but still
/// executes the statement `locked = false` in the modifier.
function f() public noReentrancy returns (uint) {
(bool success,) = msg.sender.call("");
require(success);
return 7;
}
}
複数のmodifierはホワイトスペースで分けられたリストで明記されあるファンクションに適用されます。書かれた順番に評価されます。
警告
以前のSolidityでは return
はmodifierを持つファンクションの中では異なった挙動をしていました。
modifierもしくはファンクション本体からの明示的なreturnは現在のmodifierもしくはファンクション本体からしか出ません。返ってきた変数は割り当てられ、制御フローは先に処理されたmodifierの"_"の後に続きます。
modifierの引数に任意の式が使えます。このコンテキストに置いて、ファンクションから可視の全ての記号はmodifierでも可視です。modifierで処理される記号はファンクションからは可視ではありません(オーバーライドで変わってしまう可能性があるため)。
Constant State Variables¶
状態変数は constant
として宣言することができます。この場合、コンパイル時に定数となる式で値が割り当てされている必要があります。ストレージ、ブロックチェーンデータ (例: now
、address(this).balance
、block.number
)、
実行データ (msg.value
、gasleft()
) にアクセスする式、もしくは外部のコントラクトを呼び出す式は使えません。メモリ位置の変更に影響があるかもしれない式は使えますが、メモリのオブジェクトに影響があるかもしれない式は使えません。組み込みファンクションの keccak256
、sha256
、ripemd160
、ecrecover
、addmod
、mulmod
は使用可能です( keccak256
の例外は外部コントラクトを呼び出しますが)。
メモリ位置への影響を許容する理由としては、lookup-tablesの様な複雑なオブジェクトの作成が可能であるべきだからです。 ただ、この機能はまだ完全に使用可能というわけではありません。
コンパイラはこの変数のためのストレージスロットを保持しません。結果は全て各定数式で置き換えられます(オプティマイザによって1つの値に計算されるかもしれません)。
全ての型の定数がここで実行される訳ではありません。値型と文字列だけサポートされています。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint constant x = 32**22 + 8;
string constant text = "abc";
bytes32 constant myHash = keccak256("abc");
}
Functions¶
Function Parameters and Return Variables¶
JavaScriptに見られる様に、ファンクションは入力としてパラメータをとります。JavaScriptやCと違い、ファンクションは任意の数の値を出力できます。
Function Parameters¶
ファンクションのパラメータは変数と同じ様に宣言されます。使われなかったパラメータは省略されます。
例えば、もし2つの整数でexternal callをしたい場合、次の様にできます:
pragma solidity >=0.4.16 <0.6.0;
contract Simple {
uint sum;
function taker(uint _a, uint _b) public {
sum = _a + _b;
}
}
ファンクションパラメータはローカル変数としても使えますし、その値を割り当てすることもできます。
注釈
external function は入力パラメータとして多次元配列を受け入れません。この機能に関しては、ソースファイルに pragma experimental ABIEncoderV2;
を追加して、新しい実験的な ABIEncoderV2
機能を有効にすれば使うことができます。
internal function はこの機能を有効にしなくても多次元配列を使うことができます。
Return Variables¶
ファンクションの返り値は returns
キーワードの後、同じシンタックスで宣言されます。
例えば2つの結果が欲しい時: ファンクションパラメータとして渡された2つの整数の和と積が欲しい時、次の様に書けます:
pragma solidity >=0.4.16 <0.6.0;
contract Simple {
function arithmetic(uint _a, uint _b)
public
pure
returns (uint o_sum, uint o_product)
{
o_sum = _a + _b;
o_product = _a * _b;
}
}
返り値の名前は省略可能です。返り値は他のローカル変数として使えます。返り値は default value で初期化されており、明示的に値をセットしない限りその値を持ちます。
明示的に変数に値を割り当て、return;
を使うか、直接 return
に返り値(1つもしくは multiple ones )を入れるかいずれかが可能です:
pragma solidity >=0.4.16 <0.6.0;
contract Simple {
function arithmetic(uint _a, uint _b)
public
pure
returns (uint o_sum, uint o_product)
{
return (_a + _b, _a * _b);
}
}
これは返り値を変数に割り当てて return;
を使って返す方法と結果は同じです。
注釈
internalでないファンクションからいくつかの型は返すことはできません。特に多次元動的配列と構造体です。ソースファイルに pragma experimental
ABIEncoderV2;
を加えて新しい実験的な ABIEncoderV2
機能を有効にすればもっと他の型も使える様になります。しかし、mapping
型はそれでも1つのコントラクト内でしか扱うことができず、転送することはできません。
Returning Multiple Values¶
ファンクションが複数の返り値の型を持つ時、return (v0, v1, ..., vn)
という宣言が複数の型を返すのに使用されます。
要素の数は返す値の数と同じでなければいけません。
View Functions¶
ステートを変えない場合ファンクションは view
を宣言できます。
注釈
もしコンパイラのEVMターゲットかByzantiumより新しい場合、EVM実行の一部としてステートの変更をさせないopcode STATICCALL
が view
ファンクションに使われます。ライブラリの view
ファンクションには DELEGATECALL
が使用されます。なぜなら、DELEGATECALL
と STATICCALL
が一緒になったものは存在しないからです。つまり、ライブラリの view
ファンクションはステートの変更を妨げるランタイムチェックを持っていないということです。ライブラリのコードは普通はコンパイル時に既知であり、static checkerがコンパイル時にチェックするため、これはセキュリティ的に問題ないはずです。
下記のリストはステートを変更すると考えられます。
- 状態変数を書く。
- イベントのemit。
- 他のコントラクトを作る。
selfdestruct
を使う- callを通じてEtherを送る。
view
やpure
の付いていないファンクションを呼び出す。- 低レベルcallを使う。
- あるopcodeの入ったインラインアセンブリを使う。
pragma solidity ^0.5.0;
contract C {
function f(uint a, uint b) public view returns (uint) {
return a * (b + 42) + now;
}
}
注釈
ファンクションで constant
は view
のエイリアスとして使われていましたが、バージョン0.5.0でドロップされました。
注釈
getterメソッドは自動的に view
がつきます。
注釈
バージョン0.5.0以前ではコンパイラは view
ファンクションに STATICCALL
を使っていませんでした。
これは、無効な明示的型変換を使うことで、view
ファンクションでのステートの変更を可能にしていました。
STATICCALL
を view
ファンクションに使うことで、EVM上ではステートの変更を行うことができなくなりました。
Pure Functions¶
何も読まない、ステートも変更しない場合、ファンクションで pure
を宣言できます。
注釈
コンパイラのEVMターゲットがByzantium以降であれば、opcode STATICCALL
が使えます。ステートが読まれないかは保証しませんが、少なくともステートが変更されていないことは保証されます。
ステートを変更する上記のリストに加えて、下記はステートを読み込むとされる処理のリストです。
- 状態変数から読み込む。
address(this).balance
もしくは<address>.balance
にアクセスする。block
、tx
、msg
(msg.sig
とmsg.data
は除く)のどれかにアクセスする。pure
が付いていないファンクションを呼び出す。- あるopcodeの入ったインラインアセンブリを使う。
pragma solidity ^0.5.0;
contract C {
function f(uint a, uint b) public pure returns (uint) {
return a * (b + 42);
}
}
Pureファンクションは エラーが起きた 時に、潜在的なステートの変更を元に戻すため revert() と require() を使えます。
ステートを元に戻すのは"ステートの変更"とは見なされません。
なぜなら、view
もしくは pure
がないコードで以前に作られたステートへの変更だけがrevertされていたためです。さらに、そのコードは revert
をキャッチし、受け渡ししないオプションがあります。
この挙動は STATICCALL
opcodeにも合っています。
警告
EVMレベルではファンクションがステートを読み込むことを止めることはできません。できるのは書き込みを止めることだけです( view
がEVMレベルで強制されますが、pure
はされません)。
注釈
バージョン0.5.0以前ではコンパイラは pure
ファンクションに STATICCALL
を使っていませんでした。
これは、無効な明示的型変換を使うことで、view
ファンクションでのステートの変更を可能にしていました。
STATICCALL
を pure
ファンクションに使うことで、EVM上ではステートの変更を行うことができなくなりました。
注釈
バージョン0.4.17以前では、コンパイラは pure
にステートを読まなせないということを強制していませんでした。
コンパイル時の型チェックで、コントラクト型間での無効な明示的変換を避けることができます。なぜなら、コンパイラがそのタイプのコントラクトはステートを変える操作をしないと証明するからです。ただ、ランタイム時に呼ばれるコントラクトに関しては実際にそのタイプかどうかはチェックしません。
Fallback Function¶
コントラクトは1つだけ名前の付いていないファンクションを持つことができます。そのファンクションは引数を持てず、何も返せません。そして可視性は external
である必要があります。
もし、他のファンクションが与えられたファンクションの識別子になかった場合(もしくは何のデータも渡されなかった場合)、そのコントラクトが呼ばれた時に実行されます。
さらに、このファンクションはコントラクトが(データなしの)Etherを受け取った時は実行されます。Etherを受け取って、コントラクトのトータルバランスにそれを追加するにはフォールバックファンクションは payable
でなければいけません。もしそのようなファンクションがない場合、コントラクトは通常のトランザクションを通じてEtherを受け取れず、例外を投げます。
最悪の場合、フォールバックファンクションは2300ガスだけを利用可能(例えば send か transfer を使うのに)とします。基本的なログの機能以外に他の演算のための余力をほとんど残しません。下記の演算は固定で2300ガス以上使う演算です。
- ストレージに書き込む
- コントラクトを作る
- ガスを多く使う外部ファンクションを呼び出す
- Etherを送る
他のファンクションのように、フォールバックファンクションは十分なガスが渡される限り、複雑な演算も実行可能です。
注釈
フォールバックファンクションは引数を持てませんが、呼び出しで供給されたペイロードを引き出すのに、msg.data
を使うことができます。
警告
呼び出し元が利用不可なファンクションを呼び出した時にもフォールバックファンクションは実行されます。Etherを受け取るためだけにフォールバックファンクションを実行したい場合、不正な呼び出しを防ぐために require(msg.data.length == 0)
を追加した方が良いでしょう。
警告
Etherを直接受け取る(ファンクションコール、つまり send
か transfer
を伴わない)が、フォールバックファンクションを定義しないコントラクトは例外を投げ、Etherを送り返します(Solidityバージョン0.4.0以前では違いました)。そのため、コントラクトでEtherを受け取りたい場合、payableのフォールバックファンクションを実装しなければいけません。
警告
payableフォールバックファンクションがないコントラクトは coinbase transaction (もしくは miner block reward)として、もしくは selfdestruct
の送り先としてEtherを受け取ることができます。
コントラクトはそのようなEtherの送金に関して何の反応もできないため、それを拒否することもできません。 これはEVMの設計であるため、Solidityではどうにもできません。
これは、address(this).balance
がコントラクト内で処理された手動の会計処理の合計より高くなりうることを意味しています(フォールバックファンクションでアプデートされるカウンタを持っているということです)。
pragma solidity ^0.5.0;
contract Test {
// This function is called for all messages sent to
// this contract (there is no other function).
// Sending Ether to this contract will cause an exception,
// because the fallback function does not have the `payable`
// modifier.
function() external { x = 1; }
uint x;
}
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
function() external payable { }
}
contract Caller {
function callTest(Test test) public returns (bool) {
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1.
// address(test) will not allow to call ``send`` directly, since ``test`` has no payable
// fallback function. It has to be converted to the ``address payable`` type via an
// intermediate conversion to ``uint160`` to even allow calling ``send`` on it.
address payable testPayable = address(uint160(address(test)));
// If someone sends ether to that contract,
// the transfer will fail, i.e. this returns false here.
return testPayable.send(2 ether);
}
}
Function Overloading¶
コントラクトは異なるパラメータを持つ、同じ名前のファンクションを複数持つことができます。
このプロセスは"オーバーロード"といわれ、継承されたファンクションにも適用されます。
コントラクト A
のスコープに入っているファンクション f
のオーバーロードの例を下記に示します。
pragma solidity >=0.4.16 <0.6.0;
contract A {
function f(uint _in) public pure returns (uint out) {
out = _in;
}
function f(uint _in, bool _really) public pure returns (uint out) {
if (_really)
out = _in;
}
}
オーバーロードされたファンクションは外部インターフェースの中にもあります。もし2つの外部的に可視なファンクションが外部としての型ではなく、Solidityの型として異なる場合エラーになります。
pragma solidity >=0.4.16 <0.6.0;
// This will not compile
contract A {
function f(B _in) public pure returns (B out) {
out = _in;
}
function f(address _in) public pure returns (address out) {
out = _in;
}
}
contract B {
}
両方の f
ファンクションはABIとしてはアドレス型を受け入れてオーバーロードしますが、Solidity内では違うものとして考えられます。
Overload resolution and Argument matching¶
オーバーロードされたファンクションは、現在のスコープ内のファンクションの宣言をファンクションコール内で渡された引数に合わせることによって選択されます。 全ての引数が暗示的に期待する型に変換できる場合、ファンクションはオーバーロードの候補として選ばれます。もし候補がなければ、そのresolutionは失敗します。
注釈
返ってくるパラメータはオーバーロードresolutionに考慮されません。
pragma solidity >=0.4.16 <0.6.0;
contract A {
function f(uint8 _in) public pure returns (uint8 out) {
out = _in;
}
function f(uint256 _in) public pure returns (uint256 out) {
out = _in;
}
}
50
uint8
と uint256
どちらにも暗示的に変換できるため、f(50)
の呼び出しは型エラーを生成します。
一方で、256
は暗示的に uint8
に変換できないため f(256)
は f(uint256)
オーバーロードします。
Events¶
SolidityのイベントでEVMのログ機能からデータを抽出します。アプリケーションはEthereumクライアントのRPCインターフェースを通じてイベントをsubscribeします。
イベントは継承可能なコントラクトのメンバです。イベントを呼び出すと、トランザクションのログ内に引数を保存します(ブロックチェーン上の特別なデータ構造です)。これらのログはコントラクトアドレスに結びつき、ブロックにアクセスできる限りそこに保存されます(FrontierとHomesteadからは永久にアクセス出来ますが、Serenityで変わる可能性があります)。ログとイベントデータはコントラクトからはアクセス出来ません(そのイベントを作ったコントラクトからもです)。
simple payment verification (SPV)にログをリクエスト出来るので、もし外部からその様なverificationでコントラクトを作った場合、そのSPVからその様なログが実際にブロックチェーン上に存在するかチェックすることができます。コントラクトは直近256ブロック分のハッシュしか見れないので、ブロックヘッダを渡す必要があります。
最大3つのパラメータに indexed
属性を追加することができます。それはログのデータの代わりに "topics" で知られる特別なデータ構造を追加します。インデックスされた引数として配列(string
と ``bytes``を含む)を使う場合、Keccak-256ハッシュをtopicとして代わりに保存します。なぜならtopic一つの単語(32バイト)しか保存できないからです。
全ての indexed
属性が付いていないパラメータはログのデータ部に ABI-encoded されます。
topicを使うとイベントを検索することができます。例えば、あるイベントに関してブロックシーケンスをフィルターできます。イベントから出たコントラクトアドレスでもイベントをフィルターできます。
例えば以下のコードではあるアドレス値でtopicにマッチするログをフィルターするためにweb3.js subscribe("logs")
method が使われています。
var options = {
fromBlock: 0,
address: web3.eth.defaultAccount,
topics: ["0x0000000000000000000000000000000000000000000000000000000000000000", null, null]
};
web3.eth.subscribe('logs', options, function (error, result) {
if (!error)
console.log(result);
})
.on("data", function (log) {
console.log(log);
})
.on("changed", function (log) {
});
イベントを anonymous
で宣言していなければ、イベントの署名のハッシュはtopicの1つとなります。つまり、特定のanonymousイベントに関しては名前でフィルターできません。
pragma solidity >=0.4.21 <0.6.0;
contract ClientReceipt {
event Deposit(
address indexed _from,
bytes32 indexed _id,
uint _value
);
function deposit(bytes32 _id) public payable {
// Events are emitted using `emit`, followed by
// the name of the event and the arguments
// (if any) in parentheses. Any such invocation
// (even deeply nested) can be detected from
// the JavaScript API by filtering for `Deposit`.
emit Deposit(msg.sender, _id, msg.value);
}
}
下記はJavaScript APIにおける使用例です。
var abi = /* abi as generated by the compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */);
var event = clientReceipt.Deposit();
// watch for changes
event.watch(function(error, result){
// result contains non-indexed arguments and topics
// given to the `Deposit` call.
if (!error)
console.log(result);
});
// Or pass a callback to start watching immediately
var event = clientReceipt.Deposit(function(error, result) {
if (!error)
console.log(result);
});
上記の出力は以下の様になります(トリムされています)。
{
"returnValues": {
"_from": "0x1111…FFFFCCCC",
"_id": "0x50…sd5adb20",
"_value": "0x420042"
},
"raw": {
"data": "0x7f…91385",
"topics": ["0xfd4…b4ead7", "0x7f…1a91385"]
}
}
Low-Level Interface to Logs¶
log0
、log1
、log2
、log3
、log4
ファンクションを使うことで、ログ機能の低レベルインターフェースにアクセスすることが可能です。logi
は bytes32
型の i + 1
パラメータをとります。最初の引数はログのデータ部として使用され、他はtopicとして使用されます。上記のイベントコールは下記と同じ様に実行されます。
pragma solidity >=0.4.10 <0.6.0;
contract C {
function f() public payable {
uint256 _id = 0x420042;
log3(
bytes32(msg.value),
bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20),
bytes32(uint256(msg.sender)),
bytes32(_id)
);
}
}
ここで、長い16進数の値はイベントの署名である keccak256("Deposit(address,bytes32,uint256)")
と等しいです。
Additional Resources for Understanding Events¶
Inheritance¶
Solidityはポリモフィズムを含めた多重継承をサポートしています。
全てのファンクションコールは仮想です。つまりコントラクト名が明示的に与えられた場合、super
が使われた場合を除いて、最後に派生されたファンクションが呼ばれます。
あるコントラクトがある他のコントラクトを継承するとき、1つのコントラクトだけがブロックチェーン上に生成されます。全てのベースコントラクトからのコードは生成したコントラクトにコンパイルされます。
継承のシステム全般は Python にとても似ています。特に多重継承は似ていますが、いくつかの 違い もあります。
詳細は下記の例で示します。
pragma solidity ^0.5.0;
contract owned {
constructor() public { owner = msg.sender; }
address payable owner;
}
// Use `is` to derive from another contract. Derived
// contracts can access all non-private members including
// internal functions and state variables. These cannot be
// accessed externally via `this`, though.
contract mortal is owned {
function kill() public {
if (msg.sender == owner) selfdestruct(owner);
}
}
// These abstract contracts are only provided to make the
// interface known to the compiler. Note the function
// without body. If a contract does not implement all
// functions it can only be used as an interface.
contract Config {
function lookup(uint id) public returns (address adr);
}
contract NameReg {
function register(bytes32 name) public;
function unregister() public;
}
// Multiple inheritance is possible. Note that `owned` is
// also a base class of `mortal`, yet there is only a single
// instance of `owned` (as for virtual inheritance in C++).
contract named is owned, mortal {
constructor(bytes32 name) public {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).register(name);
}
// Functions can be overridden by another function with the same name and
// the same number/types of inputs. If the overriding function has different
// types of output parameters, that causes an error.
// Both local and message-based function calls take these overrides
// into account.
function kill() public {
if (msg.sender == owner) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).unregister();
// It is still possible to call a specific
// overridden function.
mortal.kill();
}
}
}
// If a constructor takes an argument, it needs to be
// provided in the header (or modifier-invocation-style at
// the constructor of the derived contract (see below)).
contract PriceFeed is owned, mortal, named("GoldFeed") {
function updateInfo(uint newInfo) public {
if (msg.sender == owner) info = newInfo;
}
function get() public view returns(uint r) { return info; }
uint info;
}
上記注目して頂きたいのですが、破壊の要求を"転送"するのに mortal.kill()
を呼んでいます。これは下記の例で見られる様に問題があります:
pragma solidity >=0.4.22 <0.6.0;
contract owned {
constructor() public { owner = msg.sender; }
address payable owner;
}
contract mortal is owned {
function kill() public {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() public { /* do cleanup 1 */ mortal.kill(); }
}
contract Base2 is mortal {
function kill() public { /* do cleanup 2 */ mortal.kill(); }
}
contract Final is Base1, Base2 {
}
Final.kill()
のコールは、最後にオーバーライドされたものとして Base2.kill
を呼び出し、このファンクションは Base1.kill
をバイパスします。なぜなら、そのファンクションは Base1
を把握していないからです。これの回避策は super
を使うことです:
pragma solidity >=0.4.22 <0.6.0;
contract owned {
constructor() public { owner = msg.sender; }
address payable owner;
}
contract mortal is owned {
function kill() public {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() public { /* do cleanup 1 */ super.kill(); }
}
contract Base2 is mortal {
function kill() public { /* do cleanup 2 */ super.kill(); }
}
contract Final is Base1, Base2 {
}
もし Base2
が super
のファンクションを呼び出しても、単純にベースコントラクトの内の1つのこのファンクションを呼び出しません。最終的な継承図の中のベースコントラクトの次のコントラクトのファンクションを呼び出します。そのため、Base1.kill()
を呼び出します(最終的な継承の順番は、最後に継承されたコントラクトから始まります: Final、Base2、Base1、mortal、owned)。
superを使った時に呼び出される実際のファンクションは、型が分かっていても、そのクラスのコンテキストの中ではわかりません。これは一般的な仮想メソッドの検索に似ています。
Constructors¶
コンストラクタは constructor
キーワードで宣言され、コントラクト生成時に実行される任意のファンクションで、コントラクトの初期化コードを実行できます。
コンストラクタが実行される前に、インラインで初期化していれば状態変数はその値で初期化され、していなければ0になります。
コンストラクタが実行された後、コントラクトの最終的なコードがブロックチェーンにデプロイされます。コードのデプロイはコードの長さに比例して追加のガスコストがかかります。 このコードはpublicインターフェースの一部でありファンクション全てと、ファンクションコールを通じてアクセスできるファンクションを含んでいます。 このコードはコンストラクタのコードと、コンストラクタからのみ呼ばれるinternalのファンクションは含んでいません。
コンストラクタは public
か internal
です。もし、コンストラクタがなかったら、コントラクトはデフォルトのコンストラクタ( constructor() public {}
と等価の)を想定します。例えば:
pragma solidity ^0.5.0;
contract A {
uint public a;
constructor(uint _a) internal {
a = _a;
}
}
contract B is A(1) {
constructor() public {}
}
コンストラクタを internal
でセットすると、そのコントラクトは abstract になります。
警告
0.4.22以前ではコンストラクタはコントラクトと同じ名前のファンクションとして定義されていました。このシンタックスは非推奨となり、バージョン0.5.0で使えなくなりました。
Arguments for Base Constructors¶
全てのベースコントラクトのコンストラクタは下記で説明される線形ルールに則り呼び出されます。もしベースコンストラクタが引数を持っていたら、継承したコントラクトはその全てを指定する必要があります。 2通りの方法でできます:
pragma solidity >=0.4.22 <0.6.0;
contract Base {
uint x;
constructor(uint _x) public { x = _x; }
}
// Either directly specify in the inheritance list...
contract Derived1 is Base(7) {
constructor() public {}
}
// or through a "modifier" of the derived constructor.
contract Derived2 is Base {
constructor(uint _y) Base(_y * _y) public {}
}
1つ目の方法は直接継承のリストに入れることです(is Base(7)
)。もう1つの方法は継承したコンストラクタの一部としてmodifierを呼び出します(Base(_y * _y)
)。もしコンストラクタの引数が定数で、コントラクトの挙動を決めるもしくは表現するものである場合、最初の方法の方が便利です。もしベースのコンストラクタの引数が継承したコントラクトによるのであれば、2つ目の方法を使う必要があります。引数は継承のリスト、もしくは継承したコンストラクタのmodifierスタイルで与えられる必要があります。
両方で引数を指定するとエラーとなります。
もし継承したコントラクトがベースコントラクトのコンストラクタに対する引数を決めなかった場合、そのコントラクトは抽象コントラクトになります。
Multiple Inheritance and Linearization¶
多重継承ができる言語はいくつかの問題を扱わなければいけません。1つは Diamond Problem です。SolidityはPythonに似ていて、"C3 Linearization"を使っており、ベースクラスのdirected acyclic graph (DAG)で特定の順番にさせています。これはmonotonicityの理想的な性質を実現していますが、いくつかの継承図を許可していません。
特に、is
で与えられたベースクラスの順番が重要です。直のベースコントラクトを"一番ベースになるもの"から"最後に継承されるもの"の順で並べなければいけません。
これはPythonとは逆の順序であることに気をつけて下さい。
これを説明する別のシンプルな方法は、異なるコントラクトで何度か定義されたファンクションが呼ばれる時、ベースコントラクトは縦型探索で右から左に調べて(Pythonだと左から右)、最初にマッチしたところで止まります。もしすでにベースコントラクトが検索済みだった場合、それはスキップされます。
下記のコードでは、Solidityは"Linearization of inheritance graph impossible"というエラーを出します。
pragma solidity >=0.4.0 <0.6.0;
contract X {}
contract A is X {}
// This will not compile
contract C is A, X {}
この理由は、A
をオーバーライドするのに C
は X
をリクエストしましたが、A
自体は X
をオーバーライドしようとしたので、矛盾が生まれたことです。
Inheriting Different Kinds of Members of the Same Name¶
継承の結果、同じ名前のファンクションとmodifierがあるコントラクトになった場合、エラーになります。 このエラーは同じイベント名とmodifier名、同じイベント名とファンクション名でも起きます。 例外として、状態変数のgetterはpublicファンクションをオーバーライドできます。
Abstract Contracts¶
アブストラクトとなるコントラクトは少なくとも1つのファンクションは下記の様に実行部がないものを持っています(ファンクションのヘッダーが ;
で終わっていることを確認して下さい):
pragma solidity >=0.4.0 <0.6.0;
contract Feline {
function utterance() public returns (bytes32);
}
その様なコントラクトはコンパイルできません(実行できるファンクションが一緒にあったとしても)が、ベースコントラクトとして使用することができます:
pragma solidity >=0.4.0 <0.6.0;
contract Feline {
function utterance() public returns (bytes32);
}
contract Cat is Feline {
function utterance() public returns (bytes32) { return "miaow"; }
}
もしあるコントラクトがアブストラクトを継承し、全ての非実行のファンクションをオーバーライドした上で、実行しなかった場合、そのコントラクト自体がアブストラクトになります。
シンタックスはとても似ていますが、実行しないファンクションは Function Type とは違うということに注意してください。
実行しないファンクションの例(ファンクションの宣言)です:
function foo(address) external returns (address);
ファンクション型の例です(変数の型が function
である変数の宣言):
function(address) external returns (address) foo;
アブストラクトコントラクトは、より良い拡張性を持つ実装や、自己文書化、Template method の様なファシリテーションパターン、コード複製の削除を、コントラクトの定義から切り離します。 アブストラクトコントラクトはインターフェースに置いてメソッド定義が便利なのと同じ様に便利です。これがアブストラクトコントラクトの作成者が「自分の子は全てこのメソッドを実行する」と言うための方法です。
Interfaces¶
インターフェースはアブストラクトコントラクトに似ていますが、どんなファンクションも実行することはできません。他の制限がこちらです。
- 他のコントラクやインターフェースを継承できません。
- 全ての宣言されたファンクションはexternalでなければいけません。
- コンストラクタは使えません。
- 状態変数は宣言できません。
将来的にはこの制限のいくつかは撤廃されるかもしれません。
インターフェースは基本的にコントラクトABIが表せるものに制限され、ABIとインターフェース間の変換は、どんな情報の喪失もなく行われるはずです。
インターフェースはinterfaceというキーワードで表されます。
pragma solidity ^0.5.0;
interface Token {
enum TokenType { Fungible, NonFungible }
struct Coin { string obverse; string reverse; }
function transfer(address recipient, uint amount) external;
}
コントラクトは他のコントラクトを継承するようにインターフェースを継承できます。
インターフェース内で定義された型と他のコントラクトの様な構造は他のコントラクトからアクセスできます: Token.TokenType
もしくは Token.Coin
Libraries¶
ライブラリはコントラクトに似ていますが、ある特定のアドレスに一度だけデプロイされ、DELEGATECALL
(Homesteadまでは CALLCODE
)を使って、そのコードを再利用するのが目的です。つまり、ライブラリのファンクションが呼び出されたら、呼び出したコントラクトのコンテキストで実行されます。this
は呼び出したコントラクトを指しますし、特に呼び出し元のコントラクトのストレージもアクセス可能です。ライブラリは独立したソースコードなので、状態変数が明示的に渡された場合、呼び出したコントラクトの状態変数にアクセスできます(そうでないと名前をつけられません)。
ライブラリのファンクションはステートを変更しない場合( view
もしくは pure
の場合)、直接呼び出すことしかできません(つまり DELEGATECALL
を使わない)。なぜなら、ライブラリはステートレスと想定されているからです。つまり、ライブラリを破棄することはできません。
注釈
バージョン0.4.20までは、Solidityのタイプシステムを迂回することで、ライブラリの破棄が可能でした。
そのバージョンから、ライブラリがステートを変更するファンクションを直接呼び出す mechanism を導入しました(つまり DELEGATECALL
を使わない)。
ライブラリはコントラクトが使う暗示的なベースコントラクトと見做すことができます。ライブラリは継承の階層の中で明示的に可視ではないですが、ライブラリのファンクションを呼ぶのは、明示的なベースファンクションを呼ぶ様なものです( L
がライブラリの名前の時に L.f()
)。さらに、ライブラリの internal
ファンクションはまるでライブラリがベースコントラクトであるかの様に、全てのコントラクトからアクセスできます。もちろんinternalのファンクションを呼び出すにはinternalの呼び出しのルールに従う必要があります。それは全てのinternalタイプは受け渡されて、メモリに保存 された型は参照として渡され、コピーされないということです。
EVMでこれを実現するために、internalのライブラリファンクションのコードとその中から呼ばれるファンクションはコンパイル時に呼び出し元のコントラクトに入り、DELEGATECALL
の代わりに通常の JUMP
コールが使用されます。
次の例ではどの様にライブラリを使うかを説明します(マニュアルメソッドに関してはより詳細な例 using for を参照ください)。
pragma solidity >=0.4.22 <0.6.0;
library Set {
// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data { mapping(uint => bool) flags; }
// Note that the first parameter is of type "storage
// reference" and thus only its storage address and not
// its contents is passed as part of the call. This is a
// special feature of library functions. It is idiomatic
// to call the first parameter `self`, if the function can
// be seen as a method of that object.
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
}
contract C {
Set.Data knownValues;
function register(uint value) public {
// The library functions can be called without a
// specific instance of the library, since the
// "instance" will be the current contract.
require(Set.insert(knownValues, value));
}
// In this contract, we can also directly access knownValues.flags, if we want.
}
もちろん、ライブラリを使うのにこの方法に従う必要はありません。構造体型を定義しなくても使えます。ファンクションはストレージの参照型を使わなくても動きますし、どこでも複数のストレージの参照型を持つことができます。
Set.contains
、Set.insert
、Set.remove
の呼び出しは外部のコントラクト、ライブラリの呼び出し(DELEGATECALL
)としてコンパイルされます。もしライブラリを使うなら、1つの実際のexternalのファンクションコールが実行されることを覚えておいてください。msg.sender
、msg.value
、this
はその値をこのコール中に保持します( CALLCODE
、msg.sender
、msg.value
の使用方法が変わったため、Homestead以前で有効です)。
以下の例では、externalファンクションコールのオーバーヘッドなしでカスタム型を実行するために、メモリに保存される型 とライブラリのinternalファンクションをどの様に使用するか示しています。
pragma solidity >=0.4.16 <0.6.0;
library BigInt {
struct bigint {
uint[] limbs;
}
function fromUint(uint x) internal pure returns (bigint memory r) {
r.limbs = new uint[](1);
r.limbs[0] = x;
}
function add(bigint memory _a, bigint memory _b) internal pure returns (bigint memory r) {
r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length));
uint carry = 0;
for (uint i = 0; i < r.limbs.length; ++i) {
uint a = limb(_a, i);
uint b = limb(_b, i);
r.limbs[i] = a + b + carry;
if (a + b < a || (a + b == uint(-1) && carry > 0))
carry = 1;
else
carry = 0;
}
if (carry > 0) {
// too bad, we have to add a limb
uint[] memory newLimbs = new uint[](r.limbs.length + 1);
uint i;
for (i = 0; i < r.limbs.length; ++i)
newLimbs[i] = r.limbs[i];
newLimbs[i] = carry;
r.limbs = newLimbs;
}
}
function limb(bigint memory _a, uint _limb) internal pure returns (uint) {
return _limb < _a.limbs.length ? _a.limbs[_limb] : 0;
}
function max(uint a, uint b) private pure returns (uint) {
return a > b ? a : b;
}
}
contract C {
using BigInt for BigInt.bigint;
function f() public pure {
BigInt.bigint memory x = BigInt.fromUint(7);
BigInt.bigint memory y = BigInt.fromUint(uint(-1));
BigInt.bigint memory z = x.add(y);
assert(z.limb(1) > 0);
}
}
ライブラリがどこにデプロイされるかコンパイラは分からないので、アドレスはlinkerで最後のバイトコードに入れられる必要があります(リンキングのためのコマンドラインコンパイラの使い方は Using the Commandline Compiler を参照下さい)。もしアドレスが引数としてコンパイラに渡されない場合、コンパイラの16進数コードは __Set______
(
Set
はライブラリの名前) のプレースホルダを含みます。ライブラリコントラクトのアドレスの16進数エンコーディングによって、アドレスは手動で40個の記号で置き換えられ、埋められます。
注釈
生成されたバイトコード上でのライブラリの手動のリンキングは推奨されません。なぜなら、36文字に制限されているからです。コントラクトが solc
の --libraries
オプションか、コンパイラにstandard-JSONインターフェースを使っているなら、libraries
キーを使ってコンパイルされている時に、コンパイラにライブラリをリンクすることをお願いした方が良いです。
コントラクトと比べた時のライブラリの制限は:
- 状態変数がありません
- 継承する、継承されることはありません
- Etherを受け取ることができません
(これらはいつか撤廃されるかもしれません)
Call Protection For Libraries¶
イントロダクションで言及した通り、ライブラリのコードが DELEGATECALL
もしくは CALLCODE
の代わりに CALL
で実行された時、view
か pure
でなければrevertします。
EVMではコントラクトが CALL
で呼ばれたかどうか直接検知する方法はありませんが、そのコードが現在"どこ"で動作しているか調べる ADDRESS
opcodeは使えます。生成されたコードは呼び出しの種類を決めるために、このアドレスとコード生成時に使われたアドレスを比較します。
特に、ライブラリのラインタイムコードはpushから始まります。それはコンパイル時に20バイトの0で構成されています。デプロイコードがまわっている時、この定数は現在のアドレスでメモリ内で置き換えられ、修正されたコードがコントラクトに保存されます。ランタイム時に、デプロイ時のアドレスが最初の定数となって、スタック上にプッシュされ、ディスパッチャーコードが現在のアドレスとその定数をviewでもpureでもないファンクションのために比較します。
Using For¶
using A for B;
という指示はライブラリのファンクション( A
というライブラリから)をどんな型(B
)にも加えるのに使うことができます。
このファンクションは最初のパラメータとして(Pythonの self
の様に)、そのファンクションを呼び出したオブジェクトを受け取ります。
using A for *;
というのは、ライブラリ A
のファンクションが どんな 型にも追加されるということです。
いずれの場合でも、最初のパラメータの型がオブジェクトの型と合わなくても、ライブラリ中の 全ての ファンクションが追加されます。ファンクションが呼ばれた時に型はチェックされ、ファンクションのoverload resolutionが実行されます。
using A for B;
は現在の全てのファンクション内も含むコントラクト内でのみ有効です。そのコントラクトが使用されている外部のコントラクトでは使えません。この指示はファンクション内でなく、コントラクト内でのみ実行可能です。
ライブラリを含めることにより、ライブラリのファンクションを含むデータタイプが追加のコードなしで使える様になります。
Libraries の例を書き換えてみます:
pragma solidity >=0.4.16 <0.6.0;
// This is the same code as before, just without comments
library Set {
struct Data { mapping(uint => bool) flags; }
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
}
contract C {
using Set for Set.Data; // this is the crucial change
Set.Data knownValues;
function register(uint value) public {
// Here, all variables of type Set.Data have
// corresponding member functions.
// The following function call is identical to
// `Set.insert(knownValues, value)`
require(knownValues.insert(value));
}
}
この様に基本型を拡張することも可能です:
pragma solidity >=0.4.16 <0.6.0;
library Search {
function indexOf(uint[] storage self, uint value)
public
view
returns (uint)
{
for (uint i = 0; i < self.length; i++)
if (self[i] == value) return i;
return uint(-1);
}
}
contract C {
using Search for uint[];
uint[] data;
function append(uint value) public {
data.push(value);
}
function replace(uint _old, uint _new) public {
// This performs the library function call
uint index = data.indexOf(_old);
if (index == uint(-1))
data.push(_new);
else
data[index] = _new;
}
}
全てのライブラリのコールは実際のEVMのファンクションコールであるということを覚えておいてください。つまり、もしメモリもしくは値型を渡す時には、self
変数であってもコピーが実行されます。ストレージ参照の変数が使われた時だけ、コピーは実行されません。