1. 程式人生 > >Call,CallCode,DelegateCall,StaticCall,你分得清嗎?

Call,CallCode,DelegateCall,StaticCall,你分得清嗎?

孔乙己懂“回”字的四種寫法,你會智慧合約的四種呼叫方式嗎?

在中大型的專案中,我們不可能在一個智慧合約中實現所有的功能,而且這樣也不利於分工合作。一般情況下,我們會把程式碼按功能劃分到不同的庫或者合約中,然後提供介面互相呼叫。

在Solidity中,如果只是為了程式碼複用,我們會把公共程式碼抽出來,部署到一個library中,後面就可以像呼叫C庫、Java庫一樣使用了。但是library中不允許定義任何storage型別的變數,這就意味著library不能修改合約的狀態。如果需要修改合約狀態,我們需要部署一個新的合約,這就涉及到合約呼叫合約的情況。

合約呼叫合約有下面4種方式:

  • CALL
  • CALLCODE
  • DELEGATECALL
  • STATICCALL

1.CALL vs. CALLCODE

CALL和CALLCODE的區別在於:程式碼執行的上下文環境不同。

具體來說,CALL修改的是被呼叫者的storage,而CALLCODE修改的是呼叫者的storage。
在這裡插入圖片描述
我們寫個合約驗證一下我們的理解:

pragma solidity ^0.4.25;

contract A {
    int public x;
    
    function inc_call(address _contractAddress) public {
        _contractAddress.call(bytes4(keccak256("inc()")));
    }
    function inc_callcode(address _contractAddress) public {
        _contractAddress.callcode(bytes4(keccak256("inc()")));
    }
}

contract B {
    int public x;
    
    function inc() public {
        x++;
    }
}

我們先呼叫一下inc_call(),然後查詢合約A和B中x的值有什麼變化:
在這裡插入圖片描述
可以發現,合約B中的x被修改了,而合約A中的x還等於0。

我們再呼叫一下inc_callcode()試試:
在這裡插入圖片描述
可以發現,這次修改的是合約A中x,合約B中的x保持不變。

2.CALLCODE vs. DELEGATECALL

實際上,可以認為DELEGATECALL是CALLCODE的一個bugfix版本,官方已經不建議使用CALLCODE了。

CALLCODE和DELEGATECALL的區別在於:msg.sender不同

具體來說,DELEGATECALL會一直使用原始呼叫者的地址,而CALLCODE不會。
在這裡插入圖片描述


我們還是寫一段程式碼來驗證我們的理解:

pragma solidity ^0.4.25;

contract A {
    int public x;
    
    function inc_callcode(address _contractAddress) public {
        _contractAddress.callcode(bytes4(keccak256("inc()")));
    }
    function inc_delegatecall(address _contractAddress) public {
        _contractAddress.delegatecall(bytes4(keccak256("inc()")));
    }
}

contract B {
    int public x;
    
    event senderAddr(address);
    function inc() public {
        x++;
        emit senderAddr(msg.sender);
    }
}

我們首先呼叫一下inc_callcode(),觀察一下log輸出:
在這裡插入圖片描述
可以發現,msg.sender指向合約A的地址,而非交易發起者的地址。

我們再呼叫一下inc_delegatecall(),觀察一下log輸出:
在這裡插入圖片描述
可以發現,msg.sender指向的是交易的發起者。

3.STATICCALL

STATICCALL放在這裡似乎有濫竽充數之嫌,因為目前Solidity中並沒有一個low level API可以直接呼叫它,僅僅是計劃將來在編譯器層面把呼叫view和pure型別的函式編譯成STATICCALL指令。

view型別的函式表明其不能修改狀態變數,而pure型別的函式則更加嚴格,連讀取狀態變數都不允許。

目前是在編譯階段來檢查這一點的,如果不符合規定則會出現編譯錯誤。如果將來換成STATICCALL指令,就可以完全在執行時階段來保證這一點了,你可能會看到一個執行失敗的交易。

話不多說,我們就先看看STATICCALL的實現程式碼吧:
在這裡插入圖片描述
可以看到,直譯器增加了一個readOnly屬性,STATICCALL會把該屬性置為true,如果出現狀態變數的寫操作,則會返回一個errWriteProtection錯誤。

就聊到這裡,相信大家已經掌握了合約的四種呼叫方式了吧~

更多文章歡迎關注“鑫鑫點燈”專欄:https://blog.csdn.net/turkeycock
或關注飛久微信公眾號:
在這裡插入圖片描述