跳到主要内容

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使用sstorenewValue写入了storage

getData使用sload调用数据,但是这不能直接从storage返回,所以我们要先用mstore写入memory,然后返回在memory中对该数据位置的饮用。

Bits & Bytes

所有数据均由 10组成。这些被称为位,例如 uint256 变量将有 256 个唯一的 10 来表示定点数。

8 bits = 1 Byte。因此,字母 A 01000001 的字节看起来像这样。以太坊的虚拟机是围绕 32 字节槽构建的。 32*8= 256 这就是 uint256 变量在 Solidity 中如此广泛使用的原因。

Some Simple Example

  1. 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
  1. 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)
}
}
}
}
  1. 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) }
}
}
}
  1. 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/