1. 程式人生 > >區塊鏈100講:Solidity語法難點解析及故障排查

區塊鏈100講:Solidity語法難點解析及故障排查

image

1

編輯器說明

1、推薦編輯器

目前嘗試 Solidity 程式設計的最好的方式是使用 Remix(https://remix.ethereum.org/) (需要時間載入,請耐心等待)。Remix 是一個基於 Web 的 IDE,它可以讓你編寫 Solidity 智慧合約,然後部署並執行該智慧合約。

2、Visual Studio Extension

3、Visual Studio Code extension

2

REMIN的函式引用

function mint(address receiver, uint amount)

1、在REMIX輸入時,地址一定要有”“表示,否則amount就取不到值。

例如是mint(“0xca35b7d915458ef540ade6068dfe2f44e8fa733c”,101)

2、程式中,如果註釋包含中文,單步除錯的位置不準確。

3

address相關全域性函式

.balance (uint256): 該地址有多少以太坊餘額(wei為單位)
.transfer(uint256 amount): 傳送特定數量(wei為單位)的以太坊到對應地址,當出現錯誤時會扔出異常,但不會因異常而停止。固定需要耗費2300個gas。
.send(uint256 amount) returns (bool): 傳送特定數量(wei為單位)的以太坊到對應地址,當出現錯誤時會返回flase。固定需要耗費2300個gas。

【警告】send() 執行有一些風險:如果呼叫棧的深度超過1024或gas耗光,交易都會失敗。因此,為了保證安全,必須檢查send的返回值,如果交易失敗,會回退以太幣。如果用transfer會更好。

.call(…) returns (bool): CALL的低階呼叫函式,當失敗時返回false。執行需要消耗不固定的gas。

【說明】不鼓勵使用call函式,後期將會被移除。呼叫該函式可能造成安全攻擊,詳見後期安全相關文章。

.callcode(…) returns (bool): CALLCODE的低階呼叫函式,當失敗時返回false。執行需要消耗不固定的gas。

不建議使用,後續版本會刪除。

.delegatecall(…) returns (bool): DELEGATECALL的低階呼叫函式,當失敗時返回false。執行需要消耗不固定的gas。

【說明】為了和非ABI協議的合約進行互動,可以使用call() 函式, 它用來向另一個合約傳送原始資料,支援任何型別任意數量的引數,每個引數會按規則(ABI協議)打包成32位元組並一一拼接到一起。一個例外是:如果第一個引數恰好4個位元組,在這種情況下,會被認為根據ABI協議定義的函式器指定的函式簽名而直接使用。如果僅想傳送訊息體,需要避免第一個引數是4個位元組。如下面的例子:

function callfunc(address addr) returns (bool){
bytes4 methodId = bytes4(keccak256(“setScore(uint256)”));
return addr.call(methodId, 100);
}

測試地址和地址呼叫程式碼舉例

pragma solidity ^0.4.18;

contract AddrTest{

    /*event函式知識把引數資訊列印在REMIX等編譯器的LOG位置區,不需要函式定義。*/

    event logdata(bytes data);

    event LogContractAddress(address exAccount, address contractAddress);

        uint score = 0;

        /*回撥函式,沒有函式名。任何呼叫不存在的函式,這時被呼叫的合約的fallback函式會執行。

     payable:如果一個函式需要進行貨幣操作,必須要帶上payable關鍵字*/ 

   function() payable {

        logdata(msg.data);

    }

        /*智慧合約構建函式*/

    function AddrTest(){

    LogContractAddress(msg.sender,this);

    }

    function getBalance() returns (uint) {

        return this.balance;

    }

    function setScore(uint s) public {

        score = s;

    }

    function getScore() returns ( uint){ 

       return score;

    }

}

contract CallTest{ 

   /*該函式有函式申明沒有實際函式內容,在remix的value區域設定以太坊的個數,呼叫該函式會把外部賬戶(ACCOUNT)中的      以太坊轉移到智慧合約賬戶中*/

    function deposit() payable {

    }

    event logSendEvent(address to, uint value);

    event LogContractAddress(address exAccount, address contractAddress); 

           /*轉以太坊給目標地址*/ 

   function transferEther(address towho) payable { 

       towho.transfer(10);/*單位為wei*/   

            logSendEvent(towho, 10); 

   }

       /*不指定呼叫函式,則呼叫無函式名的回撥函式*/ 

   function callNoFunc(address addr) returns (bool){ 

       return addr.call("tinyxiong", 1234); 

   }

    /*制定呼叫函式的方法*/ 

   function callfunc(address addr) returns (bool){ 

       bytes4 methodId = bytes4(keccak256("setScore(uint256)"));  

      return addr.call(methodId, 100); 

   } 

     /*返回當前合約的以太坊餘額*/

    function getBalance() returns (uint) {

        return this.balance;//0

    } 

         /*銷燬智慧合約,把以太坊餘額返回給當前外部賬戶*/

    function ContractSuide() {  

      LogContractAddress(this,msg.sender);  

      suicide(msg.sender); 

   }

}

4

Contract Related

this (current contract’s type): 表示當前合約,可以顯式的轉換為Address

selfdestruct(address recipient): destroy the current contract, sending its funds to the given Address

銷燬當前合約,傳送當前以太坊餘額到給定的地址
suicide(address recipient):

selfdestruct的別名函式

5

區塊和交易屬性

block.blockhash(uint blockNumber) returns (bytes32):

給定區塊的雜湊—僅對最近的 256 個區塊有效而不包括當前區塊

block.coinbase (address):

挖出當前區塊的礦工地址

block.difficulty (uint):

當前區塊難度

block.gaslimit (uint):

當前區塊 gas 限額

block.number (uint):

當前區塊號

block.timestamp (uint):

自 unix epoch 起始當前區塊以秒計的時間戳

msg.data (bytes):

完整的 calldata

msg.gas (uint):

剩餘 gas

msg.sender (address):

訊息傳送者(當前呼叫)

msg.sig (bytes4):

calldata 的前 4 位元組(也就是函式識別符號)

msg.value (uint):

隨訊息傳送的 wei 的數量

now (uint):

目前區塊時間戳(block.timestamp)

tx.gasprice (uint):

交易的 gas 價格

tx.origin (address):

交易發起者(完全的呼叫鏈)

註解 對於每一個外部函式呼叫,包括 msg.sender 和 msg.value 在內所有 msg 成員的值都會變化。這裡包括對庫函式的呼叫。

註解 不要依賴 block.timestamp、 now 和 block.blockhash 產生隨機數,除非你知道自己在做什麼。

時間戳和區塊雜湊在一定程度上都可能受到挖礦礦工影響。例如,挖礦社群中的惡意礦工可以用某個給定的雜湊來執行賭場合約的 payout 函式,而如果他們沒收到錢,還可以用一個不同的雜湊重新嘗試。
當前區塊的時間戳必須嚴格大於最後一個區塊的時間戳,但這裡唯一能確保的只是它會是在權威鏈上的兩個連續區塊的時間戳之間的數值。

註解 基於可擴充套件因素,區塊雜湊不是對所有區塊都有效。你僅僅可以訪問最近 256 個區塊的雜湊,其餘的雜湊均為零。

6

錯誤處理

assert(bool condition): 如果條件不滿足就丟擲—用於內部錯誤。

require(bool condition): 如果條件不滿足就拋掉—用於輸入或者外部元件引起的錯誤。

revert(): 終止執行並恢復狀態變動。

7

數學和密碼學函式

addmod(uint x, uint y, uint k) returns (uint): 計算 (x + y) % k,加法會在任意精度下執行,並且加法的結果即使超過 2**256 也不會被擷取。從 0.5.0 版本的編譯器開始會加入對 k != 0 的校驗(assert)。

mulmod(uint x, uint y, uint k) returns (uint): 計算 (x * y) % k,乘法會在任意精度下執行,並且乘法的結果即使超過 2**256 也不會被擷取。從 0.5.0 版本的編譯器開始會加入對 k != 0 的校驗(assert)。

keccak256(…) returns (bytes32): 計算 (tightly packed) arguments 的 Ethereum-SHA-3 (Keccak-256)雜湊。

sha256(…) returns (bytes32): 計算 (tightly packed) arguments 的 SHA-256 雜湊。

sha3(…) returns (bytes32): 等價於 keccak256。

ripemd160(…) returns (bytes20): 計算 (tightly packed) arguments 的 RIPEMD-160 雜湊。

ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address) : 利用橢圓曲線簽名恢復與公鑰相關的地址,錯誤返回零值。(example usage:https://ethereum.stackexchange.com/q/1777/222)

上文中的“tightly packed”是指不會對引數值進行 padding 處理(就是說所有引數值的位元組碼是連續存放的,譯者注),這意味著下邊這些呼叫都是等價的:

keccak256(“ab”, “c”) keccak256(“abc”) keccak256(0x616263) keccak256(6382179) keccak256(97, 98, 99)

如果需要 padding,可以使用顯式型別轉換:keccak256(“\x00\x12”) 和 keccak256(uint16(0x12)) 是一樣的。

請注意,常量值會使用儲存它們所需要的最少位元組數進行打包。例如:keccak256(0) == keccak256(uint8(0)),keccak256(0x12345678) == keccak256(uint32(0x12345678))。

在一個私鏈上,你很有可能碰到由於 sha256、ripemd160 或者 ecrecover 引起的 Out-of-Gas。這個原因就是他們被當做所謂的預編譯合約而執行,並且在第一次收到訊息後這些合約才真正存在(儘管合約程式碼是硬程式碼)。傳送到不存在的合約的訊息非常昂貴,所以實際的執行會導致 Out-of-Gas 錯誤。在你的合約中實際使用它們之前,給每個合約傳送一點兒以太幣,比如 1 Wei。這在官方網路或測試網路上不是問題。

8

solidity常見錯誤提示及原因分析

1、智慧合約執行失敗

告警描述: ” Warning! Error encountered during contract execution [Out of gas] ”

發生場景: 執行官網眾籌智慧合約程式碼,在給智慧合約打代幣前,往智慧合約地址賬戶打ETH,交易失敗,提示如下:

程式碼及原因分析: 下面是執行的回撥函式,其中“tokenReward.transfer(msg.sender, amount / price);”的意思就是把智慧合約的代幣打給傳送者賬號。這個賬號還沒有代幣,所以肯定會執行失敗。

function () payable {

        require(!crowdsaleClosed);

        uint amount = msg.value;

       balanceOf[msg.sender] += amount; 

       amountRaised += amount; 

       tokenReward.transfer(msg.sender, amount / price); 

       FundTransfer(msg.sender, amount, true);

    }

解決方法: 先往智慧合約賬號打代幣,然後打ETH,就不會執行失敗了。

2、 REMIX+MetaMASK的場景下,呼叫智慧合約函式,提示不可知地址

告警描述:

REMIX輸出框輸出以下告警內容:
transact to Crowdsale.checkGoalReached errored: Unknown address - unable to sign transaction for this address: “0x3d7dfb80e71096f2c4ee63c42c4d849f2cbbe363”

發生場景: 更換了Meta賬號後,呼叫之前賬號建立的智慧合約的函式

原因分析: Remix輸出框提示未知地址:

image

告警描述

檢視MetaMask的當前賬號,發現是Account 1的賬號,所以無法執行合約。

image

MetaMask的當前賬號

解決方法:

更換MetaMask為Account8(地址為0x3D7DfB80E71096F2c4Ee63C42C4D849F2CBBE363)的賬號即可正常執行。

3、GETH安裝時出現異常

告警描述: GETH安裝時出現以下告警:

E: Unable to fetch some archives, maybe run apt-get update or try with –fix-missing?

發生場景:

GETH安裝,執行以下命令出現。

sudo apt-get install ethereum

原因分析:

解決方法:

<1> 參考網上解決方案
sudo vim /etc/resolv.conf ,新增:

nameserver 8.8.8.8

然後執行:

sudo /etc/init.d/networking restart

還是不行。

<2> 從綠地金融中心搬到家裡,做相同的操作,就可以安裝成功了。
應該是綠地金融中心的路由器做了某些配置,導致下載不成功。

4、問題描述模板

告警描述:
發生場景:
原因分析:
解決方法:

9

常見問題及解答

1).modifer函式是幹什麼的?

