はじめに
基本は大事ということで、Solidityを使う機会が増えてきたので、型(Types)のドキュメントをGoogle翻訳やDeepL翻訳を使用して翻訳してみます。
※:長いので記事を4分割します。
その1、その2、その3、その4
※:誤字脱字、翻訳ミスなどありましたら、お知らせくださいm(__)m
マッピング型(Mapping Types)
原文
Mapping types use the syntax mapping(KeyType KeyName? => ValueType ValueName?)
and variables of mapping type are declared using the syntax mapping(KeyType KeyName? => ValueType ValueName?) VariableName
.
The KeyType can be any built-in value type, bytes
, string
, or any contract or enum type.
Other user-defined or complex types, such as mappings, structs or array types are not allowed.
ValueType
can be any type, including mappings, arrays and structs.
KeyName
and ValueName
are optional (so mapping(KeyType => ValueType)
works as well) and can be any valid identifier that is not a type.
You can think of mappings as hash tables, which are virtually initialised such that every possible key exists and is mapped to a value whose byte-representation is all zeros, a type’s default value.
The similarity ends there, the key data is not stored in a mapping, only its keccak256
hash is used to look up the value.
Because of this, mappings do not have a length or a concept of a key or value being set, and therefore cannot be erased without extra information regarding the assigned keys (see Clearing Mappings).
Mappings can only have a data location of storage
and thus are allowed for state variables, as storage reference types in functions, or as parameters for library functions.
They cannot be used as parameters or return parameters of contract functions that are publicly visible.
These restrictions are also true for arrays and structs that contain mappings.
You can mark state variables of mapping type as public
and Solidity creates a getter for you.
The KeyType
becomes a parameter with name KeyName
(if specified) for the getter.
If ValueType
is a value type or a struct, the getter returns ValueType
with name ValueName
(if specified).
If ValueType
is an array or a mapping, the getter has one parameter for each KeyType
, recursively.
In the example below, the MappingExample
contract defines a public balances
mapping, with the key type an address
, and a value type a uint
, mapping an Ethereum address to an unsigned integer value.
As uint
is a value type, the getter returns a value that matches the type, which you can see in the MappingUser
contract that returns the value at the specified address.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(address(this));
}
}
The example below is a simplified version of an ERC20 token.
_allowances
is an example of a mapping type inside another mapping type.
In the example below, the optional KeyName
and ValueName
are provided for the mapping.
It does not affect any contract functionality or bytecode, it only sets the name
field for the inputs and outputs in the ABI for the mapping’s getter.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.18;
contract MappingExampleWithNames {
mapping(address user => uint balance) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
The example below uses _allowances
to record the amount someone else is allowed to withdraw from your account.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract MappingExample {
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
require(_allowances[sender][msg.sender] >= amount, "ERC20: Allowance not high enough.");
_allowances[sender][msg.sender] -= amount;
_transfer(sender, recipient, amount);
return true;
}
function approve(address spender, uint256 amount) public returns (bool) {
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
require(_balances[sender] >= amount, "ERC20: Not enough funds.");
_balances[sender] -= amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}
}
マッピング型は mapping(KeyType KeyName? => ValueType ValueName?)
というシンタックスを使用し、マッピングタイプの変数は mapping(KeyType KeyName? => ValueType ValueName?) VariableName
というシンタックスを使用して宣言されます。
KeyType
には、組み込みの値型、bytes
、string
、あるいはコントラクト型やenum型を指定することができます。
その他のユーザー定義型や複雑な型、例えばマッピングや構造体、配列型は使用できません。
ValueType
には、マッピング、配列、構造体を含む、任意の型を指定することができます。
KeyName
と ValueName
はオプションで (つまり mapping(KeyType => ValueType)
も同様に動作します)、型ではない任意の有効な識別子を使用することができます。
マッピングはハッシュテーブルと考えることができ、仮想的にすべての可能なキーが存在し、バイト表現がすべてゼロである値、つまり型のデフォルト値にマッピングされているように初期化されます。
キーデータはマッピングに保存されず、その keccak256
ハッシュだけが値を探すのに使われます。
このため、マッピングには長さや、キーや値が設定されているという概念がなく、割り当てられたキーに関する余分な情報がなければ消去することができません。(「マッピングの消去」を参照)
マッピングのデータロケーションは storage
のみです。したがって、ステート変数、関数内のストレージ参照型、ライブラリ関数のパラメータとして使用することができます。
また、一般に公開されているコントラクト関数のパラメータやリターンパラメータとして使用することはできません。
これらの制限は、マッピングを含む配列や構造体にも当てはまります。
KeyType
はゲッターのパラメータとなり、キー名 KeyName
(指定された場合) を持ちます。
ValueType
が値型または構造体の場合、ゲッターは ValueType
を名前 ValueName
(指定された場合) と共に返します。
ValueType
が配列やマッピングの場合、ゲッターは各 KeyType
に対して、再帰的に1つのパラメータを持つ。
以下の例では、MappingExample
コントラクトが、キータイプを address
、値タイプを uint
として、Ethereum のアドレスと符号なし整数の値を対応させた public balances
マッピングを定義しています。
uint
は値の型なので、ゲッターはその型に一致する値を返します。これは、指定したアドレスの値を返す MappingUser
コントラクトで見ることができます。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(address(this));
}
}
以下の例は、ERC20 トークンの簡略化したものです。
_allowances
は他のマッピングタイプの中にあるマッピングタイプの例です。
以下の例では、オプションの KeyName
と ValueName
がマッピングに提供されています。
これはコントラクトの機能やバイトコードには影響を与えず、マッピングのゲッターのABIの入力と出力に name
フィールドを設定するだけです。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.18;
contract MappingExampleWithNames {
mapping(address user => uint balance) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
以下の例では、_allowances
を使用して、誰かがあなたのアカウントから引き出すことができる金額を記録しています。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract MappingExample {
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
require(_allowances[sender][msg.sender] >= amount, "ERC20: Allowance not high enough.");
_allowances[sender][msg.sender] -= amount;
_transfer(sender, recipient, amount);
return true;
}
function approve(address spender, uint256 amount) public returns (bool) {
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
require(_balances[sender] >= amount, "ERC20: Not enough funds.");
_balances[sender] -= amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}
}
反復可能マッピング(Iterable Mappings)
原文
You cannot iterate over mappings, i.e. you cannot enumerate their keys.
It is possible, though, to implement a data structure on top of them and iterate over that.
For example, the code below implements an IterableMapping
library that the User
contract then adds data to, and the sum function iterates over to sum
all the values.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }
struct itmap {
mapping(uint => IndexValue) data;
KeyFlag[] keys;
uint size;
}
type Iterator is uint;
library IterableMapping {
function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {
uint keyIndex = self.data[key].keyIndex;
self.data[key].value = value;
if (keyIndex > 0)
return true;
else {
keyIndex = self.keys.length;
self.keys.push();
self.data[key].keyIndex = keyIndex + 1;
self.keys[keyIndex].key = key;
self.size++;
return false;
}
}
function remove(itmap storage self, uint key) internal returns (bool success) {
uint keyIndex = self.data[key].keyIndex;
if (keyIndex == 0)
return false;
delete self.data[key];
self.keys[keyIndex - 1].deleted = true;
self.size --;
}
function contains(itmap storage self, uint key) internal view returns (bool) {
return self.data[key].keyIndex > 0;
}
function iterateStart(itmap storage self) internal view returns (Iterator) {
return iteratorSkipDeleted(self, 0);
}
function iterateValid(itmap storage self, Iterator iterator) internal view returns (bool) {
return Iterator.unwrap(iterator) < self.keys.length;
}
function iterateNext(itmap storage self, Iterator iterator) internal view returns (Iterator) {
return iteratorSkipDeleted(self, Iterator.unwrap(iterator) + 1);
}
function iterateGet(itmap storage self, Iterator iterator) internal view returns (uint key, uint value) {
uint keyIndex = Iterator.unwrap(iterator);
key = self.keys[keyIndex].key;
value = self.data[key].value;
}
function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns (Iterator) {
while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
keyIndex++;
return Iterator.wrap(keyIndex);
}
}
// How to use it
contract User {
// Just a struct holding our data.
itmap data;
// Apply library functions to the data type.
using IterableMapping for itmap;
// Insert something
function insert(uint k, uint v) public returns (uint size) {
// This calls IterableMapping.insert(data, k, v)
data.insert(k, v);
// We can still access members of the struct,
// but we should take care not to mess with them.
return data.size;
}
// Computes the sum of all stored data.
function sum() public view returns (uint s) {
for (
Iterator i = data.iterateStart();
data.iterateValid(i);
i = data.iterateNext(i)
) {
(, uint value) = data.iterateGet(i);
s += value;
}
}
}
マッピングを反復することはできません。つまり、マッピングのキーを列挙することはできません。
しかし、マッピングの上にデータ構造を実装して、それを反復処理することは可能です。
例えば、以下のコードでは IterableMapping
ライブラリを実装しており、 User
契約がデータを追加し、sum
関数がすべての値を反復処理するようになっています。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }
struct itmap {
mapping(uint => IndexValue) data;
KeyFlag[] keys;
uint size;
}
type Iterator is uint;
library IterableMapping {
function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {
uint keyIndex = self.data[key].keyIndex;
self.data[key].value = value;
if (keyIndex > 0)
return true;
else {
keyIndex = self.keys.length;
self.keys.push();
self.data[key].keyIndex = keyIndex + 1;
self.keys[keyIndex].key = key;
self.size++;
return false;
}
}
function remove(itmap storage self, uint key) internal returns (bool success) {
uint keyIndex = self.data[key].keyIndex;
if (keyIndex == 0)
return false;
delete self.data[key];
self.keys[keyIndex - 1].deleted = true;
self.size --;
}
function contains(itmap storage self, uint key) internal view returns (bool) {
return self.data[key].keyIndex > 0;
}
function iterateStart(itmap storage self) internal view returns (Iterator) {
return iteratorSkipDeleted(self, 0);
}
function iterateValid(itmap storage self, Iterator iterator) internal view returns (bool) {
return Iterator.unwrap(iterator) < self.keys.length;
}
function iterateNext(itmap storage self, Iterator iterator) internal view returns (Iterator) {
return iteratorSkipDeleted(self, Iterator.unwrap(iterator) + 1);
}
function iterateGet(itmap storage self, Iterator iterator) internal view returns (uint key, uint value) {
uint keyIndex = Iterator.unwrap(iterator);
key = self.keys[keyIndex].key;
value = self.data[key].value;
}
function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns (Iterator) {
while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
keyIndex++;
return Iterator.wrap(keyIndex);
}
}
// How to use it
contract User {
// Just a struct holding our data.
itmap data;
// Apply library functions to the data type.
using IterableMapping for itmap;
// Insert something
function insert(uint k, uint v) public returns (uint size) {
// This calls IterableMapping.insert(data, k, v)
data.insert(k, v);
// We can still access members of the struct,
// but we should take care not to mess with them.
return data.size;
}
// Computes the sum of all stored data.
function sum() public view returns (uint s) {
for (
Iterator i = data.iterateStart();
data.iterateValid(i);
i = data.iterateNext(i)
) {
(, uint value) = data.iterateGet(i);
s += value;
}
}
}
演算子(Operators)
原文
Arithmetic and bit operators can be applied even if the two operands do not have the same type.
For example, you can compute y = x + z
, where x
is a uint8
and z
has the type uint32
.
In these cases, the following mechanism will be used to determine the type in which the operation is computed (this is important in case of overflow) and the type of the operator’s result:
- If the type of the right operand can be implicitly converted to the type of the left operand, use the type of the left operand,
- if the type of the left operand can be implicitly converted to the type of the right operand, use the type of the right operand,
- otherwise, the operation is not allowed.
In case one of the operands is a literal number it is first converted to its “mobile type”, which is the smallest type that can hold the value (unsigned types of the same bit-width are considered “smaller” than the signed types).
If both are literal numbers, the operation is computed with effectively unlimited precision in that the expression is evaluated to whatever precision is necessary so that none is lost when the result is used with a non-literal type.
The operator’s result type is the same as the type the operation is performed in, except for comparison operators where the result is always bool
.
The operators **
(exponentiation), <<
and >>
use the type of the left operand for the operation and the result.
算術演算子やビット演算子は、2つのオペランドの型が同じでなくても適用することができます。
例えば、 y = x + z
という計算ができますが、ここで x
は uint8
で、 z
は uint32
という型になります。
このような場合、以下のメカニズムにより、演算が行われた型(これはオーバーフローの場合に重要です)と演算子の結果の型が決定されます。
- 右オペランドの型を左オペランドの型に暗黙のうちに変換できる場合は、左オペランドの型を使用します。
- 左オペランドの型を右オペランドの型に暗黙のうちに変換できる場合は、右オペランドの型を使用します。
- それ以外の場合は、操作は許可されません。
オペランドの一方がリテラル数値の場合、まずその値を保持できる最小の型である「モバイル型」に変換されます(同じビット幅の符号なし型は、符号付き型よりも「小さい」とみなされます)。
両方ともリテラル数である場合、演算は事実上無制限の精度で計算されます。つまり、式は必要な精度で評価され、結果が非リテラル型と一緒に使われたときに何も失われません。
演算子の結果の型は、結果が常に bool
になる比較演算子を除いて、演算が行われた型と同じになります。
演算子 **
(指数)、<<
および >>
は、左側のオペランドの型を使って演算と結果を行います。
三項演算子(Ternary Operator)
原文
The ternary operator is used in expressions of the form <expression> ? <trueExpression> : <falseExpression>
.
It evaluates one of the latter two given expressions depending upon the result of the evaluation of the main <expression>
.
If <expression>
evaluates to true, then <trueExpression>
will be evaluated, otherwise <falseExpression>
is evaluated.
The result of the ternary operator does not have a rational number type, even if all of its operands are rational number literals.
The result type is determined from the types of the two operands in the same way as above, converting to their mobile type first if required.
As a consequence, 255 + (true ? 1 : 0)
will revert due to arithmetic overflow.
The reason is that (true ? 1 : 0)
is of uint8
type, which forces the addition to be performed in uint8
as well, and 256 exceeds the range allowed for this type.
Another consequence is that an expression like 1.5 + 1.5
is valid but 1.5 + (true ? 1.5 : 2.5)
is not.
This is because the former is a rational expression evaluated in unlimited precision and only its final value matters.
The latter involves a conversion of a fractional rational number to an integer, which is currently disallowed.
三項演算子は、<expression> ? <trueExpression> : <falseExpression>
のような形の式で使われます。
これは、メインの <expression>
の評価結果に応じて、後者の2つの式のうちどちらかを評価します。
もし <expression>
が真と評価されれば <trueExpression>
が評価され、そうでなければ <falseExpression>
が評価されます。
三項演算子の結果は、たとえオペランドがすべて有理数リテラルであっても有理数型を持ちません。
結果の型は、上記と同じように2つのオペランドの型から決定され、必要であれば最初にそのモバイル型に変換されます。
その結果、255 + (true ? 1 : 0)
は算術オーバーフローにより戻されます。
これは、(true ? 1 : 0)
が uint8
型であり、足し算も uint8
で行わなければならず、256 がこの型で許される範囲を超えているからです。
もう一つの結果は、1.5 + 1.5
のような式は有効ですが、1.5 + (true ? 1.5 : 2.5)
は無効であるということです。
これは、前者が無制限の精度で評価される有理数式であり、その最終値のみが重要だからです。
後者は分数の有理数から整数への変換を伴いますが、これは現在許可されていません。
複合演算子、インクリメント/デクリメント演算子(Compound and Increment/Decrement Operators)
原文
If a
is an LValue (i.e. a variable or something that can be assigned to), the following operators are available as shorthands:
a += e
is equivalent to a = a + e
.
The operators -=
, *=
, /=
, %=
, |=
, &=
, ^=
, <<=
and >>=
are defined accordingly.
a++
and a--
are equivalent to a += 1
/ a -= 1
but the expression itself still has the previous value of a
.
In contrast, --a
and ++a
have the same effect on a
but return the value after the change.
もし a
が LValue (つまり変数や代入できるもの) であれば、以下の演算子が短縮形として利用できます。
a += e
は a = a + e
と同じです。
演算子 -=
, *=
, /=
, %=
, |=
, &=
, ^=
, <<=
, >>=
は適宜定義されています。
a++
と a--
は a += 1
/ a -= 1
と同じですが、式自体は以前の値である a
を保持したままです。
これに対して、--a
と ++a
は a
に対して同じ効果を与えますが、変更後の値を返します。
削除(delete)
原文
delete a
assigns the initial value for the type to a
.
I.e. for integers it is equivalent to a = 0
, but it can also be used on arrays, where it assigns a dynamic array of length zero or a static array of the same length with all elements set to their initial value.
delete a[x]
deletes the item at index x
of the array and leaves all other elements and the length of the array untouched.
This especially means that it leaves a gap in the array.
If you plan to remove items, a mapping is probably a better choice.
For structs, it assigns a struct with all members reset.
In other words, the value of a
after delete a
is the same as if a
would be declared without assignment, with the following caveat:
delete
has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown).
So if you delete a struct, it will reset all members that are not mappings and also recurse into the members unless they are mappings.
However, individual keys and what they map to can be deleted: If a
is a mapping, then delete a[x]
will delete the value stored at x
.
It is important to note that delete a
really behaves like an assignment to a
, i.e. it stores a new object in a
.
This distinction is visible when a
is reference variable: It will only reset a
itself, not the value it referred to previously.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() public {
uint x = data;
delete x; // sets x to 0, does not affect data
delete data; // sets data to 0, does not affect x
uint[] storage y = dataArray;
delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also
// y is affected which is an alias to the storage object
// On the other hand: "delete y" is not valid, as assignments to local variables
// referencing storage objects can only be made from existing storage objects.
assert(y.length == 0);
}
}
delete a
は a
にその型の初期値を代入します。
つまり,整数の場合は a = 0
と同じですが,配列に対しても使用することができます。この場合,長さ 0 の動的配列,あるいは同じ長さですべての要素が初期値に設定された静的配列を代入します。
delete a[x]
は配列のインデックス x
の項目を削除し、他のすべての要素と配列の長さをそのまま残します。
これは特に、配列に隙間を残すことを意味します。
もしアイテムを削除する予定があるなら、マッピングの方が良い選択でしょう。
構造体の場合は、すべてのメンバがリセットされた構造体が代入されます。
つまり、 delete a
の後の a
の値は、以下の注意点を除いて、 a
を割り当てずに宣言した場合と同じになります。
delete
はマッピングには効果がありません(マッピングのキーは任意であり、一般に未知であるため)。
そのため、構造体を削除すると、マッピングでないメンバはすべてリセットされ、マッピングでないメンバにも再帰的にアクセスすることになります。
しかし、個々のキーとそのマッピングは削除することができます。a
がマッピングの場合、 delete a[x]
は x
に格納されている値を削除します。
重要なことは、 delete a
は実際には a
への代入のように動作することです。つまり、新しいオブジェクトを a
に格納します。
この区別は a
が参照変数の場合に見ることができます。これは a
自身をリセットするだけで、前に参照していた値をリセットするわけではありません。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() public {
uint x = data;
delete x; // sets x to 0, does not affect data
delete data; // sets data to 0, does not affect x
uint[] storage y = dataArray;
delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also
// y is affected which is an alias to the storage object
// On the other hand: "delete y" is not valid, as assignments to local variables
// referencing storage objects can only be made from existing storage objects.
assert(y.length == 0);
}
}
演算子の優先順(Order of Precedence of Operators)
原文
The following is the order of precedence for operators, listed in order of evaluation.
Precedence | Description | Operator |
---|---|---|
1 | Postfix increment and decrement |
++ , --
|
New expression | new <typename> |
|
Array subscripting | <array>[<index>] |
|
Member access | <object>.<member> |
|
Function-like call | <func>(<args...>) |
|
Parentheses | (<statement>) |
|
2 | Prefix increment and decrement |
++ , --
|
Unary minus | - |
|
Unary operations | delete |
|
Logical NOT | ! |
|
Bitwise NOT | ~ |
|
3 | Exponentiation | ** |
4 | Multiplication, division and modulo |
* , / , %
|
5 | Addition and subtraction |
+ , -
|
6 | Bitwise shift operators |
<< , >>
|
7 | Bitwise AND | & |
8 | Bitwise XOR | ^ |
9 | Bitwise OR | ` |
10 | Inequality operators |
< , > , <= , >=
|
11 | Equality operators |
== , !=
|
12 | Logical AND | && |
13 | Logical OR | ` |
14 | Ternary operator | <conditional> ? <if-true> : <if-false> |
Assignment operators |
= , \|= , ^= , &= , <<= , >>= , += , -= , *= , /= , %=
|
|
15 | Comma operator | , |
演算子の優先順位を評価順に並べると次のようになります。
優先順位 | 説明 | 演算子 |
---|---|---|
1 | 接尾インクリメントとデクリメント |
++ , --
|
New 式 | new <typename> |
|
配列の添字 | <array>[<index>] |
|
メンバアクセス | <object>.<member> |
|
関数的な呼び出し | <func>(<args...>) |
|
括弧内 | (<statement>) |
|
2 | 接頭インクリメントとデクリメント |
++ , --
|
単項マイナス | - |
|
単項演算 | delete |
|
論理NOT | ! |
|
ビット単位NOT | ~ |
|
3 | べき乗 | ** |
4 | 乗算、除算、モジュロ |
* , / , %
|
5 | 加算、減算 |
+ , -
|
6 | ビット単位シフト演算 |
<< , >>
|
7 | ビット単位AND | & |
8 | ビット単位XOR | ^ |
9 | ビット単位OR | ` |
10 | 不等号演算 |
< , > , <= , >=
|
11 | 等号演算 |
== , !=
|
12 | 論理AND | && |
13 | 論理OR | ` |
14 | 三項演算子 | <conditional> ? <if-true> : <if-false> |
代入演算 |
= , \|= , ^= , &= , <<= , >>= , += , -= , *= , /= , %=
|
|
15 | カンマ演算 | , |
基本型間の変換(Conversions between Elementary Types)
暗黙の変換(Implicit Conversions)
原文
An implicit type conversion is automatically applied by the compiler in some cases during assignments, when passing arguments to functions and when applying operators.
In general, an implicit conversion between value-types is possible if it makes sense semantically and no information is lost.
For example, uint8
is convertible to uint16
and int128
to int256
, but int8
is not convertible to uint256
, because uint256
cannot hold values such as -1
.
If an operator is applied to different types, the compiler tries to implicitly convert one of the operands to the type of the other (the same is true for assignments).
This means that operations are always performed in the type of one of the operands.
For more details about which implicit conversions are possible, please consult the sections about the types themselves.
In the example below, y
and z
, the operands of the addition, do not have the same type, but uint8
can be implicitly converted to uint16
and not vice-versa.
Because of that, y
is converted to the type of z
before the addition is performed in the uint16
type.
The resulting type of the expression y + z
is uint16
.
Because it is assigned to a variable of type uint32
another implicit conversion is performed after the addition.
uint8 y;
uint16 z;
uint32 x = y + z;
暗黙の型変換は、代入時、関数への引数渡し時、演算子適用時にコンパイラによって自動的に適用される場合があります。
一般に、値型間の暗黙の変換は、それが意味的に意味を持ち、情報が失われない場合には可能です。
例えば、 uint8
は uint16
に、 int128
は int256
に変換できますが、 uint256
は -1
などの値を保持できないため、 int8
は uint256
に変換できません。
演算子が異なる型に適用される場合、コンパイラはオペランドの一方を暗黙のうちに他方の型に変換しようとします。(代入の場合も同様)
つまり、演算は常に一方のオペランドの型で実行されます。
どのような暗黙の変換が可能かについての詳細は、型そのものについてのセクションを参照してください。
下の例では、加算のオペランドである y
と z
は同じ型ではありませんが、 uint8
は暗黙のうちに uint16
に変換でき、その逆はできません。
そのため、y
は uint16
型で加算を行う前に z
型に変換されます。
その結果、式 y + z
の型は uint16
となる。
これは uint32
型の変数に代入されるため、加算の後に別の暗黙の変換が行われます。
uint8 y;
uint16 z;
uint32 x = y + z;
明示的な変換(Explicit Conversions)
原文
If the compiler does not allow implicit conversion but you are confident a conversion will work, an explicit type conversion is sometimes possible.
This may result in unexpected behaviour and allows you to bypass some security features of the compiler, so be sure to test that the result is what you want and expect!
Take the following example that converts a negative int
to a uint
:
int y = -3;
uint x = uint(y);
At the end of this code snippet, x
will have the value 0xfffff..fd
(64 hex characters), which is -3 in the two’s complement representation of 256 bits.
If an integer is explicitly converted to a smaller type, higher-order bits are cut off:
uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now
If an integer is explicitly converted to a larger type, it is padded on the left (i.e., at the higher order end).
The result of the conversion will compare equal to the original integer:
uint16 a = 0x1234;
uint32 b = uint32(a); // b will be 0x00001234 now
assert(a == b);
Fixed-size bytes types behave differently during conversions.
They can be thought of as sequences of individual bytes and converting to a smaller type will cut off the sequence:
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b will be 0x12
If a fixed-size bytes type is explicitly converted to a larger type, it is padded on the right.
Accessing the byte at a fixed index will result in the same value before and after the conversion (if the index is still in range):
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b will be 0x12340000
assert(a[0] == b[0]);
assert(a[1] == b[1]);
Since integers and fixed-size byte arrays behave differently when truncating or padding, explicit conversions between integers and fixed-size byte arrays are only allowed, if both have the same size.
If you want to convert between integers and fixed-size byte arrays of different size, you have to use intermediate conversions that make the desired truncation and padding rules explicit:
bytes2 a = 0x1234;
uint32 b = uint16(a); // b will be 0x00001234
uint32 c = uint32(bytes4(a)); // c will be 0x12340000
uint8 d = uint8(uint16(a)); // d will be 0x34
uint8 e = uint8(bytes1(a)); // e will be 0x12
bytes
arrays and bytes
calldata slices can be converted explicitly to fixed bytes types (bytes1
/…/bytes32
).
In case the array is longer than the target fixed bytes type, truncation at the end will happen. If the array is shorter than the target type, it will be padded with zeros at the end.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;
contract C {
bytes s = "abcdefgh";
function f(bytes calldata c, bytes memory m) public view returns (bytes16, bytes3) {
require(c.length == 16, "");
bytes16 b = bytes16(m); // if length of m is greater than 16, truncation will happen
b = bytes16(s); // padded on the right, so result is "abcdefgh\0\0\0\0\0\0\0\0"
bytes3 b1 = bytes3(s); // truncated, b1 equals to "abc"
b = bytes16(c[:8]); // also padded with zeros
return (b, b1);
}
}
コンパイラが暗黙的な変換を許可していなくても、変換が機能すると確信している場合は、明示的な型変換が可能な場合があります。
これにより、予期しない動作が発生する可能性があり、コンパイラの一部のセキュリティ機能をバイパスできます。そのため、結果が意図したとおりであることを必ずテストしてください!
次の例では、負の int
を uint
に変換しています。
int y = -3;
uint x = uint(y);
このコードの最後では、x
は0xfffff..fd
(64文字の16進数)という値を持ち、これは256ビットの2の補数表現で-3です。
整数をより小さい型に明示的に変換する場合,高次のビットはカットされます。
uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now
整数がより大きな型に明示的に変換された場合,左側(つまり高次の端)にパディングされます。
変換の結果は元の整数と比較されます。
uint16 a = 0x1234;
uint32 b = uint32(a); // b will be 0x00001234 now
assert(a == b);
固定サイズのバイトタイプは、変換時に異なる動作をします。
それらは個々のバイトのシーケンスと考えることができ、より小さなタイプに変換するとシーケンスが切断されます。
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b will be 0x12
固定サイズのバイト型がより大きな型に明示的に変換された場合、右側にパディングされます。
固定インデックスのバイトにアクセスすると、変換前と変換後では同じ値になります。(インデックスがまだ範囲内にある場合)
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b will be 0x12340000
assert(a[0] == b[0]);
assert(a[1] == b[1]);
整数と固定サイズのバイト配列は、切り捨てやパディングの際に異なる振る舞いをするので、整数と固定サイズのバイト配列の間の明示的な変換は、両者が同じサイズである場合にのみ許可されます。
もし、異なるサイズの整数と固定サイズバイト配列の間を変換したいならば、 望みの切り捨てとパディングの規則を明示的にする中間変換を使わなければなりません。
bytes2 a = 0x1234;
uint32 b = uint16(a); // b will be 0x00001234
uint32 c = uint32(bytes4(a)); // c will be 0x12340000
uint8 d = uint8(uint16(a)); // d will be 0x34
uint8 e = uint8(bytes1(a)); // e will be 0x12
bytes
配列と bytes
calldata スライスは、明示的に固定バイト型 (bytes1
/.../bytes32
) に変換することが可能です。
配列が目的の固定バイト型よりも長い場合は、末尾で切り詰めます。配列が目的の固定バイト型よりも短い場合は、末尾に 0 が詰められます。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;
contract C {
bytes s = "abcdefgh";
function f(bytes calldata c, bytes memory m) public view returns (bytes16, bytes3) {
require(c.length == 16, "");
bytes16 b = bytes16(m); // if length of m is greater than 16, truncation will happen
b = bytes16(s); // padded on the right, so result is "abcdefgh\0\0\0\0\0\0\0\0"
bytes3 b1 = bytes3(s); // truncated, b1 equals to "abc"
b = bytes16(c[:8]); // also padded with zeros
return (b, b1);
}
}
リテラルと初等型の間の変換(Conversions between Literals and Elementary Types)
整数型(Integer Types)
原文
Decimal and hexadecimal number literals can be implicitly converted to any integer type that is large enough to represent it without truncation:
uint8 a = 12; // fine
uint32 b = 1234; // fine
uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456
Note
Prior to version 0.8.0, any decimal or hexadecimal number literals could be explicitly converted to an integer type.
From 0.8.0, such explicit conversions are as strict as implicit conversions, i.e., they are only allowed if the literal fits in the resulting range.
10進数と16進数の数値リテラルは、切り捨てずに表現するのに十分な大きさの任意の整数型に暗黙のうちに変換することができます。
uint8 a = 12; // fine
uint32 b = 1234; // fine
uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456
Note
バージョン0.8.0以前は、10進数や16進数のリテラルはすべて明示的に整数型に変換することができました。
0.8.0以降では、このような明示的な変換は暗黙的な変換と同様に厳格で、つまり、リテラルが結果の範囲に収まっている場合にのみ許可されます。
固定サイズバイト配列(Fixed-Size Byte Arrays)
原文
Decimal number literals cannot be implicitly converted to fixed-size byte arrays.
Hexadecimal number literals can be, but only if the number of hex digits exactly fits the size of the bytes type.
As an exception both decimal and hexadecimal literals which have a value of zero can be converted to any fixed-size bytes type:
bytes2 a = 54321; // not allowed
bytes2 b = 0x12; // not allowed
bytes2 c = 0x123; // not allowed
bytes2 d = 0x1234; // fine
bytes2 e = 0x0012; // fine
bytes4 f = 0; // fine
bytes4 g = 0x0; // fine
String literals and hex string literals can be implicitly converted to fixed-size byte arrays, if their number of characters matches the size of the bytes type:
bytes2 a = hex"1234"; // fine
bytes2 b = "xy"; // fine
bytes2 c = hex"12"; // not allowed
bytes2 d = hex"123"; // not allowed
bytes2 e = "x"; // not allowed
bytes2 f = "xyz"; // not allowed
10進数リテラルは、固定サイズのバイト配列に暗黙のうちに変換することはできません。
16進数リテラルは変換可能ですが、16進数の桁数がバイト型のサイズにぴったり合う場合のみです。
例外として、値が0の10進数および16進数リテラルは、任意の固定サイズバイト型に変換することができます。
bytes2 a = 54321; // not allowed
bytes2 b = 0x12; // not allowed
bytes2 c = 0x123; // not allowed
bytes2 d = 0x1234; // fine
bytes2 e = 0x0012; // fine
bytes4 f = 0; // fine
bytes4 g = 0x0; // fine
文字列リテラルと16進文字列リテラルは、その文字数がbytes型のサイズと一致する場合、暗黙のうちに固定サイズのバイト配列に変換されることがあります。
bytes2 a = hex"1234"; // fine
bytes2 b = "xy"; // fine
bytes2 c = hex"12"; // not allowed
bytes2 d = hex"123"; // not allowed
bytes2 e = "x"; // not allowed
bytes2 f = "xyz"; // not allowed
アドレス(Addresses)
原文
As described in Address Literals, hex literals of the correct size that pass the checksum test are of address
type.
No other literals can be implicitly converted to the address
type.
Explicit conversions to address
are allowed only from bytes20
and uint160
.
An address a
can be converted explicitly to address payable
via payable(a)
.
Note
Prior to version 0.8.0, it was possible to explicitly convert from any integer type (of any size, signed or unsigned) to address
or address payable
.
Starting with in 0.8.0 only conversion from uint160
is allowed.
アドレス・リテラルで説明したように、チェックサムテストをパスした正しいサイズの16進数リテラルは address
型になります。
他のリテラルは暗黙のうちに address
型に変換されることはありません。
アドレスへの明示的な変換は、bytes20
と uint160
からのみ可能です。
address a
は payable(a)
を介して明示的に address payable
に変換することができます。
Note
バージョン 0.8.0 以前では、任意の整数型 (サイズ、符号付き、符号なし) から address
や address payable
に明示的に変換することが可能でした。
0.8.0 以降では、 uint160
からの変換のみが可能です。
さいごに
ここで注目したのは、マッピング型です。
KeyName
が存在しない(設定していない)場合は、ValueName
は初期値として返ってくるみたいです。
注意しなければならないと思いました。
一旦、ざっとみたところ、JavaScriptに似ていますが、異なる部分があるので慎重に設計・製造しなければならなそうです。