寫在前面:HiBlock區塊鏈社區成立了翻譯小組,翻譯區塊鏈相關的技術文檔及資料,本文爲Solidity文檔翻譯的第十三部分《貢獻方式及常見問題》,特發佈出來邀請solidity愛好者、開發者作公開的審校,您能夠添加微信baobaotalk_com,驗證輸入「solidity」,而後將您的意見和建議發送給咱們,也能夠在文末「留言」區留言,有效的建議咱們會採納及合併進下一版本,同時將送一份小禮物給您以示感謝。git
貢獻方式github
對於你們的幫助,咱們一如既往地感激。web
你能夠試着 從源代碼編譯 開始,以熟悉 Solidity 的組件和編譯流程。這對精通 Solidity 上智能合約的編寫也有幫助。編程
咱們特別須要如下方面的幫助:數組
改善文檔瀏覽器
回覆 StackExchange(https://ethereum.stackexchange.com/) 和 Solidity Gitter(https://gitter.im/ethereum/solidity) 上的用戶提問安全
解決並回復 Solidity's GitHub issues(https://github.com/ethereum/solidity/issues) 上的問題,特別是被標記爲 up-for-grabs(https://github.com/ethereum/solidity/issues?q=is%3Aopen+is%3Aissue+label%3Aup-for-grabs) 的問題,他們是針對外部貢獻者的入門問題。微信
請用 GitHub issues tracker(https://github.com/ethereum/solidity/issues) 來報告問題。彙報問題時,請提供下列細節:app
你所使用的 Solidity 版本框架
源碼(若是能夠的話)
你在哪一個平臺上運行代碼
如何重現該問題
該問題的結果是什麼
預期行爲是什麼樣的
將形成問題的源碼縮減到最少,老是頗有幫助的,而且有時候甚至能澄清誤解。
爲了進行貢獻,請 fork 一個 develop 分支並在那裏進行修改。除了你 作了什麼 以外,你還須要在 commit 信息中說明,你 爲何 作這些修改(除非只是個微小的改動)。
在進行了 fork 以後,若是你還須要從 develop 分支 pull 任何變動的話(例如,爲了解決潛在的合併衝突),請避免使用 git merge ,而是 git rebase 你的分支。
此外,若是你在編寫一個新功能,請確保你編寫了合適的 Boost 測試案例,並將他們放在了 test/下。
可是,若是你在進行一個更大的變動,請先與 Solidity Development Gitter channel(https://gitter.im/ethereum/solidity-dev) 進行商量(與上文提到的那個功能不一樣,這個變動側重於編譯器和編程語言開發,而不是編程語言的使用)。
新的特性和 bug 修復會被添加到 Changelog.md 文件中:使用的時候請遵循上述方式。
最後,請確保你遵照了這個項目的 編碼風格(https://raw.githubusercontent.com/ethereum/solidity/develop/CODING_STYLE.md) 。還有,雖然咱們採用了持續集成測試,可是在提交 pull request 以前,請測試你的代碼並確保它能在本地進行編譯。
感謝你的幫助!
Solidity 有不一樣類型的測試,他們包含在應用 soltest 中。其中一些須要 cpp-ethereum 客戶端運行在測試模式下,另外一些須要安裝 libz3。
soltest 會從保存在 ./test/libsolidity/syntaxTests 中的測試合約中獲取所期待的結果。爲了使 soltest 能夠找到這些測試,可使用 --testpath 命令行參數來指定測試根目錄,例如 ./build/test/soltest -- --testpath ./test。
若要禁用 z3 測試,可以使用 ./build/test/soltest -- --no-smt --testpath ./test ,若要執行不須要 cpp-ethereum 的測試子集,則用 ./build/test/soltest -- --no-ipc --testpath ./test。
對於其餘測試,你都須要安裝 cpp-ethereum ,並在測試模式下運行它:eth --test -d /tmp/testeth。
以後再執行實際的測試文件:./build/test/soltest -- --ipcpath /tmp/testeth/geth.ipc --testpath ./test。
能夠用過濾器來執行一組測試子集:soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc --testpath ./test,其中 TestName 能夠是通配符 *。
另外, scripts/test.sh 裏有一個測試腳本可執行全部測試,並自動運行 cpp-ethereum,若是它在 scripts 路徑中的話(但不會去下載它)。
Travis CI 甚至會執行一些額外的測試(包括 solc-js 和對第三方 Solidity 框架的測試),這些測試須要去編譯 Emscripten 目標代碼。
編寫和運行語法測試
就像前文提到的,語法測試存儲在單獨的合約裏。這些文件必須包含註解,爲相關的測試標註預想的結果。測試工具將編譯並基於給定的預想結果進行檢查。
例如:./test/libsolidity/syntaxTests/double_stateVariable_declaration.sol
contract test { uint256 variable; uint128 variable; } // ---- // DeclarationError: Identifier already declared.
一個語法測試必須在合約代碼以後包含跟在分隔符 ---- 以後的測試代碼。上邊例子中額外的註釋則用來描述預想的編譯錯誤或警告。若是合約不會出現編譯錯誤或警告,這部分能夠爲空。
在上邊的例子裏,狀態變量 variable 被聲明瞭兩次,這是不容許的。這會致使一個 DeclarationError 來告知標識符已經被聲明過了。
用來進行那些測試的工具叫作 isoltest,能夠在 ./test/tools/ 下找到。它是一個交互工具,容許你使用你喜歡的文本編輯器編輯失敗的合約。讓咱們把第二個 variable 的聲明去掉來使測試失敗:
contract test { uint256 variable; } // ---- // DeclarationError: Identifier already declared. 再次運行 ./test/isoltest 就會獲得一個失敗的測試: syntaxTests/double_stateVariable_declaration.sol: FAIL Contract: contract test { uint256 variable; } Expected result: DeclarationError: Identifier already declared. Obtained result: Success
這裏,在得到告終果以後打印了預想的結果,但也提供了編輯/更新/跳過當前合約或直接退出的辦法,isoltest 提供了下列測試失敗選項:
edit:isoltest 會嘗試打開先前用 isoltest --editor /path/to/editor 所指定的編輯器。若是沒設定路徑,則會產生一個運行時錯誤。若是指定了編輯器,這將打開編輯器並容許你修改合約代碼。
update:更新測試中的合約。這將會移除包含了不匹配異常的註解,或者增長缺失的預想結果。而後測試會從新開始。
skip:跳過當前測試的執行。
quit:退出 isoltest。
在上邊的狀況自動更新合約會把它變爲:
contract test { uint256 variable; } // ----
並從新運行測試。它將會經過:
Re-running test case... syntaxTests/double_stateVariable_declaration.sol: OK
Fuzzing 是一種測試技術,它能夠經過運行多少不等的隨機輸入來找出異常的執行狀態(片斷故障、異常等等)。現代的 fuzzer 已經能夠很聰明地在輸入中進行直接的查詢。 咱們有一個專門的程序叫作 solfuzzer,它能夠將源代碼做爲輸入,當發生一個內部編譯錯誤、片斷故障或者相似的錯誤時失敗,但當代碼包含錯誤的時候則不會失敗。 經過這種方法,fuzzing 工具能夠找到那些編譯級別的內部錯誤。
咱們主要使用 AFL(http://lcamtuf.coredump.cx/afl/) 來進行 fuzzing 測試。你須要手工下載和構建 AFL。而後用 AFL 做爲編譯器來構建 Solidity(或直接構建 solfuzzer):
cd build # if needed make clean cmake .. -DCMAKE_C_COMPILER=path/to/afl-gcc -DCMAKE_CXX_COMPILER=path/to/afl-g++make solfuzzer
而後,你須要一個源文件例子。這將使 fuzzer 能夠更容易地找到錯誤。你能夠從語法測試目錄下拷貝一些文件或者從文檔中提取一些測試文件或其餘測試:
mkdir /tmp/test_cases cd /tmp/test_cases # extract from tests:path/to/solidity/scripts/isolate_tests.py path/to/solidity/test/libsolidity/SolidityEndToEndTest.cpp # extract from documentation:path/to/solidity/scripts/isolate_tests.py path/to/solidity/docs docs
AFL 的文檔指出,帳冊(初始的輸入文件)不該該太大。每一個文件自己不該該超過 1 kB,而且每一個功能最多隻能有一個輸入文件;因此最好從少許的輸入文件開始。 此外還有一個叫作 afl-cmin 的工具,能夠將輸入文件整理爲能夠具備近似行爲的二進制代碼。
如今運行 fuzzer(-m 參數將使用的內存大小擴展爲 60 MB):
afl-fuzz -m 60 -i /tmp/test_cases -o /tmp/fuzzer_reports -- /path/to/solfuzzer
fuzzer 會將致使失敗的源文件建立在 /tmp/fuzzer_reports 中。一般它會找到產生類似錯誤的相似的源文件。 你可使用 scripts/uniqueErrors.sh 工具來過濾重複的錯誤。
Whiskers 是一個相似於 Mustache(https://mustache.github.io/) 的模板系統。編譯器在各類各樣的地方使用 Whiskers 來加強可讀性,從而提升代碼的可維護性和可驗證性。
它的語法與 Mustache 有很大差異:模板標記 {{ 和 }} 被替換成了 < 和 > ,以便加強語法分析,避免與 內聯彙編 的衝突(符號 < 和 > 在內聯彙編中是無效的,而 { 和 } 則被用來限定塊)。另外一個侷限是,列表只會被解析一層,而不是遞歸解析。將來可能會改變這一個限制。
下面是一個粗略的說明:
任何出現 <name> 的地方都會被所提供的變量 name 的字符串值所替換,既不會進行任何轉義也不會迭代替換。能夠經過 <#name>...</name> 來限定一個區域。該區域中的內容將進行屢次拼接,每次拼接會使用相應變量集中的值替換區域中的 <inner> 項,模板系統中提供了多少組變量集,就會進行多少次拼接。頂層變量也能夠在這樣的區域的內部使用。
譯者注:對於區域<#name>...</name>的釋義,譯者參考自:https://github.com/janl/mustache.js#sections
常見問題
這份清單最先是由 fivedogit 收集整理的。
能夠在特定的區塊上進行操做嗎?(好比發佈一個合約或執行一筆交易)
鑑於交易數據的寫入是由礦工決定的而不是由提交者決定的,誰也沒法保證交易必定會發生在下一個或將來某一個特定的區塊上。這個結論適用於函數調用/交易以及合約的建立。
若是你但願你的合約被定時調用,可使用:alarm clock(https://www.ethereum-alarm-clock.com/)。
什麼是交易的「有效載荷(payload)」?
就是隨交易一塊兒發送的字節碼「數據」。
存在反編譯器嗎?
除了 Porosity 有點接近以外,Solidity 沒有嚴格意義上的反編譯器。因爲諸如變量名、註釋、代碼格式等會在編譯過程當中丟失,因此徹底反編譯回源代碼是沒有可能的。
不少區塊鏈瀏覽器都能將字節碼分解爲一系列操做碼。
若是區塊鏈上的合約會被第三方使用,那麼最好將源代碼一塊兒進行發佈。
建立一個能夠被停止並退款的合約
首先,須要提醒一下:停止合約聽起來是一個好主意,把垃圾打掃乾淨是個好習慣,但如上所述,合約是不會被真正清理乾淨的。甚至,被髮送至已移除合約的以太幣,會今後丟失。
若是想讓合約再也不可用,建議的作法是修改合約內部狀態來使其 失效 ,讓全部函數調用都變爲無效返回。這樣就沒法使用這份合約了,並且發送過去的以太幣也會被自動退回。
如今正式回答這個問題:在構造函數中,將 creator 賦值爲 msg.sender ,並保存。而後調用 selfdestruct(creator); 來停止程序並進行退款。
例子(https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/05_greeter.sol)
須要注意的是,若是你已經在合約頂部作了引用 import "mortal" 而且聲明瞭 contract SomeContract is mortal { ... ,而後再在已存在此合約的編譯器中進行編譯(包含 Remix),那麼 kill() 就會自動執行。當一份合約被聲明爲 mortal 時,你能夠仿照個人例子,使用 contractname.kill.sendTransaction({from:eth.coinbase}) 來停止它。
調用 Solidity 方法能夠返回一個數組或字符串(string)嗎?
能夠。參考 array_receiver_and_returner.sol 。
可是,在 Solidity內部 調用一個函數並返回變長數據(例如 uint[] 這種變長數組)時,每每會出現問題。這是 以太坊虛擬機Ethereum Virtual Machine(EVM) 自身的限制,咱們已經計劃在下一次協議升級時解決這個問題。
將變長數據做爲外部交易或調用的一部分返回是沒問題的。
數組可使用 in-line 的方式(指在聲明變量的同一個語句中)來初始化嗎?好比: string[] myarray = ["a", "b"];
能夠。然而須要注意的是,這方法如今只能用於定長 內存memory 數組。你甚至能夠在返回語句中用 in-line 的方式新建一個 內存memory 數組。聽起來很酷,對吧!
例子:
pragma solidity ^0.4.16; contract C { function f() public pure returns (uint8[5]) { string[4] memory adaArr = ["This", "is", "an", "array"]; return ([1, 2, 3, 4, 5]); } }
合約的函數能夠返回結構(struct)嗎?
能夠,但只適用於內部(internal)函數調用。
我從一個返回的枚舉類型(enum)中,使用 web3.js 只獲得了整數值。我該如何獲取具名數值?
雖然 Solidity 支持枚舉類型,但 ABI(應用程序二進制接口)並不支持。當前階段你須要本身去作映射,未來咱們可能會提供一些幫助。
可使用 in-line 的方式來初始化狀態變量嗎?
能夠,全部類型均可以(甚至包括結構)。然而須要注意的是,在數組使用這個方法的時候須要將其定義爲靜態 內存memory 數組。
例子:
pragma solidity ^0.4.0; contract C { struct S { uint a; uint b; } S public x = S(1, 2); string name = "Ada"; string[4] adaArr = ["This", "is", "an", "array"]; } contract D { C c = new C(); }
結構(structs)如何使用?
參考 struct_and_for_loop_tester.sol 。
循環(for loops)如何使用?
和 JavaScript 很是相像。但有一點須要注意:
若是你使用 for (var i = 0; i < a.length; i ++) { a[i] = i; } ,那麼 i 的數據類型將會是 uint8,須要從 0 開始計數。也就是說,若是 a 有超過 255 個元素,那麼循環就沒法停止,由於 i 最大隻能變爲 255。
最好使用 for (uint i = 0; i < a.length...
參考 struct_and_for_loop_tester.sol。
有沒有一些簡單的操做字符串的例子(substring,indexOf,charAt 等)?
這裏有一些字符串相關的功能性函數 stringUtils.sol ,而且會在未來做擴展。另外,Arachnid 有寫過 solidity-stringutils。
當前,若是你想修改一個字符串(甚至你只是想獲取其長度),首先都必須將其轉化爲一個 bytes
pragma solidity ^0.4.0; contract C { string s; function append(byte c) public { bytes(s).push(c); } function set(uint i, byte c) public { bytes(s)[i] = c; } }
我能拼接兩個字符串嗎?
目前只能經過手工實現。
爲何你們都選擇將合約實例化成一個變量(ContractB b;),而後去執行變量的函數(b.doSomething();),而不是直接調用這個 低級函數low-level function .call() ?
若是你調用實際的成員函數,編譯器會提示諸如參數類型不匹配的問題,若是函數不存在或者不可見,他也會自動幫你打包參數。
參考 ping.sol 和 pong.sol 。
沒被使用的 gas 會被自動退回嗎?
是的,立刻會退回。也就是說,做爲交易的一部分,在交易完成的同時完成退款。
當返回一個值的時候,好比說 uint 類型的值, 能夠返回一個 undefined 或者相似 null 的值嗎?
這不可能,由於全部的數據類型已經覆蓋了所有的取值範圍。
替代方案是能夠在錯誤時拋出(throw),這一樣能復原整個交易,當你遇到意外狀況時不失爲一個好的選擇。
若是你不想拋出,也能夠返回一對(a pair)值
pragma solidity >0.4.23 <0.5.0; contract C { uint[] counters; function getCounter(uint index) public view returns (uint counter, bool error) { if (index >= counters.length) return (0, true); else return (counters[index], false); } function checkCounter(uint index) public view { (uint counter, bool error) = getCounter(index); if (error) { // ... } else { // ... } } }
註釋會被包含在已部署的合約裏嗎,並且會增長部署的 gas 嗎?
不會,全部執行時非必須的內容都會在編譯的時候被移除。 其中就包括註釋、變量名和類型名。
若是在調用合約的函數時一塊兒發送了以太幣,將會發生什麼?
就像在建立合約時發送以太幣同樣,會累加到合約的餘額總數上。 你只能夠將以太幣一塊兒發送至擁有 payable 修飾符的函數,否則會拋出異常。
合約對合約的交易能夠得到交易回執嗎?
不能,合約對合約的函數調用並不會建立前者本身的交易,你必需要去查看所有的交易。這也是爲何不少區塊瀏覽器沒法正確顯示合約對合約發送的以太幣。
關鍵字 memory 是什麼?是用來作什麼的?
以太坊虛擬機Ethereum Virtual Machine(EVM) 擁有三類存儲區域。
第一類是 存儲storage,貯存了合約聲明中全部的變量。 虛擬機會爲每份合約分別劃出一片獨立的 存儲storage 區域,並在函數相互調用時持久存在,因此其使用開銷很是大。
第二類是 內存memory,用於暫存數據。其中存儲的內容會在函數被調用(包括外部函數)時擦除,因此其使用開銷相對較小。
第三類是棧,用於存放小型的局部變量。使用幾乎是免費的,但容量有限。
對絕大部分數據類型來講,因爲每次被使用時都會被複制,因此你沒法指定將其存儲在哪裏。
在數據類型中,對所謂存儲地點比較重視的是結構和數組。 若是你在函數調用中傳遞了這類變量,假設它們的數據能夠被貯存在 存儲storage 或 內存memory 中,那麼它們將不會被複制。也就是說,當你在被調用函數中修改了它們的內容,這些修改對調用者也是可見的。
不一樣數據類型的變量會有各自默認的存儲地點:
狀態變量老是會貯存在 存儲storage中
函數參數默認存放在內存memory中
結構、數組或映射類型的局部變量,默認會放在存儲storage中
除結構、數組及映射類型以外的局部變量,會儲存在棧中
例子:
pragma solidity ^0.4.0; contract C { uint[] data1; uint[] data2; function appendOne() public { append(data1); } function appendTwo() public { append(data2); } function append(uint[] storage d) internal { d.push(1); } }
函數 append 能一塊兒做用於 data1 和 data2,而且修改是永久保存的。若是你移除了 storage 關鍵字,函數的參數會默認存儲於 memory。這帶來的影響是,在 append(data1) 或 append(data2)被調用的時候,一份全新的狀態變量的拷貝會在 內存memory 中被建立,append 操做的會是這份拷貝(也不支持 .push ——但這又是另外一個話題了)。針對這份全新的拷貝的修改,不會反過來影響 data1 或 data2。
一個常見誤區就是聲明瞭一個局部變量,就認爲它會建立在 內存memory 中,其實它會被建立在 存儲storage 中:
/// 這份合約包含一處錯誤 pragma solidity ^0.4.0; contract C { uint someVariable; uint[] data; function f() public { uint[] x; x.push(2); data = x; } }
局部變量 x 的數據類型是 uint[] storage,但因爲 存儲storage 不是動態分配的,它須要在使用前經過狀態變量賦值。因此 x 自己不會被分配 存儲storage 的空間,取而代之的是,它只是做爲 存儲storage 中已有變量的別名。
實際上會發生的是,編譯器將 x 解析爲一個 存儲storage 指針,並默認將指針指向 存儲插槽storage slot 0 。這就形成 someVariable (貯存在 存儲插槽storage slot 0)會被 x.push(2) 更改。(在本例中,兩個合約變量 someVariable 和 data 會被預先分配到兩個 存儲插槽storage slot 中,即 存儲插槽storage slot 0 和 存儲插槽storage slot 1 。上面的程序會使局部變量 x 變成指向保存了變量 someVariable 的 存儲插槽storage slot 0 的指針。譯者注。)
正確的方法以下:
pragma solidity ^0.4.0; contract C { uint someVariable; uint[] data; function f() public { uint[] x = data; x.push(2); } }
怎樣才能在合約中獲取一個隨機數?(實施一份自動回款的博彩合約)
作好隨機這件事情,每每是一個加密項目最關鍵的部分,大部分的失敗都來自於使用了低劣的隨機數發生器。
若是你不考慮安全性,能夠作一個相似於 coin flipper 的東西,反之,最好調用一份能夠提供隨機性的合約,好比 RANDAO 。
從另外一份合約中的 non-constant 函數獲取返回值
關鍵點是調用者(合約)須要瞭解將被調用的函數。
參考 ping.sol 和 pong.sol 。
讓合約在首次被挖出時就開始作些事情
使用構造函數。在構造函數中寫的任何內容都會在首次被挖出時執行。
參考 replicator.sol 。
怎樣才能建立二維數組?
參考 2D_array.sol 。
須要注意的是,用 uint8 類型的數據填滿一個 10x10 的方陣,再加上合約建立,總共須要花費超過 800,000 的 gas。若是是 17x17 須要 2,000,000 的 gas。然而交易的 gas 上限是 314 萬。。。好吧,其實你也玩不了太大的花樣。
注意,「建立」數組純粹是免費的,成本在於填充數組。
還需注意,優化 存儲storage 訪問能夠大大下降 gas 的花費,由於一個 存儲插槽storage slot 能夠存放下 32 個 uint8類型的值。但這類優化目前也存在一些問題:在跨循環的時候不起做用;以及在邊界檢查時候會出問題。固然,在將來這種狀況會獲得改觀。
當咱們複製一個結構(struct)時, 結構 (struct)中定義的映射會被怎麼處理?
這是一個很是有意思的問題。假設咱們有一份合約,裏面的字段設置以下:
struct User { mapping(string => string) comments; } function somefunction public { User user1; user1.comments["Hello"] = "World"; User user2 = user1; }
在這種狀況下,因爲缺失「被映射的鍵列表」,被複制至 userList 的結構中的映射會被忽視。所以,系統沒法找出什麼值能夠被複制過去。
我應該如何初始化一份只包含指定數量 wei 的合約?
目前實現方式不是太優雅,固然暫時也沒有更好的方法。 就拿 合約A 調用一個 合約B 的新實例來講,new B 周圍必需要加括號,否則 B.value 會被認做是 B 的一個成員函數,叫作 value。 你必須確保兩份合約都知道對方的存在,而且 合約B 擁有 payable 構造函數。
就是這個例子:
pragma solidity ^0.4.0; contract B { function B() public payable {} } contract A { address child; function test() public { child = (new B).value(10)(); //construct a new B with 10 wei } }
合約的函數能夠接收二維數組嗎?
二維數組還沒法使用於外部調用和動態數組——你只能使用一維的動態數組。
bytes32 和 string 有什麼關係嗎?爲何 bytes32 somevar = "stringliteral"; 能夠生效,還有保存下來的那個 32 字節的 16 進制數值有什麼含義嗎?
數據類型 bytes32 能夠存放 32 個(原始)字節。在給變量分配值的過程當中 bytes32 samevar = "stringliteral";, 字符串已經被逐字翻譯成了原始字節。若是你去檢查 somevar ,會發現一個 32 字節的 16 進制數值,這就是用 16 進製表示的 字符串的文字 。
數據類型 bytes 與此相似,只是它的長度能夠改變。
最終來看,假設 bytes 儲存的是字符串的 UTF-8 編碼,那麼它和 string 基本是等同的。因爲 string 存儲storage 的是 UTF-8 編碼格式的數據,因此計算字符串中字符數量的成本是很高的(某些字符的編碼甚至大於一個字節)。所以,系統還不支持 string s; s.length ,甚至不能經過索引訪問 s[2] 。但若是你想訪問字符串的下級字節編碼,可使用 bytes(s).length 和 bytes(s)[2],它們分別會返回字符串在 UTF-8 編碼下的字節數量(不是字符數量)以及字符串 UTF-8 編碼的第二個字節(不是字符)。
一份合約能夠傳遞一個數組(固定長度)或者一個字符串或者一個 bytes (不定長度)給另外一份合約嗎?
固然能夠。但若是不當心跨越了 內存memory / 存儲storage 的邊界,一份獨立的拷貝就會被建立出來:
pragma solidity ^0.4.16; contract C { uint[20] x; function f() public { g(x); h(x); } function g(uint[20] y) internal pure { y[2] = 3; } function h(uint[20] storage y) internal { y[3] = 4; } }
因爲會在 內存memory 中對 存儲storage 的值建立一份獨立的拷貝(默認存儲在 內存memory 中),因此對 g(x) 的調用其實並不會對 x 產生影響。另外一方面,因爲傳遞的只是引用而不是一個拷貝, h(x) 得以成功地修改了 x。
有些時候,當我想用相似這樣的表達式: arrayname.length = 7; 來修改數組長度,卻會獲得一個編譯錯誤 Value must be an lvalue。這是爲何?
你可使用 arrayname.length = <some new length>; 來調整 存儲storage 中的動態數組(也就是在合約級別聲明的數組)的長度。若是你獲得一個 lvalue 錯誤,那麼你有可能作錯了如下兩件事中的一件或所有。
你在嘗試修改長度的數組多是保存在 內存memory中的,或者
你可能在嘗試修改一個非動態數組的長度。
// 這將沒法編譯經過 pragma solidity ^0.4.18; contract C { int8[] dynamicStorageArray; int8[5] fixedStorageArray; function f() { int8[] memory memArr; // 第一種狀況 memArr.length++; // 非法 int8[5] storage storageArr = fixedStorageArray; // 第二種狀況 storageArr.length++; // 非法 int8[] storage storageArr2 = dynamicStorageArray; storageArr2.length++; // 非法 } }
**重要提醒: **在 Solidity 中,數組維數的聲明方向是和在 C 或 Java 中的聲明方向相反的,但訪問方式相同。
舉個例子,int8[][5] somearray; 是5個 int8 格式的動態數組。
這麼作的緣由是,T[5] 老是能被識別爲5個 T 的數組,哪怕 T 自己就是一個數組(而在 C 或 Java 是不同的)。
Solidity 的函數能夠返回一個字符串數組嗎(string[])?
暫時還不能夠,由於這要求兩個維度都是動態數組(string 自己就是一種動態數組)。
若是你發起了一次獲取數組的調用,有可能得到整個數組嗎?仍是說另外須要寫一個輔助函數來實現?
一個數組類型的公共狀態變量會有一個自動的獲取函數 getter function , 這個函數只會返回單個元素。若是你想獲取完整的數組,那麼只能再手工寫一個函數來實現。
若是某個帳戶只存儲了值但沒有任何代碼,將會發生什麼?例子: http://test.ether.camp/account/5f740b3a43fbb99724ce93a879805f4dc89178b5
構造函數作的最後一件事情是返回合約的代碼。這件事消耗的 gas 取決於代碼的長度,其中有種可能的狀況是提供的 gas 不夠。這是惟一的一種狀況下,出現了 「out of gas」 異常卻不會去復原改變了的狀態,這個改變在這裏就是對狀態變量的初始化。
https://github.com/ethereum/wiki/wiki/Subtleties
當 CREATE 操做的某個階段被成功執行,若是這個操做返回 x,那麼 5 * len(x) 的 gas 在合約被建立前會從剩餘 gas 中被扣除。若是剩餘的 gas 少於 5 * len(x),那麼就不進行 gas 扣除,而是把建立的合約代碼改變成空字符串,但這時候並不認爲是發生了異常——不會發生復原。
在定製 通證token 的合約中,下面這些奇怪的校驗是作什麼的?
require((balanceOf[_to] + _value) >= balanceOf[_to]);
在Solidity(以及大多數其餘機器相關的編程語言)中的整型都會被限定在必定範圍內。 好比 uint256 ,就是從 0 到 2**256 - 1 。若是針對這些數字進行操做的結果不在這個範圍內,那麼就會被截斷。這些截斷會帶來 嚴重的後果 ,因此像上面這樣的代碼須要考慮避免此類攻擊。
更多問題?
若是你有其餘問題,或者你的問題在這裏找不到答案,請在此聯繫咱們 gitter(https://gitter.im/ethereum/solidity) 或者提交一個 issue(https://github.com/ethereum/solidity/issues)。
延伸閱讀:智能合約-Solidity官方文檔(1)
根據例子學習Solidity-Solidity官方文檔(3)
深刻理解Solidity之源文件及合約結構——Solidity中文文檔(4)
應用二進制接口(ABI) 說明——Solidity中文文檔(7)
點擊「閱讀原文」便可查看完整中文文檔
注:本文爲solidity翻譯的第十三部分《貢獻方式及常見問題》,特發佈出來邀請solidity愛好者、開發者作公開的審校,您能夠添加微信baobaotalk_com,驗證輸入「solidity」,而後將您的意見和建議發送給咱們,也可在文末「留言」區留言,或經過原文連接訪問咱們的Github。有效的建議咱們會收納並及時改進,同時將送一份小禮物給您以示感謝。
本文內容來源於HiBlock區塊鏈社區翻譯小組,感謝全體譯者的辛苦工做。點擊「閱讀原文」便可查看完整中文文檔。
線上課程推薦 --
線上課程:《8小時區塊鏈智能合約開發實踐》
培訓講師:《白話區塊鏈》做者 蔣勇
課程原價:999元,現價 399元
更多福利:
點擊「閱讀原文」便可查看完整中文文檔