Blockchain
Ethereum
solidity
Yul
EthereumDay 23

中間言語Yulについて

Ethereum Advent Calendar 2018 23日目の記事です。

(完全に遅刻しました...)

今回はYulの話をします。

追記: Yul Objectの部分のコードの例が間違っていたので修正しました。 差分 差分2


TL;DR


  • EVM assemblyにコンパイル可能な中間言語としてYulの開発が進められています。

  • Solidity v0.5.2からYulだけでコントラクトが作れるようになりました。

  • Yul onlyなコントラクトの完全な例はまだ無い(はず)なので、作ると徳が高い人になれます。


Yulとは

https://solidity.readthedocs.io/en/v0.5.2/yul.html#yul

現在EVM assemblyにコンパイル可能な中間言語としてYul(Yet another Universal Language)の開発が進められています。

以前はJULIAやIULIAと呼ばれていましたが、データ分析系でよく使われる言語Juliaと名前が同じである等の理由からYulと呼ばれるようになりました。

Yet another Universal Languageという名前の通り、EVM(1.0)に特化したものではなく、将来的にはEVM1.5やeWASMへの対応も予定されています。

さて、最近までYulはinline assemblyとしてしか使えなかったのですが、つい先日(12/19)Solidityのv0.5.2がリリースされ、Yulだけで完全なコントラクトを作れるようになりました。

https://github.com/ethereum/solidity/releases/tag/v0.5.2

You can now create complete contracts in Yul through the support of the Yul object format and the special functions datasize, dataoffset and datacopy.

(datasize, dataoffset, datacopyといったデプロイに必要なビルトイン関数が追加されました。)

この記事では主に(従来のinline assemblyとして使われたものではなく)こちらのYul onlyなコントラクトで使われているYulの仕様やコード例などを紹介していきます。

なおYulの最新の動向については

などが参考になります。


仕様

大まかな特徴としては公式ドキュメントから引用すると


  • コアのコンポーネントは関数、ブロック、変数、リテラル, for, if, switch, 変数に対する式(?)や割り当てである

  • 変数とリテラルは bool, u8, s8, u32, s32, u64, s64, u128, s128, u256, s256 の型を持つ


    • (u は unsigned, s はsignedの略)



  • EVM opcode自体はYulの仕様には含まれておらず、EVMがターゲットとなる時にビルトイン関数としてopcodeを使うことが出来る

です。

(補足: Solidity v0.5.2以前のinline assemblyとして使われていたYulは、全ての型が u256 でビルトイン関数が全てEVM opcodeと同じであるという違いがあります。実装例: AZTEC)

現状ではEVM1.0向けの実装しかなく、まだ実際にSolidityの中間言語として使われているわけではないので今のところforやifなどの便利なビルトイン関数が追加されたinline assemblyと言えるかもしれません。 (※個人の感想です

説明だけでは分かりづらいので(ドキュメントそのままですが)以下実際のコード例です。

中間言語とはいえ書き方は比較的直感的です。


関数単位での例

まずは関数単位での例から。


再帰によるべき乗の計算

{

function power(base:u256, exponent:u256) -> result:u256
{
switch exponent
case 0:u256 { result := 1:u256 }
case 1:u256 { result := base }
default
{
result := power(mul(base, base), div(exponent, 2:u256))
switch mod(exponent, 2:u256)
case 1:u256 { result := mul(base, result) }
}
}
}

switch がYul、 mul, div, mod がopcodeをそのまま使っている部分です。


ループによるべき乗の計算

{

function power(base:u256, exponent:u256) -> result:u256
{
result := 1:u256
for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) }
{
result := mul(result, base)
}
}
}

こちらも同様に for がYul, lt, add がopcodeの部分です。


コード全体での例

YulにはYul Objectというコードの断片をグルーピング出来る仕様があり、それを利用しています。

以下の例はデプロイ用のコード(creation code, コンストラクタに相当)と実際にブロックチェーン上に書き込まれるコード(runtime code)をYul objectを使って記述している例です。

https://gist.github.com/chriseth/f55c0384f6bb11e3970830a160ca5c03

 object "MyContract" {

code {
// this is the constructor.
// store the creator in the first storage slot
sstore(0, caller())
// now return the runtime code using the special functions
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
// runtime - just return the creator
mstore(0, sload(0))
return(0, 0x20)
}
}

このコードは本質的には object "MyContract" { code { ... } } 部分がSolidityでいうところの

  address creator;

constructor() public {
creator = msg.sender;
}

に対応していて、 object "Runtime" { code { ... } } の部分が

  return creator;

に対応しています。

上記の例はデプロイが出来てトランザクションを送ると creator が返ってくるという(bytecodeレベルでの)最小限のコードです。

実際のSolidityからコンパイルしたコントラクトでは、トランザクションが送られて来た時にどの関数を実行するかを選ぶFunction Selectorの部分などがbytecodeに含まれています。

関数呼び出しなどが出来る完全なコントラクトの例は、自分が知る限りではv0.5.2がリリースされたばかりなのもあってまだ無いようです。


まとめになってないまとめ


  • Yulを使わないでSolidityだけで書けるならSolidityで書いたほうが良いです

  • inline assemblyを書かざるを得なくなったらYulのビルトイン関数を使うことを検討したほうが良いです

  • (誰か完全なコントラクトの例をYulで書いてください)