在這裏使用cryptoPunks爲實例來進行solidity的介紹,通常這些內容理解了就可以進行相對簡單的智能合約的編寫了,同時會添加一些我認爲也十分重要的內容
學習文檔爲http://solidity-cn.readthedocs.io/zh/develop/layout-of-source-files.html
()pragma solidity ^0.4.0;
這樣,意味着源文件將既不容許低於 0.4.0 版本的編譯器編譯, 也不容許高於(包含) 0.5.0 版本的編譯器編譯(第二個條件因使用 ^ 被添加)
還有pragma solidity >0.4.23 <0.5.0;
()聲明狀態變量
string public name;
通常我都會顯示地將他們定義爲public,雖然你不這麼定義,調用合約時也能調用,可是用remix-ide的就知道區別,當你在run中deploy後,出現的變量按鈕都是你聲明爲public
()變量聲明後將有默認初始值,其初始值字節表示所有爲零。任何類型變量的「默認值」是其對應類型的典型「零狀態」,因此並不須要本身賦值,聲明便可
例如, bool 類型的默認值是 false 。 uint 或 int 類型的默認值是 0 。對於靜態大小的數組和 bytes1 到 bytes32 ,每一個單獨的元素將被初始化爲與其類型相對應的默認值。 最後,對於動態大小的數組, bytes 和 string 類型,其默認缺省值是一個空數組或字符串。
因此其實代碼中mapping (uint => Bid) public punkBids的punkBids被聲明爲{false,0,0x0,0}
()映射
mapping (address => uint256) public balanceOf;
當你想要將兩個變量設定必定關係時,如該例子,想要經過帳戶的地址來查出帳戶擁有的token數量
也能夠寫得複雜一點,好比:
mapping (address => mapping (address => uint256)) public allowance;
訪問就是allowance[‘’][‘’]
()結構類型¶
結構是能夠將幾個變量分組的自定義類型
1》structure結構體
struct Bid {
bool hasBid;//是否正在競標
uint punkIndex;
address bidder;
uint value;
}
這樣你能夠將一組相關的信息寫在一塊兒,而後再結合語句:
mapping (uint => Bid) public punkBids;
這樣你就能夠經過punkBids[5]的映射方式去獲取結構體Bid的信息了
2》enum枚舉類型
枚舉可用來建立由必定數量的「常量值」構成的自定義類型
舉例:enum Gender {Male,Female}
Male = 0 , Female = 1
訪問枚舉方式 Gender.Male 實際等於數字 0
性別枚舉的值就有男和女,因此當你聲明一個枚舉類型的變量的時候,它的值要麼是男,要麼是女這樣,枚舉下標定義從左至右從零開始css
()event
用於事件監聽,好比
event PunkTransfer(address indexed from, address indexed to, uint256 punkIndex);//聲明
function transferPunk(address to, uint punkIndex) {
…
emit PunkTransfer(msg.sender, to, punkIndex); //監聽事件
…
}
該event的做用就是你可使用instance.PunkTransfer(filter,callback)函數,經過設置過濾條件filter,如{from:web3.eth.accounts[0]}來在調用transferPunk的同時監聽該函數的特定條件的交易是否成功及其返回內容。
監聽時的emit要加上,沒有回警告
() if (msg.sender != owner) throw;
這是在合約中咱們常見到的條件判斷語句,可是如今用這個的時候會有警告,如今都不用這種寫法了,能夠換成
• if(msg.sender != owner) { revert(); }
• assert(msg.sender == owner);
• require(msg.sender == owner);
assert和require正確纔可往下走
revert() 和 require() 都會返還剩餘 gas,並且容許返回一個message,好比:
revert(‘Something bad happened’);
或者
require(condition, ‘Something bad happened’);
就是若是當判斷的條件不成立的時候就會輸出的錯誤信息
三者使用狀況:
• 通常地,儘可能使用 require 函數
• 通常地,require 應該在函數最開始的地方使用
若是有複雜的 if/else 邏輯流,那麼應該考慮使用 revert() 函數而不是require()。
• 通常地,儘可能少使用 assert 調用
• 通常地,assert 應該在函數結尾處使用
基本上,require() 應該被用於函數中檢查條件,assert() 用於預防不該該發生的狀況,但不該該使條件錯誤。
另外,「除非認爲以前的檢查(用 if 或 require )會致使沒法驗證 overflow,不然不該該盲目使用 assert 來檢查 overflow」——來自於@chriseth
舉例,在下例中,你能夠看到如何輕鬆使用``require``檢查輸入條件以及如何使用``assert``檢查內部錯誤,注意,你能夠給 require 提供一個消息字符串,而 assert 不行:
contract Sharer {
function sendHalf(address addr) public payable returns (uint balance) {
require(msg.value % 2 == 0, "Even value required.");
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2);
//因爲轉移函數在失敗時拋出異常而且不能在這裏回調,所以咱們應該沒有辦法仍然有一半的錢。
assert(this.balance == balanceBeforeTransfer - msg.value / 2);
return this.balance;
}
因此我通常就簡單地記爲,當想要在調用函數以前進行條件的限制時,就使用require或者if-revert,並且能夠寫message來輸出錯誤信息以肯定是哪裏的判斷限制了交易的進行。在remix-ide中,message會在觸發後在控制檯顯示錯誤信息,但如果使用本身配置的私有鏈,就須要去本身查看了(沒試過,以後有空試試)
()函數
function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]
1》參數類型<parameter types>能夠爲空();返回類型<return types>與參數類型相反,返回類型不能爲空 —— 若是函數類型不須要返回,則須要刪除整個 returns (<return types>) 部分。
solidity與 Javascript 和 C 不一樣的是,它們可能返回任意數量的參數做爲輸出,但solidity不是,它返回多少個變量是定義死的,returns (uint a, uint b)。
可是參數的名稱是能夠省略的。在函數中未使用參數的名稱(特別是返回參數)能夠省略。好比一個函數return(false,2),那它的返回參數就能夠寫爲returns(bool,uint)。這些參數仍然存在於堆棧中,但它們沒法訪問。
舉例說明:
contract C {
// 省略參數名稱
function func(uint k, uint) public pure returns(uint) {
return k;
}
2》被聲明爲internal|external,是內部函數和外部函數的區別
有兩種方法能夠訪問當前合約中的函數:一種是直接使用它的名字,f ,另外一種是使用 this.f 。 前者適用於聲明爲內部函數internal,後者適用於聲明爲外部函數external的函數
請注意,當前合約函數被聲明爲 public 函數(默認也爲public),則該函數既能夠被看成內部函數也能夠被看成外部函數使用。 若是想將一個函數看成內部函數使用,就用 f 調用,若是想將其看成外部函數,使用 this.f
3》pure|constant|view|payablehtml
1)帶上payable關鍵字,說明這個函數須要接受msg.value,好比
function buyPunk(uint punkIndex) payable {}
那麼在你部署好合約後,想要調用這個函數,你的調用形式必定是:
contract.buyPunk(punkIndex,{from:web3.eth.accounts[0],value:30,gas:30000});
這裏的value傳入的值就是在合約中使用的msg.value,from傳入的值就是合約中的msg.sender
這裏的gas的傳入也是一個十分重要的點,詳細分析請看
2)view是不能修改狀態(至關於constant)
1. 寫入狀態變量;
2. (emitting??)發送事件;
3. 建立其餘合約;
4. 使用selfdestruct;
5. 經過調用發送Ether;
6. 調用沒有被聲明爲view和pure的函數
7. 使用低級調用;
8. 使用包含特定操做碼的內聯程序集。
3)pure的限制更多一點,它是不能修改狀態也不能讀取狀態
1. 讀取狀態變量;
2. 訪問this.balance 或者<address>.balance;
3. 訪問block,tx,msg(除了msg.sig和msg.data);
4. 調用沒有標記爲pure的函數;
5. 使用包含特定操做碼的內聯程序集。
()cryptoPunks函數withdraw函數中有一句msg.sender.transfer(amount)
這句話的意思是,當咱們在合同中有寫payable的函數(如buyPunk)的時候,當調用這個函數時,msg.sender就須要付必定的msg.value,在這裏,這個msg.value的去向to是合約的地址。
可是通常在另外一個函數(withdraw)中,咱們收取這個value是但願它最終是會流向某一個帳戶的地址的,因此在這裏咱們通常都會定義一個數組,即一個臨時帳戶pendingWithdrawals來記錄你付的value的值, uint amount = pendingWithdrawals[msg.sender]。
當最後想要把這個值給一個帳戶地址的時候,咱們就會使用語句msg.sender.transfer(amount)來將合約地址上的這筆錢轉到msg.sender這個帳戶地址中去
()地址類型address
address:地址類型存儲一個 20 字節的值(以太坊地址的大小)
可使用 balance 屬性來查詢一個地址的餘額, 也可使用 transfer 函數向一個地址發送 以太幣(以 wei 爲單位),即上文的msg.sender.transfer(amount)
()在remix-ide中編譯合約的時候有出現過下面的警告:
Warning: Variable is declared as a storage pointer. Use an explicit "storage" keyword to silence this warning.
解決辦法是: 加一個顯式聲明storage(搞清楚其與memory的區別)
Bid storage exist = bidForPicture[pictureId];
() 數據位置——詳細說明:
有三種類型,memory,storage和calldata,通常只有外部函數的參數(不包括返回參數)被強制指定爲calldata。這種數據位置是隻讀的,不會持久化到區塊鏈
storage存儲或memory內存
memory存儲位置同咱們普通程序的內存相似,即分配,即便用,動態分配,越過做用域即不可被訪問,等待被回收。
而對於storage的變量,數據將永遠存在於區塊鏈上。
總結¶
強制指定的數據位置:
• 外部函數的參數(不包括返回參數): calldata,效果跟 memory 差很少
• 狀態變量: storage
默認數據位置:
• 函數參數(包括返回參數): memory
• 全部其它局部變量: storage
舉例說明:python
contract C { uint[] x; // x 的數據存儲位置是 storage,狀態變量 // memoryArray 的數據存儲位置是 memory,函數參數 function f(uint[] memoryArray) public { x = memoryArray; // 將整個數組拷貝到 狀態變量storage 中,可行 var y = x; // 分配一個指針(其中 y 的數據存儲位置是 storage),可行,其餘局部變量 y[7]; // 返回第 8 個元素,可行 y.length = 2; // 經過 y 修改 x,可行 delete x; // 清除數組,同時修改 y,可行 // 下面的就不可行了;須要在 storage 中建立新的未命名的臨時數組 // 但 storage 是「靜態」分配的: // y = memoryArray; // 下面這一行也不可行,由於這會「重置」指針, // 但並無可讓它指向的合適的存儲位置。 // delete y; } }
三種數據之間的相互賦值行爲:
(1)當咱們把一個storage類型的變量賦值給另外一個storage時,咱們只是修改了它的指針,一個變,另外一個也變
struct S{string a;uint b;}
S s;
function convertStorage(S storage s) internal{
S tmp = s;
tmp.a = 「Test」;//s的a的值也改變了
}
(2)memory轉爲memory,memory之間是引用傳遞,並不會拷貝數據,一個變,另外一個也跟着變
(3)memory轉換爲storage
由於局部變量和狀態變量的類型均可能是storage。因此咱們要分開來講這兩種狀況:web
1. memory賦值給狀態變量
將一個memory類型的變量賦值給一個狀態變量時,實際是將內存變量拷貝到存儲中,後續二者不會有任何關係
S s;
function memoryToState(S memory tmp) internal{
s = tmp;//從內存中複製到狀態變量中。
//修改舊memory中的值,並不會影響狀態變量
tmp.a = 「Test」;//s的就不會變了
}
2.memory賦值給局部變量是不能夠的
因爲在區塊鏈中,storage必須是靜態分配存儲空間的。局部變量雖然是一個storage的,但它僅僅是一個storage類型的指針。若是進行這樣的賦值,實際會產生一個錯誤。
function assign(S s) internal{ //默認的變量是storage的指針
//error:Type struct MemoryToLocalVar.S memory is not implicitly convertible to expected type struct MemoryToLocalVar.S storage pointer.
S tmp = s; //修改變量爲memory類型S memory tmp = s;便可
}
(4)storage轉爲memory,會建立一份獨立的拷貝,兩兩互不影響
()在合約中有不少地方即有uint8/uint256/uint,區別是:
聲明一個類型爲 uint (256位無符號整數)
因此在solidity中,若是你聲明瞭一個變量類型是uint的,其實就是聲明瞭一個uint256的
int / uint :分別表示有符號和無符號的不一樣位數的整型變量。 支持關鍵字 uint8 到 uint256 (無符號,從 8 位到 256 位)以及 int8 到 int256,以 8 位爲步長遞增。 uint 和 int 分別是 uint256 和 int256 的別名
()判斷語句
JavaScript 中的大部分控制結構在 Solidity 中都是可用的,除了 switch 和 goto。 所以 Solidity 中有 if,else,while,do,for,break,continue,return,``? :``這些與在 C 或者 JavaScript 中表達相同語義的關鍵詞。
用於表示條件的括號 不能夠 被省略,單語句體兩邊的花括號能夠被省略。
注意,與 C 和 JavaScript 不一樣, Solidity 中非布爾類型數值不能轉換爲布爾類型,所以 if (1) { ... } 的寫法在 Solidity 中 無效 。
()
Gas requirement of function CryptoPicturesMarket.imageHash() high: infinite. If the gas requirement of a function is higher than the block gas limit, it cannot be executed. Please avoid loops in your functions or actions that modify large areas of storage (this includes clearing or copying arrays in storage)
可是我查了一下資料,發現這個好像不是什麼問題,但願之後可以看到能夠解決它的方法吧數據庫
以上是在cryptoPunks中須要知道的內容,下面是一些添加內容:json
其餘內容:
()一個合約如何調用另外一個合約數組
contract MappingExample { mapping(address => uint) public balances; function update(uint newBalance) public { balances[msg.sender] = newBalance; } } contract MappingUser { function f() public returns (uint) { MappingExample m = new MappingExample(); m.update(100); return m.balances(this);//this是該合約的地址 } }
經過 new 建立合約
使用關鍵字 new 能夠建立一個新合約。待建立合約的完整代碼必須事先知道,因此在MappingUser合約中建立合約MappingExample,MappingExample合約必須在MappingUser合約以前聲明。所以遞歸的建立依賴是不可能的。
即一個合約中建立另外一個合約,另外一個合約必定要在該合約以前就已經聲明瞭
()合約相關¶
this (current contract's type):
當前合約,能夠明確轉換爲 地址類型。
selfdestruct(address recipient):
銷燬合約,並把餘額發送到指定 地址類型。
suicide(address recipient):
與 selfdestruct 等價,但已不推薦使用。
()導入
import * as symbolName from 「filename」;//或「.sol」文件
等同於import "filename" as symbolName;
()註釋
單行(//)、多行註釋(/*…*/)和(/** ... */)在函數開頭或內部作的註釋
/** @title 形狀計算器。 */
contract shapeCalculator {
/** @dev 求矩形代表面積與周長。
* @param w 矩形寬度。
* @param h 矩形高度。
* @return s 求得表面積。
* @return p 求得周長。
*/
function rectangle(uint w, uint h) returns (uint s, uint p) {
s = w * h;
p = 2 * (w + h);
}
}
()定長字節數組¶
關鍵字有:bytes1, bytes2, bytes3, ..., bytes32。byte 是 bytes1 的別名。
.length 表示這個字節數組的長度(只讀)
註解
能夠將 byte[] 看成字節數組使用,但這種方式很是浪費存儲空間,準確來講,是在傳入調用時,每一個元素會浪費 31 字節。 更好地作法是使用 bytes。
()變長字節數組¶
bytes:
變長字節數組,參見 數組。它並非值類型。
string:
變長 UTF-8 編碼字符串類型,參見 數組。並非值類型。
()字面常數-須要好好看看
Solidity 中是沒有八進制的,所以前置 0 是無效的
註解
數值字面常數表達式只要在非字面常數表達式中使用就會轉換成非字面常數類型。 在下面的例子中,儘管咱們知道 b 的值是一個整數,但 2.5 + a 這部分表達式並不進行類型檢查,所以編譯不能經過。
//在remix中編譯的時候的確是會報錯
browser/test.sol:4:17: TypeError: Operator + not compatible with types rational_const 5 / 2 and uint128
uint128 b = 2.5 + a + 0.5;
^-----^
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
意思應該就是說由於在字面上咱們能夠看見不進行類型檢查時a是非字面常數類型
十六進制字面常數以關鍵字 hex 打頭,後面緊跟着用單引號或雙引號引發來的字符串(例如,hex」001122FF")
()library ArrayUtils {
// internal內部函數能夠在內部庫函數中使用,
// 由於它們會成爲同一代碼上下文的一部分
…}
contract Pyramid {
using ArrayUtils for *;//使用,而後經過ArrayUtils.函數名()調用
}
()數組:
能夠在聲明時指定長度,也能夠動態調整大小。
對於 存儲storage的數組來講,元素類型能夠是任意的(即元素也能夠是數組類型,映射類型或者結構體)。
對於 內存memory的數組來講,元素類型不能是映射類型,若是做爲 public 函數的參數,它只能是 ABI 類型。
(1)在memory中:一經建立,內存memory數組的大小就是固定的(但倒是動態的,也就是說,它依賴於運行時的參數
1.內存中的數組是不容許建立變長數組的
uint[] memory a;//wrong
2.數組字面常數是一種定長的 內存memory數組類型
在memory中,聲明定長數組:
uint[] memory a = new uint[](2);
在memory中,聲明定長數組並初始化:
uint[2] memory a = [uint(1),2];
uint[] memory a = [uint(1),2];
//wrong,定長的 內存memory數組並不能賦值給變長的 內存memory數組
爲何要在第一個元素一前面加上uint的類型,這是由於咱們前面聲明的是uint的數組,而在solidity中有uint8,uint248等多種不一樣位數的無符號int型常量,在不聲明的狀況下,默認使用uint256,而咱們初始化的數組元素是1和2,系統會斷定爲uint8,因此須要咱們在第一個元素1前面強制轉化爲uint型
固然,咱們也能夠以下的聲明方式:
uint8[2] memory a = [1,2];
但要注意,那麼a數組裏面就不能夠出現2進制裏面大於8位的數了
定長數組是沒法經過length和push方法更改長度或插入的
(2)在storage中
1.在storage中,聲明定長數組,並初始化變量:
uint[2] a = [1,2];
2.在storage中,聲明變長數組,並初始化變量:
uint[] a = [1,2];
3.二維數組初始化,行列是反過來定義的:
uint[2][3] a = [[1,1],[2,2],[3,3]];
但訪問是相同的:
訪問第三個動態數組的第二個元素,你應該使用 a[2][1]
(3)
bytes 和 string 類型的變量是特殊的數組。 bytes 相似於 byte[],但它在 calldata 中會被「緊打包」(譯者注:將元素連續地存在一塊兒,不會按每 32 字節一單元的方式來存放)。 string 與 bytes 相同,但(暫時)不容許用長度或索引來訪問。
(4)變長的 存儲storage數組以及 bytes 類型(而不是 string 類型)都有一個叫作 push 的成員函數
()delete的做用:
delete a 的結果是將 a 的類型在初始化時的值賦值給 a。即對於整型變量來講,至關於 a = 0, 但 delete 也適用於數組,對於動態數組來講,是將數組的長度設爲 0,而對於靜態數組來講,是將數組中的全部元素重置。 若是對象是結構體,則將結構體中的全部屬性重置。
delete 對整個映射是無效的(由於映射的鍵能夠是任意的,一般也是未知的)。 所以在你刪除一個結構體時,結果將重置全部的非映射屬性,這個過程是遞歸進行的,除非它們是映射。 然而,單個的鍵及其映射的值是能夠被刪除的。
安全
contract DeleteExample { uint data; uint[] dataArray; function f() public { uint x = data; delete x; // 將 x 設爲 0,並不影響數據 delete data; // 將 data 設爲 0,並不影響 x,由於它仍然有個副本 uint[] storage y = dataArray; //能夠經過改變dataArray來改變y,也能夠經過改變y來改變dataArray delete dataArray; // 將 dataArray.length 設爲 0,但因爲 uint[] 是一個複雜的對象,y 也將受到影響, // 由於它是一個存儲位置是 storage 的對象的別名。 // 另外一方面:"delete y" 是非法的,引用了 storage 對象的局部變量只能由已有的 storage 對象賦值。 } }
()基本類型之間的轉換
(1)隱式轉換
int8 不能轉換成 uint256(由於 uint256 不能涵蓋某些值,例如,-1)。 更進一步來講,無符號整型能夠轉換成跟它大小相等或更大的字節類型,但反之不能。 任何能夠轉換成 uint160 的類型均可以轉換成 address 類型。
(2)顯式轉換
但有些時候會出問題
int8 y = -3;
uint x = uint(y);
這段代碼的最後,x 的值將是 0xfffff..fd (64 個 16 進制字符),由於這是 -3 的 256 位補碼形式。
若是一個類型顯式轉換成更小的類型,相應的高位將被捨棄
uint32 a = 0x12345678;
uint16 b = uint16(a); // 此時 b 的值是 0x5678
類型推斷
爲了方便起見,沒有必要每次都精確指定一個變量的類型,編譯器會根據分配該變量的第一個表達式的類型自動推斷該變量的類型
uint24 x = 0x123;
var y = x;
這裏 y 的類型將是 uint24。不能對函數參數或者返回參數使用 var。
()時間seconds、 minutes、 hours、 days、 weeks 和 years
years 後綴已經不推薦使用了,由於從 0.5.0 版本開始將再也不支持。
這些後綴不能直接用在變量後邊。若是想用時間單位(例如 days)來將輸入變量換算爲時間,你能夠用以下方式來完成:
function f(uint start, uint daysAfter) public {
if (now >= start + daysAfter * 1 days) {
// ...
}
}
()賦值¶
解構賦值和返回多值¶
Solidity 內部容許元組 (tuple) 類型,也就是一個在編譯時元素數量固定的對象列表,列表中的元素能夠是不一樣類型的對象。這些元組能夠用來同時返回多個數值,也能夠用它們來同時給多個新聲明的變量或者既存的變量(或一般的 LValues):網絡
pragma solidity >0.4.23 <0.5.0; contract C { uint[] data; function f() public pure returns (uint, bool, uint) { return (7, true, 2); } function g() public { //基於返回的元組來聲明變量並賦值 (uint x, bool b, uint y) = f(); //交換兩個值的通用竅門——但不適用於非值類型的存儲 (storage) 變量。 (x, y) = (y, x); //元組的末尾元素能夠省略(這也適用於變量聲明)。 (data.length,,) = f(); // 將長度設置爲 7 //省略元組中末尾元素的寫法,僅能夠在賦值操做的左側使用,除了這個例外: (x,) = (1,); //(1,) 是指定單元素元組的惟一方法,由於 (1) //至關於 1。 } }
()錯誤處理:
下列狀況將會產生一個 assert 式異常:
1 若是你訪問數組的索引太大或爲負數(例如 x[i] 其中 i >= x.length 或 i < 0)。
2 若是你訪問固定長度 bytesN 的索引太大或爲負數。
3 若是你用零當除數作除法或模運算(例如 5 / 0 或 23 % 0 )。
4 若是你移位負數位。
5 若是你將一個太大或負數值轉換爲一個枚舉類型。
6 若是你調用內部函數類型的零初始化變量。
7 若是你調用 assert 的參數(表達式)最終結算爲 false。
下列狀況將會產生一個 require 式異常:
1 調用 throw 。
2 若是你調用 require 的參數(表達式)最終結算爲 false 。
3 若是你經過消息調用調用某個函數,但該函數沒有正確結束(它耗盡了 gas,沒有匹配函數,或者自己拋出一個異常),上述函數不包括低級別的操做 call , send , delegatecall 或者 callcode 。低級操做不會拋出異常,而經過返回 false 來指示失敗。
4 若是你使用 new 關鍵字建立合約,但合約沒有正確建立(請參閱上條有關」未正確完成「的定義)。
5 若是你對不包含代碼的合約執行外部函數調用。
6 若是你的合約經過一個沒有 payable 修飾符的公有函數(包括構造函數和 fallback 函數)接收 Ether。
7 若是你的合約經過公有 getter 函數接收 Ether 。
8 若是 .transfer() 失敗。
在內部, Solidity 對一個 require 式的異常執行回退操做(指令 0xfd )並執行一個無效操做(指令 0xfe )來引起 assert 式異常。 在這兩種狀況下,都會致使 EVM 回退對狀態所作的全部更改。回退的緣由是不能繼續安全地執行,由於沒有實現預期的效果。 由於咱們想保留交易的原子性,因此最安全的作法是回退全部更改並使整個交易(或至少是調用)不產生效果。 請注意, assert 式異常消耗了全部可用的調用 gas ,而從 Metropolis 版本起 require 式的異常不會消耗任何 gas。
下邊的例子展現瞭如何在 revert 和 require 中使用錯誤字符串:
pragma solidity ^0.4.22;
contract VendingMachine {
function buy(uint amount) payable {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
// 下邊是等價的方法來作一樣的檢查:
require(
amount <= msg.value / 2 ether,
"Not enough Ether provided."
);
// 執行購買操做
}
}
這裏提供的字符串應該是通過 ABI 編碼 以後的,由於它其實是調用了 Error(string) 函數。在上邊的例子裏,revert("Not enough Ether provided."); 會產生以下的十六進制錯誤返回值:
0x08c379a0 // Error(string) 的函數選擇器
0x0000000000000000000000000000000000000000000000000000000000000020 // 數據的偏移量(32)
0x000000000000000000000000000000000000000000000000000000000000001a // 字符串長度(26)
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // 字符串數據("Not enough Ether provided.")
()可見性
contract C {
uint private data;
function f(uint a) private returns(uint b) { return a + 1; }//不可繼承
function setData(uint a) public { data = a; }
function getData() public returns(uint) { return data; }
function compute(uint a, uint b) internal returns (uint) { return a+b; }//可繼承
}
contract D {//這能訪問public
function readData() public {
C c = new C();
uint local = c.f(7); // error: member `f` is not visible
c.setData(3);
local = c.getData();
local = c.compute(3, 5); // error: member `compute` is not visible
}
}
contract E is C {//繼承至C,因此能夠訪問internal,public
function g() public {
C c = new C();
uint val = compute(3, 5); // access to internal member (from derived to parent contract)
}
}
()getter函數
(1)就是在合約中聲明的狀態變量如data,其實都自動地生成了getter函數,就是能夠像訪問函數同樣訪問它的值,在合約外訪問時就能夠直接 合約名.data()
pragma solidity ^0.4.0;
contract C {
uint public data = 42;
}
contract Caller {
C c = new C();
function f() public {
uint local = c.data();
}
}
(2)若是是在合約內部訪問,它有兩種訪問的形式:internal和external
internal則是直接變量名訪問便可
external則是使用this.data()
pragma solidity ^0.4.0;
contract C {
uint public data;
function x() public {
data = 3; // internal access
uint val = this.data(); // external access
}
}
()
contract Complex {
//Note that the mapping in the struct is omitted because there is no good way to provide the key for the mapping.
struct Data {
//若是在一個結構體中聲明瞭一個映射,通常賦值時都先省略,而後在賦值mapping,由於它的key是不固定的
uint a;
bytes3 b;
mapping (uint => uint) map;
}
mapping (uint => mapping(bool => Data[])) public data;
//調用方法:data[arg1][arg2][arg3].a
}
()修飾器modifier
繼承後能夠直接使用被繼承處的修飾器
使用修改器實現的一個防重複進入的例子。
pragma solidity ^0.4.0;
contract Mutex {
bool locked;
modifier noReentrancy() {
if (locked) throw;
locked = true;
_;//函數體f()return前的內容執行的區域
locked = false;
}
/// This function is protected by a mutex, which means that
/// reentrant calls from within msg.sender.call cannot call f again.
/// The `return 7` statement assigns 7 to the return value but still
/// executes the statement `locked = false` in the modifier.
function f() noReentrancy returns (uint) {
if (!msg.sender.call()) throw;
return 7;
}
}
例子中,因爲call()方法有可能會調回當前方法,修改器實現了防重入的檢查。
若是同一個函數有多個修改器,他們之間以空格隔開,修飾器會依次檢查執行。
須要注意的是,在Solidity的早期版本中,有修改器的函數,它的return語句的行爲有些不一樣。
在修改器中和函數體內的顯式的return語句,僅僅跳出當前的修改器和函數體。返回的變量會被賦值,但整個執行邏輯會在前一個修改器後面定義的」_"後繼續執行。
()fallback function \ call()\send()數據結構
(1)fallback function
contract ExecuteFallback{ //回退事件,會把調用的數據打印出來 event FallbackCalled(bytes data); //fallback函數,注意是沒有名字的,沒有參數,沒有返回值的 function(){
//msg.data其實就是你調用一個函數後,函數名,參數進行keccak256哈希後鏈接起來的data FallbackCalled(msg.data);//event } //調用已存在函數的事件,會把調用的原始數據,請求參數打印出來 event ExistFuncCalled(bytes data, uint256 para); //一個存在的函數 function existFunc(uint256 para){ ExistFuncCalled(msg.data, para); } // 模擬從外部對一個存在的函數發起一個調用,將直接調用函數 function callExistFunc(){ bytes4 funcIdentifier = bytes4(keccak256("existFunc(uint256)")); this.call(funcIdentifier, uint256(1)); } //模擬從外部對一個不存在的函數發起一個調用,因爲匹配不到函數,將調用回退函數 function callNonExistFunc(){ bytes4 funcIdentifier = bytes4(keccak256("functionNotExist()")); this.call(funcIdentifier); } }
回退函數(fallback function):則是當調用一個合約裏的某個函數時,若是該函數並不存在,那麼就會去調用該回調函數,回調函數無參,無名,無返回值,你能夠經過在裏面返回個什麼或者emit一個事件來顯示調用了該回退函數,該函數仍是頗有用的
(2)Call()
Call():call()是一個底層的接口,用來向一個合約發送消息,進行合約之間的交互,可是很不安全,通常是不用的。這個函數是這樣的,第一個參數是該訪問的函數的名字,後面的參數則是該函數所需的參數,調用它的是一個合約的地址
(3)send()
send()函數發送ether
當咱們使用address.send(ether to send)向某個合約直接轉賬時,因爲這個行爲沒有發送任何數據,因此接收合約老是會調用fallback函數
function() payable{fallbackTrigged(msg.data);}
function deposit() payable{//用來給合約存點錢
}
在上述的代碼中,咱們先要使用deposit()合約存入一些ether,不然將會因爲餘額不足,調用send()函數將報錯。
查看事件fallbackTrigged獲得:
fallbackTrigged[
"0x"
]
能夠看到,咱們成功使用send()發送了1wei到合約,觸發了fallback函數,附帶的數據是0x(bytes類型的默認空值),空數據。
這裏須要特別注意的是:
1. 若是咱們要在合約中經過send()函數接收,就必須定義fallback函數,不然會拋異常。
2. fallback函數必須增長payable關鍵字,不然send()執行結果將會始終爲false。
fallback中的限制
send()函數老是會調用fallback,這個行爲很是危險,著名的DAO被黑也與這有關。若是咱們在分成時,對一系列賬戶進行send()操做,其中某個作惡意賬戶中的fallback函數實現了一個無限循環,將由於gas耗盡,致使全部send()失敗。爲解決這個問題,send()函數當前即使gas充足,也只會附帶限定的2300gas,故而fallback函數內除了能夠進行日誌操做外,你幾乎不能作任何操做。
下述行爲消耗的gas都將超過fallback函數限定的gas值:
• 向區塊鏈中寫數據
• 建立一個合約
• 調用一個external的函數
• 發送ether
因此通常,咱們只能在fallback函數中進行一些日誌操做:
()底層的日誌接口(Low-level Interface to Logs):能夠代替event
經過函數log0,log1,log2,log3,log4,能夠直接訪問底層的日誌組件。logi表示總共有帶i + 1個參數(i表示的就是可帶參數的數目,只是是從0開始計數的)。其中第一個參數會被用來作爲日誌的數據部分,其它的會作爲主題(topics)。前面例子中的事件可改成以下:
log3(
msg.value,
0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,
msg.sender,
_id
);
其中的長16進制串是事件的簽名,計算方式是keccak256("Deposit(address,hash256,uint256)")
()繼承
pragma solidity ^0.4.0; contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { function kill() { /* do cleanup 1 */ mortal.kill(); } } contract Base2 is mortal { function kill() { /* do cleanup 2 */ mortal.kill(); } } contract Final is Base1, Base2 { }
對Final.kill()的調用只會調用Base2.kill(),由於派生重寫,會跳過Base1.kill,由於它根本就不知道有Base1。一個變通方法是使用super。
pragma solidity ^0.4.0; contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { function kill() { /* do cleanup 1 */ super.kill(); } } contract Base2 is mortal { function kill() { /* do cleanup 2 */ super.kill(); } } contract Final is Base2, Base1 { }
若是Base1調用了函數super,它不會簡單的調用基類的合約函數,它還會調用繼承關係圖譜上的下一個基類合約,因此會調用Base2.kill()。須要注意的最終的繼承圖譜將會是:Final,Base1,Base2,mortal,owned。使用super時會調用的實際函數在使用它的類的上下文中是未知的,儘管它的類型是已知的。這相似於普通虛函數查找(ordinary virtual method lookup)
當基類的構造函數中若是須要傳參,那麼繼承它時的方式是:
contract Base { uint x; function Base(uint _x) { x = _x; } } contract Derived is Base(7) { function Derived(uint _y) Base(_y * _y) { } }
繼承寫的順序是很重要的,從繼承少的到多的
()抽象(Abstract Contracts)
抽象函數是沒有函數體的的函數。以下:
pragma solidity ^0.4.0; contract Feline { function utterance() returns (bytes32); }
這樣的合約不能經過編譯,即便合約內也包含一些正常的函數。但它們能夠作爲基合約被繼承。
pragma solidity ^0.4.0; contract Feline { function utterance() returns (bytes32); function getContractName() returns (string){ return "Feline"; } } contract Cat is Feline { function utterance() returns (bytes32) { return "miaow"; } }
若是一個合約從一個抽象合約裏繼承,但卻沒實現全部函數,那麼它也是一個抽象合約。
()接口(interface)
接口與抽象合約相似,與之不一樣的是,接口內沒有任何函數是已實現的,同時還有以下限制:
1. 不能繼承其它合約,或接口。
2. 不能定義構造器
3. 不能定義變量
4. 不能定義結構體
5. 不能定義枚舉類
其中的一些限制可能在將來放開。
接口基本上限制爲合約ABI定義能夠表示的內容,ABI和接口定義之間的轉換應該是可能的,不會有任何信息丟失。
接口用本身的關鍵詞表示:
interface Token {
function transfer(address recipient, uint amount);
}
合約能夠繼承於接口,由於他們能夠繼承於其它的合約。
pragma solidity ^0.4.11; interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; } //使用則是在合同函數中直接使用: function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { tokenRecipient spender = tokenRecipient(_spender);
()library庫
使用庫合約的合約,能夠將庫合約視爲隱式的父合約(base contracts),固然它們不會顯式的出如今繼承關係中。意思就是不用寫is來繼承,直接能夠在合約中使用:
library Set {
struct Data { mapping(uint => bool) flags; }
}
contract C {
Set.Data knownValues;
}
但調用庫函數的方式很是相似,如庫L有函數f(),使用L.f()便可訪問。此外,internal的庫函數對全部合約可見,若是把庫想像成一個父合約就能說得通了。固然調用內部函數使用的是internal的調用慣例,這意味着全部internal類型能夠傳進去,memory類型則經過引用傳遞,而不是拷貝的方式。
library Set { // We define a new struct datatype that will be used to // hold its data in the calling contract. struct Data { mapping(uint => bool) flags; } // Note that the first parameter is of type "storage // reference" and thus only its storage address and not // its contents is passed as part of the call. This is a // special feature of library functions. It is idiomatic // to call the first parameter 'self', if the function can // be seen as a method of that object. function insert(Data storage self, uint value) returns (bool) { if (self.flags[value]) return false; // already there self.flags[value] = true; return true; } function remove(Data storage self, uint value) returns (bool) { if (!self.flags[value]) return false; // not there self.flags[value] = false; return true; } function contains(Data storage self, uint value) returns (bool) { return self.flags[value]; } } contract C { Set.Data knownValues; function register(uint value) { // The library functions can be called without a // specific instance of the library, since the // "instance" will be the current contract. if (!Set.insert(knownValues, value)) throw; } // In this contract, we can also directly access knownValues.flags, if we want. }
上面的例子中:
• Library定義了一個數據結構體struct Data,用來在調用的合約中使用(庫自己並未實際存儲的數據)。若是函數須要操做數據,這個數據通常是經過庫函數的第一個參數傳入(Data storage self),按慣例會把參數名定爲self。
• 另一個須要留意的是上例中self的類型是storage,那麼意味着傳入的會是一個引用,而不是拷貝的值,那麼修改它的值,會同步影響到其它地方,俗稱引用傳遞,非值傳遞。
• 庫函數的使用不須要實例化,c.register中能夠看出是直接使用Set.insert。但實際上當前的這個合約自己就是它的一個實例。
• 這個例子中,c能夠直接訪問,knownValues。雖然這個值主要是被庫函數使用的
對比普通合約來講,庫的限制:
• 無狀態變量(state variables)。
• 不能繼承或被繼承
• 不能接收ether。
附着庫(Using for)
指令using A for B;用來附着庫裏定義的函數(從庫A)到任意類型B。這些函數將會默認接收調用函數對象的實例做爲第一個參數。語法相似,python中的self變量同樣。
using A for *的效果是,庫A中的函數被附着在作任意的類型上。
在這兩種情形中,全部函數,即便那些第一個參數的類型與調用函數的對象類型不匹配的,也被附着上了。類型檢查是在函數被真正調用時,函數重載檢查也會執行。
using A for B;指令僅在當前的做用域有效,且暫時僅僅支持當前的合約這個做用域,後續也很是有可能解除這個限制,容許做用到全局範圍。若是能做用到全局範圍,經過引入一些模塊(module),數據類型將能經過庫函數擴展功能,而不須要每一個地方都得寫一遍相似的代碼了。
上面的例子就改爲了:
contract C { using Set for Set.Data; // this is the crucial change Set.Data knownValues; function register(uint value) { // Here, all variables of type Set.Data have // corresponding member functions. // The following function call is identical to // Set.insert(knownValues, value) if (!knownValues.insert(value)) throw; } }
//其實就是原本要訪問的話的語句是Set.insert(knownValues, value),如今是knownValues.insert(value),即將庫中的函數都附着在告終構體struct Data上,由於以前庫函數的第一個參數的聲明其實也是這個結構體(self),附着後調用時就能夠省略掉第一個參數了,就算沒有結構體,那麼附着的必定是函數的第一個參數聲明的那個self的類型
如:
library Search { function indexOf(uint[] storage self, uint value) } contract C { using Search for uint[]; uint[] data; }
即 library 庫,有兩種做用
《1》定義 library,使用結構體,調用函數
pragma solidity ^0.4.20; // This is the same code as before, just without comments library Set { struct Data { mapping(uint => bool) flags; } function insert(Data storage self, uint value) public returns (bool) { if (self.flags[value]) return false; // already there self.flags[value] = true; return true; } function remove(Data storage self, uint value) public returns (bool) { if (!self.flags[value]) return false; // not there self.flags[value] = false; return true; } function contains(Data storage self, uint value) public view returns (bool) { return self.flags[value]; } } //而後合約中調用庫中的函數 contract C { using Set for Set.Data; // this is the crucial change Set.Data knownValues; function register(uint value) public { // Here, all variables of type Set.Data have // corresponding member functions. // The following function call is identical to // `Set.insert(knownValues, value)` require(knownValues.insert(value)); } }
《2》 使用庫來擴展數據類型
pragma solidity ^0.4.20; library Search { function indexOf(uint[] storage self, uint value) public view returns (uint) { for (uint i = 0; i < self.length; i++) if (self[i] == value) return i; return uint(-1); } } contract C { using Search for uint[];//使得類型爲uint[]的數值能夠有函數indexOf進行使用 uint[] data; function append(uint value) public { data.push(value); } function replace(uint _old, uint _new) public { // This performs the library function call uint index = data.indexOf(_old); if (index == uint(-1)) data.push(_new); else data[index] = _new; } }
()若是你想要把數據寫到交易中:
參考:https://blog.csdn.net/koastal/article/details/78794275
let Web3 = require("web3"); let fs = require("fs"); let web3 = new Web3(); web3.setProvider(new Web3.providers.HttpProvider("http://192.168.1.10:8545")); let log = { time:(new Date).getTime(), type:"error", msg:"數據庫鏈接失敗" }; let str = JSON.stringify(log);//將json轉換爲string let data = Buffer.from(str).toString('hex'); data = '0x'+data; console.log(data); //將數據寫入到交易中 let coinbase = "0x8a1C505f1ff14045c03622E9ab82EB19c730cef3"; let user1 = "0xb4B6338977078f1c355ee394D67D6DFE5C24dad8"; web3.personal.unlockAccount(coinbase, "coinbase"); let address = web3.eth.sendTransaction({ from:coinbase, to:user1, value:'0x00', data:data }); //從交易地址獲取數據 let transaction = web3.eth.getTransaction(address); let inputData = transaction.input; let res_str = Buffer.from(inputData.replace('0x',''),'hex').toString(); let res_json = JSON.parse(res_str);//將string轉換爲json console.log(transaction); console.log(res_json);
web3.eth.sendTransaction(transactionObject [, callback])
參數:
transactionObject
: Object - 要發送的交易對象。 返回值:
String
- 32字節的交易哈希串。用16進製表示。
web3.eth.getTransaction(transactionHash [, callback])
參數:
transactionHash
: String - 交易的哈希值。callback
: Function - 回調函數,用於支持異步的方式執行7。返回值:
Object
- 一個交易對象
結果爲:
0x7b2274696d65223a313531333135363433363137392c2274797065223a226572726f72222c226d7367223a22e695b0e68daee5ba93e8bf9ee68ea5e5a4b1e8b4a5227d { blockHash: '0x0000000000000000000000000000000000000000000000000000000000000000', blockNumber: null, from: '0x8a1c505f1ff14045c03622e9ab82eb19c730cef3', gas: 90000, gasPrice: { [String: '18000000000'] s: 1, e: 10, c: [ 18000000000 ] }, hash: '0x3149578fbb8cf75f264fc87426b7bfa2a89256763fe5194ccad8b724a0325470', input: '0x7b2274696d65223a313531333135363433363137392c2274797065223a226572726f72222c226d7367223a22e695b0e68daee5ba93e8bf9ee68ea5e5a4b1e8b4a5227d', nonce: 57, to: '0xb4b6338977078f1c355ee394d67d6dfe5c24dad8', transactionIndex: 0, value: { [String: '0'] s: 1, e: 0, c: [ 0 ] }, v: '0x26', r: '0x742335f7b649c554a35baf624fe90c8e3fa3c9cfc116cfe858af420dc893359a', s: '0x20a63d760ab574736aaed4799f2a15bc727a988b4d41ee279e3b753b1c1f3913' } { time: 1513156436179, type: 'error', msg: '數據庫鏈接失敗' }