Solidity

為什麼編譯的 Solidity 程式碼中跳轉地址計算如此復雜?

  • July 29, 2017

我有以下簡單的 Solidity 合約:

pragma solidity ^0.4.0;
contract Test {

  function Test() {
      intfunc(5);
  }

  uint8 store;

  function intfunc (uint8 a) internal {
      store = a * 9;
  }
}

我正在使用Remix對其進行編譯,並且得到了在地址 0x11 和 0x1E 之間無法解釋的字節碼(為方便起見,我在左側包含了十六進制地址):

//Standard preamble:
0x00: PUSH1 0x60 PUSH1 0x40 MSTORE CALLVALUE ISZERO PUSH1 0xB JUMPI INVALID

//Beginning of Test() constructor:
0x0B: JUMPDEST JUMPDEST PUSH1 0x20 PUSH1 0x5

//Here's the really strange code:
0x11: PUSH5 0x100000000
0x17: PUSH1 0x7
0x19: PUSH1 0x25
0x1B: DUP3
0x1C: MUL
0x1D: OR
0x1E: DIV

//Here we jump to the intfunc() function
0x1F: JUMP

//Here we come back from intfunc() and jump to rest of the Test() constructor
0x20: JUMPDEST JUMPDEST PUSH1 0x3B JUMP

//intfunc() itself:
0x25: JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH1 0xFF NOT AND PUSH1 0x9 DUP4 MUL PUSH1 0xFF AND OR SWAP1 SSTORE JUMPDEST POP JUMP

//The rest of the Test() constructor and the rest of the code is here...
0x3B: JUMPDEST ...
//(The rest isn't really relevant to this question)

0x11 和 0x1E 之間的程式碼是怎麼回事——為什麼這麼麻煩?不能用簡單的“ PUSH1 0x25 ”代替嗎?僅僅為了計算 0x25 值而經過所有這些奇怪的步驟不是浪費氣體嗎?

此外,0x17 指令中的數字 7 是從哪裡來的?這似乎完全沒有意義。

請注意,對於“優化”和“未優化”混音模式,我得到了類似的程式碼。

任何見解將不勝感激!

此行為是在此 Github PR中引入的。它看起來與在建構子上下文中儲存函式呼叫標籤的優化有關。

左移 32 位MUL 0x0100000000和操作由文件libsolidity/codegen/CompilerUtils.cppOR中的函式插入。隨後的 32 位右移由ExpressionCompiler.cpp插入,它呼叫rightShiftNumberOnStackpushCombinedFunctionEntryLabel

在某些情況下,這對於包裝需要儲存在契約儲存中的跳轉標籤可能非常有用(因此可以節省大量氣體 - 儲存很昂貴)。在這個簡單的契約的情況下,它似乎只是一個不必要的殘餘。

如果我們編譯一個簡單的合約,比如:

   contract C {

  uint store=45;

   }

我們將得到(我使用的是編譯器 0.4):

00 PUSH1 60
02 PUSH1 40
04 MSTORE

05 PUSH1 2d //value to store 45
07 PUSH1 00 //storage address
09 PUSH1 00 // useless
11 POP      //useless
12 SSTORE

但是,如果我們改變情況uintuint8改變。我們將得到一個更長的字節碼:

00 PUSH1 60
02 PUSH1 40
04 MSTORE

05 PUSH1 2d //value
07 PUSH1 00 //storage address
09 PUSH1 00//mask offset
11 PUSH2 0100// multiplier
14 EXP
15 DUP2
16 SLOAD
17 DUP2
18 PUSH1 ff
20 MUL
21 NOT
22 AND
23 SWAP1
24 DUP4
25 MUL
26 OR
27 SWAP1
28 SSTORE
29 POP

所以有什麼問題?

當我們使用時,uint我們直接使用 32 字節的字,但是當我們使用時,uint8我們只需要儲存字中的第一個字節將其與儲存槽中的其他值放在一起,因此我們需要執行一些操作以避免任何數據覆蓋。

我認為編譯器會將 0x2d 填充到 32 字節,因此插槽中的其他值將被覆蓋,我們將只保留第一個字節 (2d)。為了避免這個問題,我們使用sload載入單詞上的先前值並使用位運算(MUL NOT AND SWAP1 DUP4 MUL OR)來計算(使用的 div 和 mul 用於移位值)要儲存在將填充值 0x2d00000…00000 和前一個值組合在一起的插槽,最後我們呼叫sstore以保存結果。

引用自:https://ethereum.stackexchange.com/questions/15382