Solidity之旅(十八)內聯彙編 [inline assembly]

BSN研習社發表於2024-01-12

概要

自從C語言問世,而後類C語言猶如雨後春筍般地攪動著IT界,而這些語言有別於組合語言那樣。它們就是更貼切自然語言的高 級程式語言,可這些高 級程式語言最終還是要編譯成機器語言(組合語言)。

EVM(Ethereum Virtual Machine)是一種棧(Stack)結構,我們知道棧是一種先進後出(LIFO)的資料結構。

為什麼要用匯編來編寫呢?

借您所問,既然Solidity可以編寫出優秀的智慧合約,那為什麼還要使用低階的組合語言呢?

在回答這個問題之前,我們來看看每個新的程式語言誕生都是為了解決當前程式語言無法解決,或者說使用當前程式語言解決起來比較麻煩,那麼,新的程式語言就在這樣的環境下應運而生,當然咯,並不是所有新程式語言都是為了解決當前程式語言不能解決的問題,才被開發出來,而是……(此處不便說出緣由,畢竟它也不是本文的重點)。

細粒度控制

Assembly允許您執行一些僅僅靠Solidity無法實現的邏輯,比如,指向特定的記憶體插槽(Memory Slot)。

當我們在編寫庫(library)時,細粒度控制特別有用,因為它們會被重複使用。

節省gas

在Solidity中使用Assembly的主要好處之一是節省gas。讓我們嘗試透過建立一個將2個值x和y相加並返回結果的函式來比較Solidity和Assembly之間的gas成本。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract AssemblyExample {
    
    function addAssembly(uint x, uint y) public pure returns (uint) {
        assembly {
            let result := add(x, y)
            mstore(0x0, result)
            return(0x0, 32)
        }
    }
 
    function addSolidity(uint x, uint y) public pure returns (uint) {
        return x + y;
    }
  
}

Solidity之旅(十八)內聯彙編 [inline assembly]

Solidity中兩種方式實現Assembly

1、內聯彙編:也可以在Solidity程式碼中使用。2、獨立程式集:無需編寫Solidity程式碼即可使用。

怎麼使用Assembly?

正如上面的例子那樣,彙編程式碼執行在assembly{...}彙編塊中的。而彙編程式碼是使用YUL語言來編寫的!內聯彙編塊不共享名稱空間,即不能在一個彙編塊呼叫另一個彙編塊中定義的變數。

assembly {
   // some assembly code here
}

Solidity之旅(十八)內聯彙編 [inline assembly]

以下是一個簡單的示例,函式接受兩個引數,並將它們的和作為返回值,看看使用Assembly是怎麼實現的?瞭解它們在EVM的工作方式。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract AssemblyExample {
    
   function addition(uint x, uint y) public pure returns (uint) {
     
    assembly {
        
        //宣告一個 result 變數,並將 x,y之和賦值給它
        let result := add(x, y)   // x + y
        
        //使用 mstore 操作碼將 result存在 memory 中,地址是 0x0
        mstore(0x0, result)       // store result in memory
         
        //返回 32 位元組的 memory 地址
        return(0x0, 32)          
        
    }
}
  
}

Solidity之旅(十八)內聯彙編 [inline assembly]

資料儲存

讓我們來看看一個簡單的例子。我們將資料存放在storage(儲存)中,然後再去呼叫它。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract StorageDataExample {
    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)
        }
    }
}

Solidity之旅(十八)內聯彙編 [inline assembly]

setData函式使用了sstore操作碼將變數newValue寫入storage(儲存)中。getData函式先是用了sload操作碼來載入storage(儲存)中的資料,它並不能從storage中直接返回。所以才需要mstore操作碼將其寫入memory(記憶體)中,最後我們返回引用memory(記憶體)中存放資料的地址和32位元組長度的資料。

指令

