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


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


目前嘗試 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 編譯器。

2. REMIN的函數引用

function mint(address receiver, uint amount)

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

例如是mint("0xca35b7d915458ef540ade6068dfe2f44e8fa733c",101)

(2) 程序中,如果註釋包含中文,單步調試的位置不準確。

3.address相關全局函數

<address>.balance (uint256):/<address>

該地址有多少以太坊餘額(wei為單位)

<address>.transfer(uint256 amount):/<address>

發送特定數量(wei為單位)的以太坊到對應地址,當出現錯誤時會扔出異常,但不會因異常而停止。固定需要耗費2300個gas。

<address>.send(uint256 amount) returns (bool):/<address>

發送特定數量(wei為單位)的以太坊到對應地址,當出現錯誤時會返回flase。固定需要耗費2300個gas。

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

<address>.call(...) returns (bool):/<address>

CALL的低級調用函數,當失敗時返回false。執行需要消耗不固定的gas。

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

<address>.callcode(...) returns (bool):/<address>

CALLCODE的低級調用函數,當失敗時返回false。執行需要消耗不固定的gas。

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

<address>.delegatecall(...) returns (bool):/<address>

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)

上文中的“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 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] 是從右邊減少了一級)。

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

註解

如果想要訪問以字節表示的字符串 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輸出框提示未知地址:


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


告警描述

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


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


MetaMask的當前賬號

解決方法:

更換MetaMask為Account8(地址為0x3D7DfB80E71096F2c4Ee63C42C4D849F2CBBE363)的賬號即可正常運行。3). GETH安裝時出現異常

告警描述:

GETH安裝時出現以下告警:

E: Failed to fetchhttp://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運行,可以證明這個推測


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


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 - 完全理解函數修改器


分享到:


相關文章: