第十三課 SOLIDITY語法難點解析及故障排查

1.編輯器說明

(1)推薦編輯器 目前嘗試 Solidity 編程的最好的方式是使用 Remix (須要時間加載,請耐心等待)。Remix 是一個基於 Web 的 IDE,它可讓你編寫 Solidity 智能合約,而後部署並運行該智能合約。 若是外網不能訪問,能夠訪問歐陽哥哥搭建的REMIX編輯器 (2)Visual Studio Extension Microsoft Visual Studio 的 Solidity 插件,包含 Solidity 編譯器。 (3)Visual Studio Code extension Microsoft Visual Studio Code 插件,包含語法高亮和 Solidity 編譯器。html

2. REMIN的函數引用

function mint(address receiver, uint amount)web

(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會更好。json

.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); }ubuntu

測試地址和地址調用代碼舉例vim

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的別名函數visual-studio-code

#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 函數,而若是他們沒收到錢,還能夠用一個不一樣的哈希從新嘗試。 當前區塊的時間戳必須嚴格大於最後一個區塊的時間戳,但這裏惟一能確保的只是它會是在權威鏈上的兩個連續區塊的時間戳之間的數值。bash

註解 基於可擴展因素,區塊哈希不是對全部區塊都有效。你僅僅能夠訪問最近 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)

上文中的「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))。

在一個私鏈上,你頗有可能碰到因爲 sha25六、ripemd160 或者 ecrecover 引發的 Out-of-Gas。這個緣由就是他們被當作所謂的預編譯合約而執行,而且在第一次收到消息後這些合約才真正存在(儘管合約代碼是硬代碼)。發送到不存在的合約的消息很是昂貴,因此實際的執行會致使 Out-of-Gas 錯誤。在你的合約中實際使用它們以前,給每一個合約發送一點兒以太幣,好比 1 Wei。這在官方網絡或測試網絡上不是問題。

8 Using for 如何使用

using A for B,這裏A一般是某個library裏面定義的某個方法,B是某種數據類型,這句話是把A方法綁定到B類型上,至關於給B類型附加了一個A方法。(也有翻譯爲附着庫的) 在上面的例子中,將LibContract裏定義的方法綁定到全部的數據類型。可是通常咱們不會在全部的類型實例上都去調用LibContract的方法,應該是要按需using的,這裏偷懶就寫*。 在通俗一點的例子就是, 好比 using LibInt for uint,而後LibInt裏面有定義一個toString方法。咱們有一個uint a;那麼能夠這樣調用a.toString(),toString方法在定義的時候,第一個參數會是一個uint類型的變量,表示調用者。

using A for B,A的函數的第一個參數必須和B的數據類型一致。
複製代碼

還有這個方法是能夠重載的,你能夠定義好幾個同名的方法,可是第一個參數的類型不一樣,調用的時候自動的根據調用類型選擇某一種方法。

#9 數組

數組能夠在聲明時指定長度,也能夠動態調整大小。 對於 "存儲"的數組來講,元素類型能夠是任意的(即元素也能夠是數組類型,映射類型或者結構體)。 對於 "memory"的數組來講,元素類型不能是映射類型,若是做爲 public 函數的參數,它只能是 ABI 類型。

一個元素類型爲 T,固定長度爲 k 的數組能夠聲明爲 T[k],而動態數組聲明爲 T[]。 舉個例子,一個長度爲 5,元素類型爲 uint 的動態數組的數組,應聲明爲 uint[][5] (注意這裏跟其它語言比,數組長度的聲明位置是反的)。 要訪問第三個動態數組的第二個元素,你應該使用 x[2][1](數組下標是從 0 開始的,且訪問數組時的下標順序與聲明時相反,也就是說,x[2] 是從右邊減小了一級)。

bytesstring 類型的變量是特殊的數組。 bytes 相似於 byte[],但它在 calldata 中會被「緊打包」(譯者注:將元素連續地存在一塊兒,不會按每 32 字節一單元的方式來存放)。 stringbytes 相同,但(暫時)不容許用長度或索引來訪問。

註解 若是想要訪問以字節表示的字符串 s,請使用 bytes(s).length / bytes(s)[7] = 'x';。 注意這時你訪問的是 UTF-8 形式的低級 bytes 類型,而不是單個的字符。

能夠將數組標識爲 public,從而讓 Solidity 建立一個 getter。 以後必須使用數字下標做爲參數來訪問 getter。

建立內存數組

可以使用 new 關鍵字在內存中建立變長數組。 與"存儲"(storage)數組相反的是,你不能經過修改爲員變量 .length 改變 "內存"(memory)數組的大小。

contract C {
    function f(uint len) public pure {
        uint[] memory a = new uint[](7);
        bytes memory b = new bytes(len);
        // 這裏咱們有 a.length == 7 以及 b.length == len
        a[6] = 8;
    }
}
複製代碼

數組字面常數 / 內聯數組

數組字面常數是寫做表達式形式的數組,而且不會當即賦值給變量。

contract C {
    function f() public pure {
        g([uint(1), 2, 3]);
    }
    function g(uint[3] _data) public pure {
        // ...
    }
}
複製代碼

數組字面常數是一種定長的 "內存"(memory) 數組類型,它的基礎類型由其中元素的普通類型決定。 例如,[1, 2, 3] 的類型是 uint8[3] memory,由於其中的每一個字面常數的類型都是 uint8。 正由於如此,有必要將上面這個例子中的第一個元素轉換成 uint 類型。 目前須要注意的是,定長的 "內存"(memory) 數組並不能賦值給變長的 "內存"(memory) 數組,下面是個反例:

// 這段代碼並不能編譯。

pragma solidity ^0.4.0;

contract C {
    function f() public {
        // 這一行引起了一個類型錯誤,由於 unint[3] memory
        // 不能轉換成 uint[] memory。
        uint[] x = [uint(1), 3, 4];
    }
}
複製代碼

已經計劃在將來移除這樣的限制,但目前數組在 ABI 中傳遞的問題形成了一些麻煩。

成員

length: 數組有 length 成員變量表示當前數組的長度。 動態數組能夠在 存儲storage (而不是 內存memory )中經過改變成員變量 .length 改變數組大小。 並不能經過訪問超出當前數組長度的方式實現自動擴展數組的長度。 一經建立,內存memory 數組的大小就是固定的(但倒是動態的,也就是說,它依賴於運行時的參數)。

push: 變長的 存儲storage 數組以及 bytes 類型(而不是 string 類型)都有一個叫作 push 的成員函數,它用來附加新的元素到數組末尾。 這個函數將返回新的數組長度。

警告 在外部函數中目前還不能使用多維數組。

警告 因爲 以太坊虛擬機Ethereum Virtual Machine(EVM) 的限制,不能經過外部函數調用返回動態的內容。 例如,若是經過 web3.js 調用 contract C { function f() returns (uint[]) { ... } } 中的 f 函數,它會返回一些內容,但經過 Solidity 不能夠。 目前惟一的變通方法是使用大型的靜態數組。

contract ArrayContract {
    uint[2**20] m_aLotOfIntegers;
    // 注意下面的代碼並非一對動態數組,
    // 而是一個數組元素爲一對變量的動態數組(也就是數組元素爲長度爲 2 的定長數組的動態數組)。
    bool[2][] m_pairsOfFlags;
    // newPairs 存儲在 memory 中 —— 函數參數默認的存儲位置

    function setAllFlagPairs(bool[2][] newPairs) public {
        // 向一個 storage 的數組賦值會替代整個數組
        m_pairsOfFlags = newPairs;
    }

    function setFlagPair(uint index, bool flagA, bool flagB) public {
        // 訪問一個不存在的數組下標會引起一個異常
        m_pairsOfFlags[index][0] = flagA;
        m_pairsOfFlags[index][1] = flagB;
    }

    function changeFlagArraySize(uint newSize) public {
        // 若是 newSize 更小,那麼超出的元素會被清除
        m_pairsOfFlags.length = newSize;
    }

    function clear() public {
        // 這些代碼會將數組所有清空
        delete m_pairsOfFlags;
        delete m_aLotOfIntegers;
        // 這裏也是實現一樣的功能
        m_pairsOfFlags.length = 0;
    }

    bytes m_byteData;

    function byteArrays(bytes data) public {
        // 字節的數組(語言意義中的 byte 的複數 ``bytes``)不同,由於它們不是填充式存儲的,
        // 但能夠看成和 "uint8[]" 同樣對待
        m_byteData = data;
        m_byteData.length += 7;
        m_byteData[3] = byte(8);
        delete m_byteData[2];
    }

    function addFlag(bool[2] flag) public returns (uint) {
        return m_pairsOfFlags.push(flag);
    }

    function createMemoryArray(uint size) public pure returns (bytes) {
        // 使用 `new` 建立動態 memory 數組:
        uint[2][] memory arrayOfPairs = new uint[2][](size);
        // 建立一個動態字節數組:
        bytes memory b = new bytes(200);
        for (uint i = 0; i < b.length; i++)
            b[i] = byte(i);
        return b;
    }
}
複製代碼

18. 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輸出框提示未知地址:

告警描述
查看MetaMask的當前帳號,發現是Account 1的帳號,因此沒法執行合約。
MetaMask的當前帳號
解決方法: 更換MetaMask爲Account8(地址爲0x3D7DfB80E71096F2c4Ee63C42C4D849F2CBBE363)的帳號便可正常運行。

3). GETH安裝時出現異常

告警描述: GETH安裝時出現如下告警: E: Failed to fetch http://101.110.118.22/ppa.launchpad.net/ethereum/ethereum/ubuntu/pool/main/e/ethereum/ethereum_1.8.10+build13740+artful_i386.deb Could not connect to 101.110.118.22:80 (101.110.118.22), connection timed out 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). 問題描述模板

告警描述:

DocstringParsingError: Documented parameter "owner" not found in the parameter list of the function.
複製代碼

發生場景: remix編譯如下智能合約函數時出現:

/** 
     * @dev Withdraw the token remained to the constructor address.
     * @param owner Address of owner.
     */
    function withdrawToken() public onlyCreator{
        if( 0 < token.balanceOf(address(this))) {
           token.transfer(creator, token.balanceOf(address(this)));
        }
    }    

複製代碼

緣由分析: remix居然要檢測@param後面跟的參數,看來註釋不能隨便亂寫了。 解決方法: 函數修改成如下的就能編譯經過了。

/** 
     * @dev Withdraw the token remained to the constructor address.
     */
    function withdrawToken() public onlyCreator{
        if( 0 < token.balanceOf(address(this))) {
           token.transfer(creator, token.balanceOf(address(this)));
        }
    } 
複製代碼

2). 問題描述模板

告警描述: 發生場景: 緣由分析: 解決方法:

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運行,能夠證實這個推測

LOG說明

10 View Functions,Pure Functions,Fallback Function的定義?

答案:點擊參考官網文檔,尚未時間翻譯過來。

11. 文檔參考

1,官方中文網站 http://solidity-cn.readthedocs.io/zh/develop/

2, tiny熊翻譯系列 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 - 徹底理解函數修改器

相關文章
相關標籤/搜索