解釋
stop() - F 停止執行,與return(0, 0)相同
add(x, y)
F x + y
sub(x, y)
F x - y
mul(x, y)
F x * y
div(x, y)
F x / y 或 如果 y == 0,則為 0
sdiv(x, y)
F x / y,對於有符號的二進位制補數,如果 y == 0,則為 0
mod(x, y)
F x % y, 如果 y == 0,則為 0
smod(x, y)
F x % y, 對於有符號的二進位制補數, 如果 y == 0,則為 0
exp(x, y)
F x的y次方
not(x)
F x的位 “非”(x的每一個位都被否定)
lt(x, y)
F 如果 x < y,則為1,否則為0
gt(x, y)
F 如果 x > y,則為1,否則為0
slt(x, y)
F 如果 x < y,則為1,否則為0,適用於有符號的二進位制數
sgt(x, y)
F 如果 x > y,則為1,否則為0,適用於有符號的二進位制補數
eq(x, y)
F 如果 x == y,則為1,否則為0
iszero(x)
F 如果 x == 0,則為1,否則為0
and(x, y)
F x 和 y 的按位 “與”
or(x, y)
F x 和 y 的按位 “或”
xor(x, y)
F x 和 y 的按位 “異或”
byte(n, x)
F x的第n個位元組,其中最重要的位元組是第0個位元組
shl(x, y)
C 將 y 邏輯左移 x 位
shr(x, y)
C 將 y 邏輯右移 x 位
sar(x, y)
C 將 y 算術右移 x 位
addmod(x, y, m)
F (x + y) % m,採用任意精度算術,如果m == 0則為0
mulmod(x, y, m)
F (x * y) % m,採用任意精度算術,如果m == 0則為0
signextend(i, x)
F 從第 (i*8+7) 位開始進行符號擴充套件,從最低符號位開始計算
keccak256(p, n)
F keccak(mem[p…(p+n)))
pc()
F 程式碼中的當前位置
pop(x) - F 丟棄值 x
mload§
F mem[p…(p+32))
mstore(p, v) - F mem[p…(p+32)) := v
mstore8(p, v) - F mem[p] := v & 0xff ((只修改了一個位元組))
sload§
F storage[p]
sstore(p, v) - F storage[p] := v
msize()
F 記憶體的大小,即最大的訪問記憶體索引
gas()
F 仍可以執行的氣體值
address()
F 當前合約/執行環境的地址
balance(a)
F 地址為A的餘額,以wei為單位
selfbalance()
I 相當於balance(address()),但更便宜
caller()
F 訊息呼叫者(不包括 delegatecall 呼叫)。
callvalue()
F 與當前呼叫一起傳送的wei的數量
calldataload§
F 從位置p開始的呼叫資料(32位元組)
calldatasize()
F 呼叫資料的大小,以位元組為單位
calldatacopy(t, f, s) - F 從位置f的calldata複製s位元組到位置t的記憶體中
codesize()
F 當前合約/執行環境的程式碼大小
codecopy(t, f, s) - F 從位置f的code中複製s位元組到位置t的記憶體中
extcodesize(a)
F 地址為a的程式碼的大小
extcodecopy(a, t, f, s) - F 像codecopy(t, f, s)一樣,但在地址a處取程式碼
returndatasize()
B 最後返回資料的大小
returndatacopy(t, f, s) - B 從位置f的returndata複製s位元組到位置t的記憶體中
extcodehash(a)
C 地址a的程式碼雜湊值
create(v, p, n)
F 用程式碼mem[p…(p+n))建立新的合約,傳送v數量的wei並返回新地址; 錯誤時返回0
create2(v, p, n, s)
C 在keccak256(0xff . this . s . keccak256(mem[p…(p+n)))地址處 建立程式碼為mem[p…(p+n)]的新合約 併傳送v 數量個wei和返回新地址, 其中 0xff 是一個1位元組的值, this 是當前合約的地址, 是一個20位元組的值, s 是一個256位的大端的值; 錯誤時返回0
call(g, a, v, in, insize, out, outsize)
F 呼叫地址 a 上的合約,以 mem[in…(in+insize)) 作為輸入 一併傳送 g 數量的 gas 和 v 數量的 wei, 以 mem[out…(out+outsize)) 作為輸出空間。 若錯誤,返回 0 (比如,gas 用光) 若成功,返回 1
callcode(g, a, v, in, insize, out, outsize)
F 相當於 call 但僅僅使用地址 a 上的程式碼, 執行時留在當前合約的上下文當中
delegatecall(g, a, in, insize, out, outsize)
H 相當於 callcode, 但同時保留 caller 和 callvalue
staticcall(g, a, in, insize, out, outsize)
B 相當於 call(g, a, 0, in, insize, out, outsize) 但不允許狀態變數的修改
return(p, s) - F 終止執行,返回 mem[p…(p+s)) 上的資料
revert(p, s) - B 終止執行,恢復狀態變更,返回 mem[p…(p+s)) 上的資料
selfdestruct(a) - F 終止執行,銷燬當前合約,並且將餘額傳送到地址 a
invalid() - F 以無效指令終止執行
log0(p, s) - F 用 mem[p…(p+s)] 上的資料產生日誌,但沒有 topic
log1(p, s, t1) - F 用 mem[p…(p+s)] 上的資料和 topic t1 產生日誌
log2(p, s, t1, t2) - F 用 mem[p…(p+s)] 上的資料和 topic t1,t2 產生日誌
log3(p, s, t1, t2, t3) - F 用 mem[p…(p+s)] 上的資料和 topic t1,t2,t3 產生日誌
log4(p, s, t1, t2, t3, t4) - F 用 mem[p…(p+s)] 上的資料和 topic t1,t2,t3,t4 產生日誌
chainid()
I 執行鏈的ID(EIP-1344)
basefee()
L 當前區塊的基本費用(EIP-3198和EIP-1559)
origin()
F 交易傳送者
gasprice()
F 交易的氣體價格n
blockhash(b)
F 區塊編號b的雜湊值–只針對最近的256個區塊,不包括當前區塊。
coinbase()
F 目前的挖礦的受益者
timestamp()
F 自 epoch 開始的,當前塊的時間戳,以秒為單位
number()
F 當前區塊號
difficulty()
F 當前區塊的難度
gaslimit()
F 當前區塊的區塊 gas 限制

版權宣告:本文為 CSDN 博主「甄齊才」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結及本宣告。

原文連結:

https://blog.csdn.net/coco2d_x2014/article/details/128473584

文章來源:CSDN 博主「甄齊才」

文章原標題:《玩以太坊鏈上專案的必備技能(內聯彙編 [inline assembly]-Solidity之旅十八)》

旨在傳播區塊鏈相關技術,如有侵權請與我們聯絡刪除。


來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70012206/viewspace-3003715/,如需轉載,請註明出處,否則將追究法律責任。

相關文章