はじめに
基本は大事ということで、Solidityを使う機会が増えてきたので、型(Types)のドキュメントをGoogle翻訳やDeepL翻訳を使用して翻訳してみます。
※:長いので記事を4分割します。
その1、その2、その3、その4
※:誤字脱字、翻訳ミスなどありましたら、お知らせくださいm(__)m
原文
String literals are written with either double or single-quotes ("foo"
or 'bar'
), and they can also be split into multiple consecutive parts ("foo"
"bar"
is equivalent to "foobar"
) which can be helpful when dealing with long strings.
They do not imply trailing zeroes as in C; "foo"
represents three bytes, not four.
As with integer literals, their type can vary, but they are implicitly convertible to bytes1
, …, bytes32
, if they fit, to bytes
and to string
.
For example, with bytes32 samevar = "stringliteral"
the string literal is interpreted in its raw byte form when assigned to a bytes32
type.
String literals can only contain printable ASCII characters, which means the characters between and including 0x20 .. 0x7E.
Additionally, string literals also support the following escape characters:
-
\<newline>
(escapes an actual newline) -
\\
(backslash) -
\'
(single quote) -
\"
(double quote) -
\n
(newline) -
\r
(carriage return) -
\t
(tab) -
\xNN
(hex escape, see below) -
\uNNNN
(unicode escape, see below)
\xNN
takes a hex value and inserts the appropriate byte, while \uNNNN
takes a Unicode codepoint and inserts an UTF-8 sequence.
Note
Until version 0.8.0 there were three additional escape sequences: \b
, \f
and \v
.
They are commonly available in other languages but rarely needed in practice.
If you do need them, they can still be inserted via hexadecimal escapes, i.e. \x08
, \x0c
and \x0b
, respectively, just as any other ASCII character.
The string in the following example has a length of ten bytes.
It starts with a newline byte, followed by a double quote, a single quote a backslash character and then (without separator) the character sequence abcdef
.
"\n\"\'\\abc\
def"
Any Unicode line terminator which is not a newline (i.e. LF, VF, FF, CR, NEL, LS, PS) is considered to terminate the string literal.
Newline only terminates the string literal if it is not preceded by a \
.
文字列リテラルはダブルクォートまたはシングルクォートで記述します ("foo"
または 'bar'
) 。また、連続した複数の部分に分割することもできます ("foo"
"bar"
は "foobar"
と同じです)。これは長い文字列を扱う場合に便利です。
また、C言語のように末尾に0をつけることはできません。"foo"
は4バイトではなく、3バイトを表します。
整数リテラルと同様に、その型はさまざまですが、 bytes1
, ..., bytes32
に暗黙のうちに変換され、それが適合すれば bytes
や string
にも変換されます。
例えば、bytes32 samevar = "stringliteral"
とすると、文字列リテラルは bytes32
型に割り当てられたときに生のバイト形式で解釈されます。
文字列リテラルには、印刷可能な ASCII 文字のみを含めることができます。これは、0x20 .. 0x7E の間および 0x7E を含む文字を意味します。
さらに、文字列リテラルは次のエスケープ文字もサポートしています。
-
\<newline>
(実際の改行をエスケープします) -
\\
(バックスラッシュ) -
\'
(一重引用符) -
\"
(二重引用符) -
\n
(改行) -
\r
(キャリッジ リターン) -
\t
(タブ) -
\xNN
(16 進エスケープ、以下を参照) -
\uNNNN
(Unicodeエスケープ、下記参照)
\xNN
は 16 進値を取り、適切なバイトを挿入しますが、\uNNNN
は Unicode コードポイントを取り、UTF-8 シーケンスを挿入します。
Note
バージョン0.8.0までは、さらにb
, f
, v
という3つのエスケープシーケンスがありました。
これらは他の言語では一般的に利用可能ですが、実際にはほとんど必要ありません。
もし必要であれば、他のASCII文字と同じように、それぞれ x08
, x0c
, x0b
という16進数のエスケープを使って挿入することができます。
次の例の文字列は10バイトの長さである。
改行バイトで始まり、二重引用符、一重引用符、バックスラッシュ文字、そして(セパレータなしで)文字シーケンス abcdef
が続きます。
"\n\"\'\\abc\
def"
改行ではない Unicode 行終端文字 (つまり、LF、VF、FF、CR、NEL、LS、PS) は、文字列リテラルを終了すると見なされます。
改行は、前に \
がない場合にのみ文字列リテラルを終了します。
Unicodeリテラル(Unicode Literals)
原文
While regular string literals can only contain ASCII, Unicode literals – prefixed with the keyword unicode
– can contain any valid UTF-8 sequence. They also support the very same escape sequences as regular string literals.
string memory a = unicode"Hello 😃";
通常の文字列リテラルには ASCII のみを含めることができますが、Unicode リテラルにはキーワード unicode
をプレフィックスとして付けて、有効な UTF-8 シーケンスを含めることができます。
また、通常の文字列リテラルとまったく同じエスケープ シーケンスもサポートしています。
string memory a = unicode"Hello 😃";
16 進リテラル(Hexadecimal Literals)
原文
Hexadecimal literals are prefixed with the keyword hex
and are enclosed in double or single-quotes (hex"001122FF"
, hex'0011_22_FF'
).
Their content must be hexadecimal digits which can optionally use a single underscore as separator between byte boundaries.
The value of the literal will be the binary representation of the hexadecimal sequence.
Multiple hexadecimal literals separated by whitespace are concatenated into a single literal: hex"00112233" hex"44556677"
is equivalent to hex"0011223344556677"
Hexadecimal literals behave like string literals and have the same convertibility restrictions.
16進数リテラルはキーワード hex
を先頭に持ち、二重引用符または単一引用符で囲まれます (hex "001122FF"
, hex'0011_22_FF'
).
その内容は16進数でなければならず、オプションとしてバイトの境界を区切るアンダースコアを1つ使用することができます。
リテラルの値は,16進数列を2進数で表現したものになります。
空白で区切られた複数の16進数リテラルは,1つのリテラルに連結されます: hex "00112233" hex "44556677"
は hex "0011223344556677"
と同じです.
16進数のリテラルは文字列リテラルと同じように動作し、同じように変換可能な制約があります。
列挙型(Enums)
原文
Enums are one way to create a user-defined type in Solidity.
They are explicitly convertible to and from all integer types but implicit conversion is not allowed.
The explicit conversion from integer checks at runtime that the value lies inside the range of the enum and causes a Panic error otherwise.
Enums require at least one member, and its default value when declared is the first member.
Enums cannot have more than 256 members.
The data representation is the same as for enums in C: The options are represented by subsequent unsigned integer values starting from 0.
Using type(NameOfEnum).min
and type(NameOfEnum).max
you can get the smallest and respectively largest value of the given enum.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
}
// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity.
function getChoice() public view returns (ActionChoices) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
function getLargestValue() public pure returns (ActionChoices) {
return type(ActionChoices).max;
}
function getSmallestValue() public pure returns (ActionChoices) {
return type(ActionChoices).min;
}
}
Note
Enums can also be declared on the file level, outside of contract or library definitions.
列挙型は、Solidity でユーザー定義型を作成する 1 つの方法です。
これらはすべての整数型との間で明示的に変換できますが、暗黙的な変換は許可されていません。
整数からの明示的な変換は、値が列挙型の範囲内にあることを実行時にチェックし、それ以外の場合はパニック エラーを引き起こします。
列挙型には少なくとも 1 つのメンバが必要であり、宣言時の既定値は最初のメンバです。
Enum は 256 を超えるメンバを持つことはできません。
データ表現は C の列挙型と同じです。オプションは、0 から始まる後続の符号なし整数値で表されます。
type(NameOf Enum).min
と type(NameOf Enum).max
を使用すると、指定された列挙型の最小値とそれぞれ最大値を取得できます。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
}
// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity.
function getChoice() public view returns (ActionChoices) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
function getLargestValue() public pure returns (ActionChoices) {
return type(ActionChoices).max;
}
function getSmallestValue() public pure returns (ActionChoices) {
return type(ActionChoices).min;
}
}
Note
列挙型は、コントラクトまたはライブラリ定義の外で、ファイル レベルで宣言することもできます。
ユーザー定義値型(User Defined Value Types)
原文
A user defined value type allows creating a zero cost abstraction over an elementary value type.
This is similar to an alias, but with stricter type requirements.
A user defined value type is defined using type C is V
, where C
is the name of the newly introduced type and V
has to be a built-in value type (the “underlying type”).
The function C.wrap
is used to convert from the underlying type to the custom type. Similarly, the function C.unwrap
is used to convert from the custom type to the underlying type.
The type C
does not have any operators or attached member functions.
In particular, even the operator ==
is not defined.
Explicit and implicit conversions to and from other types are disallowed.
The data-representation of values of such types are inherited from the underlying type and the underlying type is also used in the ABI.
The following example illustrates a custom type UFixed256x18
representing a decimal fixed point type with 18 decimals and a minimal library to do arithmetic operations on the type.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
// Represent a 18 decimal, 256 bit wide fixed point type using a user defined value type.
type UFixed256x18 is uint256;
/// A minimal library to do fixed point operations on UFixed256x18.
library FixedMath {
uint constant multiplier = 10**18;
/// Adds two UFixed256x18 numbers. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
}
/// Multiplies UFixed256x18 and uint256. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
}
/// Take the floor of a UFixed256x18 number.
/// @return the largest integer that does not exceed `a`.
function floor(UFixed256x18 a) internal pure returns (uint256) {
return UFixed256x18.unwrap(a) / multiplier;
}
/// Turns a uint256 into a UFixed256x18 of the same value.
/// Reverts if the integer is too large.
function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(a * multiplier);
}
}
Notice how UFixed256x18.wrap
and FixedMath.toUFixed256x18
have the same signature but perform two very different operations: The UFixed256x18.wrap
function returns a UFixed256x18
that has the same data representation as the input, whereas toUFixed256x18
returns a UFixed256x18
that has the same numerical value.
ユーザー定義値型を使用すると、基本的な値の型に対してゼロ コストの抽象化を作成できます。
これはエイリアスに似ていますが、より厳密な型要件があります。
ユーザー定義の値型は、type C is V
を使用して定義されます。ここで、 C
は新しく導入された型の名前であり、V
は組み込み値型 (「基になる型」“underlying type”) である必要があります。
関数 C.wrap
は、基になる型からカスタム型に変換するために使用されます。 同様に、関数 C.unwrap
を使用してカスタム型から基になる型に変換します。
型 C
には、演算子または付加されたメンバ関数はありません。
特に、演算子 ==
でさえ定義されていません。
他の型との間の明示的および暗黙的な変換は許可されていません。
このような型の値のデータ表現は基になる型から継承され、基になる型は ABI でも使用されます。
次の例は、18 桁の 10 進固定小数点型を表すカスタム型 UFixed256x18
と、その型で算術演算を行うための最小限のライブラリを示しています。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
// Represent a 18 decimal, 256 bit wide fixed point type using a user defined value type.
type UFixed256x18 is uint256;
/// A minimal library to do fixed point operations on UFixed256x18.
library FixedMath {
uint constant multiplier = 10**18;
/// Adds two UFixed256x18 numbers. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
}
/// Multiplies UFixed256x18 and uint256. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
}
/// Take the floor of a UFixed256x18 number.
/// @return the largest integer that does not exceed `a`.
function floor(UFixed256x18 a) internal pure returns (uint256) {
return UFixed256x18.unwrap(a) / multiplier;
}
/// Turns a uint256 into a UFixed256x18 of the same value.
/// Reverts if the integer is too large.
function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(a * multiplier);
}
}
UFixed256x18.wrap
と FixedMath.toUFixed256x18
は同じ署名を持っていますが、2 つの非常に異なる操作を実行することに注意してください: UFixed256x18.wrap
関数は、入力と同じデータ表現を持つ UFixed256x18
を返します。
一方、toUFixed256x18
は同じ数値を持つ UFixed256x18
を返します。
関数型(Function Types)
原文
Function types are the types of functions.
Variables of function type can be assigned from functions and function parameters of function type can be used to pass functions to and return functions from function calls.
Function types come in two flavours - internal and external functions:
Internal functions can only be called inside the current contract (more specifically, inside the current code unit, which also includes internal library functions and inherited functions) because they cannot be executed outside of the context of the current contract.
Calling an internal function is realized by jumping to its entry label, just like when calling a function of the current contract internally.
External functions consist of an address and a function signature and they can be passed via and returned from external function calls.
Function types are notated as follows:
function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]
In contrast to the parameter types, the return types cannot be empty - if the function type should not return anything, the whole returns (<return types>)
part has to be omitted.
By default, function types are internal, so the internal
keyword can be omitted. Note that this only applies to function types.
Visibility has to be specified explicitly for functions defined in contracts, they do not have a default.
Conversions:
A function type A
is implicitly convertible to a function type B
if and only if their parameter types are identical, their return types are identical, their internal/external property is identical and the state mutability of A
is more restrictive than the state mutability of B
.
In particular:
-
pure
functions can be converted toview
andnon-payable
functions -
view
functions can be converted tonon-payable
functions -
payable
functions can be converted tonon-payable
functions
No other conversions between function types are possible.
The rule about payable
and non-payable
might be a little confusing, but in essence, if a function is payable
, this means that it also accepts a payment of zero Ether, so it also is non-payable
.
On the other hand, a non-payable
function will reject Ether sent to it, so non-payable
functions cannot be converted to payable
functions.
To clarify, rejecting ether is more restrictive than not rejecting ether.
This means you can override a payable function with a non-payable but not the other way around.
Additionally, When you define a non-payable
function pointer, the compiler does not enforce that the pointed function will actually reject ether.
Instead, it enforces that the function pointer is never used to send ether. Which makes it possible to assign a payable
function pointer to a non-payable
function pointer ensuring both types behave the same way, i.e, both cannot be used to send ether.
If a function type variable is not initialised, calling it results in a Panic error. The same happens if you call a function after using delete
on it.
If external function types are used outside of the context of Solidity, they are treated as the function
type, which encodes the address followed by the function identifier together in a single bytes24
type.
Note that public functions of the current contract can be used both as an internal and as an external function.
To use f
as an internal function, just use f
, if you want to use its external form, use this.f
.
A function of an internal type can be assigned to a variable of an internal function type regardless of where it is defined.
This includes private, internal and public functions of both contracts and libraries as well as free functions.
External function types, on the other hand, are only compatible with public and external contract functions.
Note
External functions with calldata
parameters are incompatible with external function types with calldata
parameters.
They are compatible with the corresponding types with memory
parameters instead.
For example, there is no function that can be pointed at by a value of type function (string calldata) external
while function (string memory) external
can point at both function f(string memory) external {}
and function g(string calldata) external {}
.
This is because for both locations the arguments are passed to the function in the same way.
The caller cannot pass its calldata directly to an external function and always ABI-encodes the arguments into memory.
Marking the parameters as calldata
only affects the implementation of the external function and is meaningless in a function pointer on the caller’s side.
Libraries are excluded because they require a delegatecall
and use a different ABI convention for their selectors.
Functions declared in interfaces do not have definitions so pointing at them does not make sense either.
Members:
External (or public) functions have the following members:
-
.address
returns the address of the contract of the function. -
.selector
returns the ABI function selector
Note
External (or public) functions used to have the additional members .gas(uint)
and .value(uint)
.
These were deprecated in Solidity 0.6.2 and removed in Solidity 0.7.0. Instead use {gas: ...}
and {value: ...}
to specify the amount of gas or the amount of wei sent to a function, respectively.
See External Function Calls for more information.
関数型は、関数の型である。
関数型の変数は関数から代入することができ、関数型のパラメータは関数呼び出しに関数を渡したり、関数から関数を返したりするために使用することができます。
関数型には、内部関数と外部関数の2種類があります。
内部関数は、現在のコントラクトのコンテキスト外では実行できないため、現在のコントラクト内 (より具体的には、内部ライブラリ関数と継承された関数も含む現在のコード ユニット内) でのみ呼び出すことができます。
内部関数の呼び出しは、現在のコントラクトの関数を内部的に呼び出す場合と同様に、そのエントリ ラベルにジャンプすることによって実現されます。
外部関数は、アドレスと関数シグネチャから構成され、外部関数呼び出しから渡したり返したりすることができます。
関数型は次のように表記されます。
function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]
パラメータ型とは対照的に、戻り値の型を空にすることはできません。
関数型が何も返さない場合は、returns (<return types>)
部分全体を省略する必要があります。
デフォルトでは、関数型は内部的なものなので、 internal
キーワードは省略することができます。これは関数タイプにのみ適用されることに注意してください。
コントラクトで定義された関数については、可視性を明示的に指定する必要があり、デフォルトではありません。
コンバージョン(変換 Conversions):
関数型 A
は、パラメータ型が同じで、戻り値が同じで、内部/外部プロパティが同じで、かつ A
の状態遷移が B
の状態遷移よりも厳しい場合に限り、関数型 B
に暗黙的に変換可能です。
具体的には
-
pure
の関数はview
とnon-payable
の関数に変換することができます。 -
view
型の関数はnon-payable
型の関数に変換することができます。 -
payable
型の関数はnon-payable
型の関数に変換することができます。
それ以外の関数型間の変換はできません。
payable
と non-payable
のルールは少しわかりにくいかもしれませんが、要するに、関数が payable
である場合、それは 0 Ether の支払いも受け付けるということなので、 non-payable
でもあるということです。
一方、non-payable
関数は送られてきたetherを拒否するので、non-payable
関数をpayable
関数に変換することはできません。
明確に言うと、etherを拒否することは、etherを拒否しないことよりも制限されます。
つまり、payableな関数をnon-payableでオーバーライドすることはできますが、その逆はできません。
さらに、non-payable
関数ポインタを定義した場合、コンパイラはその関数が実際にetherを拒否することを強制しません。
その代わり、その関数ポインタは決してetherを送るために使われないことを強制します。そのため、payable
関数ポインタをnon-payable
関数ポインタに代入すると、両方のタイプが同じように動作することを保証することができます。
関数型変数が初期化されていない場合、それを呼び出すとパニックエラーになります。関数に対して delete
を使用した後に関数を呼び出すと、同じことが起こります。
外部関数型が Solidity のコンテキスト外で使用される場合、それらは function
型として扱われ、アドレスに続いて関数識別子が一つの bytes24
型に一緒にエンコードされます。
現在のコントラクトのパブリック関数は、内部関数としても外部関数としても使用できることに注意してください。
内部関数として f
を使用する場合は、単に f
を使用し、外部関数として使用する場合は、 this.f
を使用します。
内部関数型の関数は、それが定義されている場所に関係なく、内部関数型の変数に代入することができます。
これには、コントラクトとライブラリの両方のプライベート関数、内部関数、パブリック関数と、フリー関数が含まれます。
一方、外部関数型は、パブリック関数と外部コントラクト関数とのみ互換性があります。
Note
calldata
パラメータを持つ外部関数は、 calldata
パラメータを持つ外部関数型と互換性がありません。
代わりに memory
パラメータを持つ対応する型と互換性があります。
例えば、 function (string calldata) external
型の値が指すことのできる関数はありませんが、 function (string memory) external
型は function f(string memory) external {}
と function g(string calldata) external {}
の両方を指せます。
これは、両方の場所において、引数が同じように関数に渡されるからです。
呼び出し側は calldata を直接外部関数に渡すことができず、常に引数をメモリに ABI エンコードします。
パラメータを calldata
としてマークすることは、外部関数の実装にのみ影響し、呼び出し側の関数ポインタの中では意味がありません。
ライブラリは delegatecall
を必要とし、セレクタに異なる ABI 規約を使用するので除外されます。
インターフェースで宣言された関数は定義を持たないので、それを指定することも意味を持ちません。
メンバ:
External(またはpublic)関数は、以下のようなメンバで構成されています。
-
.address
は関数のコントラクトのアドレスを返す。 -
.selector
は ABI の関数セレクタを返す。
Note
External (またはpublic) 関数は、追加のメンバ .gas(uint)
および .value(uint)
を持っていました。
これらは Solidity 0.6.2 で廃止され、Solidity 0.7.0 で削除されました。 代わりに {gas: ...}
と {value: ...}
を使用して、関数に送られるガスの量または wei の量をそれぞれ指定します。
詳細については、外部関数呼び出しを参照してください。
原文
Example that shows how to use the members:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.4 <0.9.0;
contract Example {
function f() public payable returns (bytes4) {
assert(this.f.address == address(this));
return this.f.selector;
}
function g() public {
this.f{gas: 10, value: 800}();
}
}
Example that shows how to use internal function types:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal pure returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
}
Another example that uses external function types:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract Oracle {
struct Request {
bytes data;
function(uint) external callback;
}
Request[] private requests;
event NewRequest(uint);
function query(bytes memory data, function(uint) external callback) public {
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, uint response) public {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant private ORACLE_CONST = Oracle(address(0x00000000219ab540356cBB839Cbe05303d7705Fa)); // known contract
uint private exchangeRate;
function buySomething() public {
ORACLE_CONST.query("USD", this.oracleResponse);
}
function oracleResponse(uint response) public {
require(
msg.sender == address(ORACLE_CONST),
"Only oracle can call this."
);
exchangeRate = response;
}
}
Note
Lambda or inline functions are planned but not yet supported.
メンバの使い方を示す例です。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.4 <0.9.0;
contract Example {
function f() public payable returns (bytes4) {
assert(this.f.address == address(this));
return this.f.selector;
}
function g() public {
this.f{gas: 10, value: 800}();
}
}
内部関数型の使用方法を示す例です。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal pure returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
}
また、外部関数型を使用した例です。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract Oracle {
struct Request {
bytes data;
function(uint) external callback;
}
Request[] private requests;
event NewRequest(uint);
function query(bytes memory data, function(uint) external callback) public {
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, uint response) public {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant private ORACLE_CONST = Oracle(address(0x00000000219ab540356cBB839Cbe05303d7705Fa)); // known contract
uint private exchangeRate;
function buySomething() public {
ORACLE_CONST.query("USD", this.oracleResponse);
}
function oracleResponse(uint response) public {
require(
msg.sender == address(ORACLE_CONST),
"Only oracle can call this."
);
exchangeRate = response;
}
}
Note
Lambda関数やinline関数は予定されていますが、まだサポートされていません。
参照型(Reference Types)
原文
Values of reference type can be modified through multiple different names.
Contrast this with value types where you get an independent copy whenever a variable of value type is used.
Because of that, reference types have to be handled more carefully than value types.
Currently, reference types comprise structs, arrays and mappings. If you use a reference type, you always have to explicitly provide the data area where the type is stored: memory (whose lifetime is limited to an external function call), storage (the location where the state variables are stored, where the lifetime is limited to the lifetime of a contract) or calldata (special data location that contains the function arguments).
An assignment or type conversion that changes the data location will always incur an automatic copy operation, while assignments inside the same data location only copy in some cases for storage types.
参照型の値は、複数の異なる名前を通して変更することができます。
これに対して値型は、値型の変数が使用されるたびに独立したコピーを取得します。
そのため、参照型は値型よりも慎重に扱わなければなりません。
現在、参照型は構造体、配列、マッピングで構成されています。参照型を使用する場合、その型が格納されるデータ領域を常に明示的に提供しなければなりません。メモリ(その寿命は外部関数呼び出しに限られます)、ストレージ(ステート変数が格納される場所、その寿命は契約の寿命に限られます)、calldata(関数引数を格納する特別なデータ領域)です。
データロケーションを変更する割り当てやタイプ変換は、常に自動コピー操作が発生しますが、同じデータロケーション内の割り当ては、ストレージタイプの場合のみ、いくつかのケースでコピーされます。
データロケーション(Data location)
原文
Every reference type has an additional annotation, the “data location”, about where it is stored.
There are three data locations: memory
, storage
and calldata
.
Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
Note
If you can, try to use calldata
as data location because it will avoid copies and also makes sure that the data cannot be modified.
Arrays and structs with calldata
data location can also be returned from functions, but it is not possible to allocate such types.
Note
Prior to version 0.6.9 data location for reference-type arguments was limited to calldata
in external functions, memory
in public functions and either memory
or storage
in internal and private ones.
Now memory
and calldata
are allowed in all functions regardless of their visibility.
Note
Prior to version 0.5.0 the data location could be omitted, and would default to different locations depending on the kind of variable, function type, etc., but all complex types must now give an explicit data location.
すべての参照型には、それがどこに格納されるかについての「データロケーション data location」という注釈が追加されています。
データロケーションは3つあり memory
、storage
、そしてcalldata
です。
calldataは関数の引数が格納される、変更不可、永続的でない領域で、ほとんどメモリのように動作します。
Note
可能であれば、データの場所として calldata
を使用するようにしてください。コピーを避けることができ、また、データを変更することができないようにすることができます。
データロケーションが calldata
の配列や構造体も関数から返すことができるが、そのような型を割り当てることはできません。
Note
バージョン 0.6.9 以前では、参照型引数のデータロケーションは、外部関数では calldata
、パブリック関数では memory
、内部およびプライベート関数では memory
または storage
に制限されていました。
現在は、 memory
と calldata
は可視性に関係なく、すべての関数で許可されています。
Note
バージョン0.5.0以前は、データロケーションを省略することができ、変数の種類や関数型などに応じて異なるロケーションをデフォルトとしていましたが、現在はすべての複合型に明示的にデータロケーションを指定する必要があります。
データロケーションと代入の挙動(Data location and assignment behaviour)
原文
Data locations are not only relevant for persistency of data, but also for the semantics of assignments:
- Assignments between
storage
andmemory
(or fromcalldata
) always create an independent copy. - Assignments from
memory
tomemory
only create references. This means that changes to one memory variable are also visible in all other memory variables that refer to the same data. - Assignments from
storage
to a local storage variable also only assign a reference. - All other assignments to
storage
always copy. Examples for this case are assignments to state variables or to members of local variables of storage struct type, even if the local variable itself is just a reference.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
// The data location of x is storage.
// This is the only place where the
// data location can be omitted.
uint[] x;
// The data location of memoryArray is memory.
function f(uint[] memory memoryArray) public {
x = memoryArray; // works, copies the whole array to storage
uint[] storage y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.pop(); // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated:
// y = memoryArray;
// Similarly, "delete y" is not valid, as assignments to local variables
// referencing storage objects can only be made from existing storage objects.
// It would "reset" the pointer, but there is no sensible location it could point to.
// For more details see the documentation of the "delete" operator.
// delete y;
g(x); // calls g, handing over a reference to x
h(x); // calls h and creates an independent, temporary copy in memory
}
function g(uint[] storage) internal pure {}
function h(uint[] memory) public pure {}
}
データロケーションは、データの永続性だけでなく、代入のセマンティクスにも関連しています。
-
storage
とmemory
の間 (またはcalldata
から) の代入は、常に独立したコピーを作成します。 -
memory
からmemory
への代入は、参照のみを作成します。つまり、あるメモリ変数への変更は、同じデータを参照する他のすべてのメモリ変数にも反映されます。 -
storage
から ローカル ストレージ変数への代入も、参照のみを代入します。 - その他の
storage
への代入はすべて常にコピーされます。この場合の例としては、ローカル変数自体が単なる参照であっても、ステート変数やストレージ構造体型のローカル変数のメンバへの代入があります。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
// The data location of x is storage.
// This is the only place where the
// data location can be omitted.
uint[] x;
// The data location of memoryArray is memory.
function f(uint[] memory memoryArray) public {
x = memoryArray; // works, copies the whole array to storage
uint[] storage y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.pop(); // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated:
// y = memoryArray;
// Similarly, "delete y" is not valid, as assignments to local variables
// referencing storage objects can only be made from existing storage objects.
// It would "reset" the pointer, but there is no sensible location it could point to.
// For more details see the documentation of the "delete" operator.
// delete y;
g(x); // calls g, handing over a reference to x
h(x); // calls h and creates an independent, temporary copy in memory
}
function g(uint[] storage) internal pure {}
function h(uint[] memory) public pure {}
}
さいごに
この中で注目したのは、データーロケーションです。
Solidityではmemory
、storage
、calldata
があるので、
これらの使い方を間違えないようにしないといけません。
その3へ