2).如何打幣回支付賬號?

3).智慧合約的定時器和系統函式是什麼?

4).當建立一個智慧合約時,msg.sender和this的區別?

答覆:msg.sender是指外部賬戶的地址,this是指當前建立的智慧合約的地址。

contract AddrTest{ 

   event LogContractAddress(address exAccount, address contractAddress); 

       function AddrTest(){ 

   LogContractAddress(msg.sender,this);

    }    

}

在remix執行,可以證明這個推測

image

LOG說明

10

參考資料

1] Solidity教程式列1 - 型別介紹
2] 智慧合約語言Solidity教程系列2 - 地址型別介紹
3] 智慧合約語言 Solidity 教程系列3 - 函式型別
4] 智慧合約語言 Solidity 教程系列4 - 資料儲存位置分析
5] 智慧合約語言 Solidity 教程系列5 - 陣列介紹
6] 智慧合約語言 Solidity 教程系列6 - 結構體與對映
7] 智慧合約語言 Solidity 教程系列7 - 以太單位及時間單位
8] 智慧合約語言 Solidity 教程系列8 - Solidity API
9] 智慧合約語言 Solidity 教程系列9 - 錯誤處理
10] 智慧合約語言 Solidity 教程系列10 - 完全理解函式修改器

本文作者:HiBlock區塊鏈技術佈道群-輝哥

原文釋出於簡書

加微信baobaotalk_com,加入技術佈道群

以下是我們的社群介紹,歡迎各種合作、交流、學習:)

image