Yul
根据官方文档:
Yul(以前也称为 JULIA 或 IULIA)是一种中间语言,可以编译为不同后端的字节码。 它可以在独立模式下使用,也可以在 Solidity 中用于“内联汇编”。编译器使用 Yul 作为基于 IR 的代码生成器(“new codegen”或“IR-based codegen”)中的中间语言。 Yul 是高级优化阶段的良好目标,可以使所有目标平台平等受益。
Why Yul
通过使用汇编,我们可以直接访问堆栈,并且可以优化代码以提高内存效率,从而节省执行事务所需的气体量。这最终降低了我们用户的交易成本。
但是在可读性方面做出了妥协,比如下面是一个返回合约名称的例子
function name() public pure returns (string memory) {
assembly {
mstore(0x20, 0x20)
mstore(0x47, 0x07536561706f7274)
return(0x20, 0x60)
}
}
学习Yul
从一个简单的例子来学习:
contract StoringData {
function setData(uint256 newValue) public {
assembly {
sstore(0, newValue)
}
}
function getData() public view returns(uint256) {
assembly {
let v := sload(0)
mstore(0x80, v)
return(0x80, 32)
}
}
}
我们来一行行分析一下:
setData
使用sstore
将newValue
写入了storage
getData
使用sload
调用数据,但是这不能直接从storage
返回,所以我们要先用mstore
写入memory
,然后返回在memory
中对该数据位置的饮用。
Bits & Bytes
所有数据均由 1
和 0
组成。这些被称为位,例如 uint256
变量将有 256
个唯一的 1
和 0
来表示定点数。
8
bits
= 1
Byte
。因此,字母 A
01000001
的字节看起来像这样。以太坊的虚拟机是围绕 32
字节槽构建的。 32*8= 256
这就是 uint256
变量在 Solidity
中如此广泛使用的原因。
Some Simple Example
- If&Switch
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
contract AssemblyIf {
function yul_if(uint256 x) public pure returns (uint256 z) {
assembly {
// if condition = 1 { code }
// no else
// if 0 { z := 99 }
// if 1 { z := 99 }
if lt(x, 10) { z := 99 }
}
}
function yul_switch(uint256 x) public pure returns (uint256 z) {
assembly {
switch x
case 1 { z := 10 }
case 2 { z := 20 }
default { z := 0 }
}
}
function min(uint256 x, uint256 y) public pure returns (uint256 z) {
z = y;
// Code here
assembly{
if lt(x,y) {z := x}
}
}
function max(uint256 x, uint256 y) public pure returns (uint256 z) {
// Code here
assembly{
switch gt(x,y)
case 1 {z:=x}
default {z := y}
}
}
}
- no If else in Assembly
- For & While
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
contract AssemblyLoop {
function yul_for_loop() public pure returns (uint256 z) {
assembly {
for { let i := 0 } lt(i, 10) { i := add(i, 1) } { z := add(z, 1) }
}
}
function yul_while_loop() public pure returns (uint256 z) {
assembly {
let i := 0
for {} lt(i, 5) {} {
i := add(i, 1)
z := add(z, 1)
}
}
}
function sum(uint256 n) public pure returns (uint256 z) {
// Code here
assembly {
for {let i :=0} lt(i,n) {i := add(i,1)} {
z := add(z,i)
}
}
}
// Calculate x**n where n = 2**k
// x > 0
// No overflow check
function pow2k(uint256 x, uint256 n) public pure returns (uint256 z) {
require(x > 0, "x = 0");
assembly {
if mod(n, 2) { revert(0, 0) }
switch n
case 0 { z := 1 }
default { z := x }
for {} gt(n, 1) {} {
if mod(n, 2) { revert(0, 0) }
z := mul(z, z)
n := div(n, 2)
}
}
}
}
- Error
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract AssemblyError {
function yul_revert(uint256 x) public pure {
assembly {
// revert(p, s) - end execution
// revert state changes
// return data mem[p…(p+s))
if gt(x, 10) { revert(0, 0) }
}
}
}
- Wirte-to-any-slot
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
library StorageSlot {
// Wrap address in a struct so that it can be passed around as a storage pointer
struct AddressSlot {
address value;
}
function getAddressSlot(bytes32 slot)
internal
pure
returns (AddressSlot storage pointer)
{
assembly {
// Get the pointer to AddressSlot stored at slot
pointer.slot := slot
}
}
}
contract TestSlot {
bytes32 public constant TEST_SLOT = keccak256("TEST_SLOT");
function write(address _addr) external {
StorageSlot.AddressSlot storage data =
StorageSlot.getAddressSlot(TEST_SLOT);
data.value = _addr;
}
function get() external view returns (address) {
StorageSlot.AddressSlot storage data =
StorageSlot.getAddressSlot(TEST_SLOT);
return data.value;
}
}
Refer
https://docs.soliditylang.org/en/latest/yul.html https://jamesbachini.com/assembly-in-solidity/