20180925_合約總結(jié)(Solidity)

一.ABI

ABI是和Ethereum生態(tài)系統(tǒng)的合約進行交互的標(biāo)準(zhǔn)方式,所有合約的調(diào)用都是通過ABI.一個函數(shù)調(diào)用時的call data中前四個bytes指定了哪個函數(shù)被調(diào)用,從第五個字節(jié)開始則是函數(shù)的編碼.

function selector

所有合約交易的function selector都是根據(jù)methodName(type01,type02,...typeN)的keccak256前八位來決定的,例如例子的Erc20規(guī)范轉(zhuǎn)賬,前四個bytes一定是a9059cbb

String encF = Hash.sha3String("transfer(address,uint256)");
//0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b

arugment encoding

不同類型有不同的編碼規(guī)則,定長類型和不定長類型的編碼規(guī)則也是不同的.

這些是不定長的類型:

  • bytes
  • string
  • T[] : T可以是任何類型
  • T[k] T是任何類型的動態(tài)長度類型, 并且 k >= 0
  • (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k

其他類型都是定長的.

example

給定一個合約:

pragma solidity ^0.4.16;

contract Foo {
  function bar(bytes3[2]) public pure {}
  function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
  function sam(bytes, bool, uint[]) public pure {}
}
function baz(uint32 x, bool y)

因此,對于我們的合約Foo,如果我們想傳入69true來調(diào)用函數(shù)baz,我們總共會傳入68個bytes,拆開來看:

  • 0xcdcd77c0: Method ID. 這是作為baz(uint32,bool)簽名的ASCII形式的Keccak hash的前四個字節(jié).
  • 0x0000000000000000000000000000000000000000000000000000000000000045:第一個參數(shù),一個uint32類型的 69, 用0填充到 32 bytes
  • 0x0000000000000000000000000000000000000000000000000000000000000001: 第二個參數(shù) - boolean true, 用0填充到 32 bytes

拼一起:

0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001

這個函數(shù)返回單一參數(shù)bool.如果它返回了false,它的輸出會是一個單一的byte數(shù)組0x0000000000000000000000000000000000000000000000000000000000000000,一個bool.

function bar(bytes3[2])

如果我們想傳入["abc","def"]來調(diào)用bar,我們一共會傳入68bytes,細(xì)分如下:

  • 0xfce353f6: the Method ID. 從 bar(bytes3[2])的簽名而來.
  • 0x6162630000000000000000000000000000000000000000000000000000000000:第一個參數(shù), bytes3 類型的值 "abc" (left-aligned).
  • 0x6465660000000000000000000000000000000000000000000000000000000000: 第二個參數(shù), bytes3類型的值 "def" (left-aligned).

拼一起:

0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
function sam(bytes, bool, uint[])

如果我們想傳入?yún)?shù) "dave", true[1,2,3]來調(diào)用 sam, 我們總共會傳入292 bytes, 細(xì)分如下:

  • 0xa5643bf2: the Method ID. This is derived from the signature sam(bytes,bool,uint256[]). Note that uint會被替換成規(guī)范的表示- uint256.
  • 0x0000000000000000000000000000000000000000000000000000000000000060: 第一個參數(shù)的數(shù)據(jù)部分所在位置 (dynamic type), 從參數(shù)區(qū)塊開始位置開始計算,以字節(jié)為單位. 這種情況下, 0x60.
  • 0x0000000000000000000000000000000000000000000000000000000000000001: 第二個參數(shù): boolean true.
  • 0x00000000000000000000000000000000000000000000000000000000000000a0: 第三個參數(shù)的數(shù)據(jù)部分所在位置 (dynamic type),以字節(jié)為單位. In this case, 0xa0.
  • 0x0000000000000000000000000000000000000000000000000000000000000004: 第一個參數(shù)的數(shù)據(jù)位置部分, 它以元素中字節(jié)數(shù)組的長度開始, in this case, 4.
  • 0x6461766500000000000000000000000000000000000000000000000000000000: 第一個參數(shù)的內(nèi)容: UTF-8 (equal to ASCII in this case) 編碼 的 "dave", 右邊填充至 32 bytes.
  • 0x0000000000000000000000000000000000000000000000000000000000000003: 第三個參數(shù)的數(shù)據(jù)部分,他以數(shù)組的長度開始, in this case, 3.
  • 0x0000000000000000000000000000000000000000000000000000000000000001: 第三個參數(shù)的第一個條目.
  • 0x0000000000000000000000000000000000000000000000000000000000000002: 第三個參數(shù)的第二個條目.
  • 0x0000000000000000000000000000000000000000000000000000000000000003: 第三個參數(shù)的第三個條目.

拼一起:

0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003

dynamic types

調(diào)用一個有簽名的函數(shù)f(uint,uint32[],bytes10,bytes)傳入值(0x123,[0x456,0x789],"1234567890","Hello, world!")會以下面這種方式編碼:

調(diào)用一個有簽名的函數(shù)f(uint,uint32[],bytes10,bytes)傳入值(0x123,[0x456,0x789],"1234567890","Hello, world!")會以下面這種方式編碼:

先獲取sha3("f(uint256,uint32[],bytes10,bytes)")的前四個字節(jié),我們拿到0x8be65246.然后我們對四個參數(shù)的頭部部分進行編碼.對于靜態(tài)類型uint256bytes10,他們會直接將他們自己穿進去,而對于動態(tài)類型uint32[]bytes來說,我們將它們的數(shù)據(jù)區(qū)域的開始部分以字節(jié)為單位進行偏移,從編碼后的位置開始數(shù)(例如:函數(shù)簽名的前四個字節(jié)不納入位置).將會編碼成:

  • 0x0000000000000000000000000000000000000000000000000000000000000123 (0x123 左填充到 32 bytes)
  • 0x0000000000000000000000000000000000000000000000000000000000000080 (第二個參數(shù)數(shù)據(jù)部分的偏移位置,從開始計算, 4*32 bytes, 剛好是頭部部分的大小)
  • 0x3132333435363738393000000000000000000000000000000000000000000000 ("1234567890",右填充到32bytes)
  • 0x00000000000000000000000000000000000000000000000000000000000000e0 (第四個參數(shù)數(shù)據(jù)部分的偏移位置 = 第一個動態(tài)參數(shù)數(shù)據(jù)部分的偏移位置的開始 + 第一個動態(tài)參數(shù)數(shù)據(jù)部分的大小 = 432 + 332 (參照下面))

在這之后,第一個動態(tài)參數(shù)的數(shù)據(jù)部分[0x456,0x789]如下所示:

  • 0x0000000000000000000000000000000000000000000000000000000000000002 (數(shù)組元素的個數(shù), 2)
  • 0x0000000000000000000000000000000000000000000000000000000000000456 (第一個元素)
  • 0x0000000000000000000000000000000000000000000000000000000000000789 (第二個元素)

最后,我們將第二個動態(tài)參數(shù)的數(shù)據(jù)"Hello, world!"編碼出來:

  • 0x000000000000000000000000000000000000000000000000000000000000000d (元素的大小 (bytes in this case): 13)
  • 0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000 (`"Hello, world!" 向右填充到32bytes)

全部放在一起, (0x123,[0x456,0x789],"1234567890","Hello, world!")編碼出來的內(nèi)容是 (為了易讀性,函數(shù)選擇器后每32-bytes都會另起一行):

0x8be65246 //函數(shù)選擇器 (Method ID)
  0000000000000000000000000000000000000000000000000000000000000123 //uint 的0x123
  0000000000000000000000000000000000000000000000000000000000000080 //uint32[] 的內(nèi)容偏移位置
  3132333435363738393000000000000000000000000000000000000000000000 //string的utf-8轉(zhuǎn)hex
  00000000000000000000000000000000000000000000000000000000000000e0 //bytes 的偏移位置
  0000000000000000000000000000000000000000000000000000000000000002 //第二個參數(shù)(數(shù)組)的長度
  0000000000000000000000000000000000000000000000000000000000000456 //第二個參數(shù)的第一個元素
  0000000000000000000000000000000000000000000000000000000000000789 //第二個參數(shù)的第二個元素
  000000000000000000000000000000000000000000000000000000000000000d //第四個參數(shù)的長度
  48656c6c6f2c20776f726c642100000000000000000000000000000000000000 //第四個參數(shù)的bytes轉(zhuǎn)hex

一個函數(shù)中如果包含定長類型和動長類型,ABI的生成總結(jié)下來就是:

  1. method ID
  2. 依據(jù)參數(shù)順序依次進行編碼,如果是定長類型,直接encode,如果是動長類型,存入其開始的偏移位置
  3. 依次展示動長類型的長度以及他們的數(shù)據(jù)編碼

events

編碼規(guī)則和function ABI類似,會由以下部分組成:

  • 合約的地址(address)
  • 最多四個的topic(角標(biāo)為0的為keccak(EVENT_NAME+("+EVENT_ARGS.map(canonical_type_of).join(",")+")") ,從1-3是定義event時添加了indexed的參數(shù),例如event Transfer(address indexed from, address indexed to, uint256 value);中的fromto)
  • 長度不定的二進制數(shù)據(jù)(例如上面的value)

例如一個標(biāo)準(zhǔn)Erc20轉(zhuǎn)賬的交易:

Function: transfer(address _to, uint256 _value)

MethodID: 0xa9059cbb
[0]:  000000000000000000000000320bc367d5c1ebe5695f8f2544db46d990d5241c
[1]:  000000000000000000000000000000000000000000000002a802f8630a240000

會生成以下Event:


Address     0xc3af5103551287cfc8f12d7bfe208e0c2c3c3ff1  
Name        Transfer (index_topic_1 address from, index_topic_2 address to, uint256 value)
Topics
[0]         0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
[1]         0x0000000000000000000000007ab2b50a62006c0e87b527228761b8501c798c6e
[2]         0x000000000000000000000000320bc367d5c1ebe5695f8f2544db46d990d5241c
   Data 
            000000000000000000000000000000000000000000000002a802f8630a240000

對于event和function來說,他們的ABI都會將省略類型補成官方定義的類型

二.storage or memory

官方定義中EVM有三個可以存儲的區(qū)域:

第一個是"storage",其中包含所有合同狀態(tài)變量古胆。每個合同都有自己的存儲空間诈皿,它在函數(shù)調(diào)用之間是持久的欠雌,使用起來非常昂貴蹄梢。
第二個是"memory"富俄,這用于保存臨時值而咆。它在(外部)函數(shù)調(diào)用之間被擦除,并且使用起來更便宜幕袱。
第三個是"stack",用于保存小的局部變量馍驯。它幾乎可以免費使用玛痊,但只能保留有限數(shù)量的值狂打。
最重要的是,如果您例如在函數(shù)調(diào)用中傳遞此類變量,則如果它們可以保留在memory中或保留在storage中对省,則不會復(fù)制它們的數(shù)據(jù)蒿涎。
  1. storagememory關(guān)鍵字用于在存儲和存儲器分別引用數(shù)據(jù)惦辛。
  2. 合同存儲在合同構(gòu)建期間預(yù)先分配,不能在函數(shù)調(diào)用中創(chuàng)建玻淑。畢竟呀伙,如果要保留函數(shù)剿另,在函數(shù)存儲中創(chuàng)建新變量沒有多大意義。
  3. 在合同構(gòu)建期間不能分配內(nèi)存谚攒,而是在函數(shù)執(zhí)行中創(chuàng)建戚篙。合同狀態(tài)變量始終在存儲中聲明岔擂。同樣浪耘,擁有不能持久的狀態(tài)變量也沒有意義。
  4. memory引用數(shù)據(jù)分配給storage引用變量時七冲,我們將數(shù)據(jù)從內(nèi)存復(fù)制到存儲规婆。沒有創(chuàng)建新存儲抒蚜。
  5. storage引用數(shù)據(jù)分配給memory引用變量時,我們將數(shù)據(jù)從存儲復(fù)制到內(nèi)存嗡髓。分配了新內(nèi)存。
  6. 當(dāng)storage在函數(shù)通過查找創(chuàng)建本地變量浊伙,它簡單地引用已經(jīng)分配上存儲數(shù)據(jù)嚣鄙。沒有創(chuàng)建新存儲串结。

以下面的代碼作為示例,先看看getter:

// 不設(shè)置成view函數(shù)來估算gas
function getUsingStorage(uint _itemIdx) public returns (uint){
    Item storage item = items[_itemIdx];
    return item.units;
}
// 不設(shè)置成view函數(shù)來估算gas
function getUsingMemory(uint _itemIdx) public returns (uint){
    Item memory item = items[_itemIdx];
    return item.units;
}

兩個函數(shù)都返回相同的結(jié)果,但是getUsingMemory創(chuàng)建一個新變量所以使用了更多的gas :

// getUsingStorage
"gasUsed": 21849
// getUsingMemory
"gasUsed": 22149

另外我們再看看setter:

function addItemUsingStorage(uint _itemIdx, uint _units) public{
    Item storage item = items[_itemIdx];
    item.units += _units;
}

function addItemUsingMemory(uint _itemIdx, uint _units) public{
    Item memory item = items[_itemIdx];
    item.units += _units;
}

只有addItemUsingStorage 修改了狀態(tài)變量(意味著消費了更多gas):

// addItemUsingStorage
// `units` changes in `items`
"gasUsed": 27053,
// addItemUsingMemory
// `units` does not change in `items`
"gasUsed": 22287

作為結(jié)論:

  1. memorystorage指定data location變量引用的內(nèi)容
  2. storage不能在函數(shù)中新創(chuàng)建。storage函數(shù)中任何引用的變量總是指在合同存儲(狀態(tài)變量)上預(yù)先分配的一段數(shù)據(jù)声功。函數(shù)調(diào)用后任何突變都會持續(xù)存在先巴。
  3. memory只能在函數(shù)中新創(chuàng)建伸蚯。它可以是新實例化的復(fù)雜類型,如array / struct(例如通過new int[...])摇幻,也可以從storage引用的變量中復(fù)制绰姻。
  4. 由于引用是通過函數(shù)參數(shù)在內(nèi)部傳遞的,請記住它們默認(rèn)是memory榨馁,如果變量是在 storage中翼虫,它將創(chuàng)建一個副本屡萤,任何修改都不會持久死陆。(這條有問題吧)

參考

三.細(xì)節(jié)及技巧

命名勿用關(guān)鍵字

除了目前已經(jīng)存在的關(guān)鍵字(函數(shù)聲明,事件聲明,變量聲明的關(guān)鍵字)以外,以下關(guān)鍵字也請保留

abstract, after, case, catch, default, final, in, inline, let, match, null, of, relocatable, static, switch, try, type, typeof.

storage中變量的存儲位置

當(dāng)使用小于32 bytes的元素時,你合約用的gas可能會更高.這是因為EVM每次操作都是使用32bytes.因此,如果一個元素比32 bytes小,EVM必須使用更多的操作去減小這個元素的大小到它需要的大小.

最后,EVM存儲中有一個概念叫存儲槽,為了讓EVM去優(yōu)化,保證你嘗試去給你的存儲變量以及struct的成員變量排序,以讓他們能被緊緊地打包.例如:將你聲明的uint128,uint256, uint128替換為 uint128, uint128, uint256,前者會使用3個存儲槽而后者只會使用2個.

錯誤處理(作為事務(wù))

為了讓合約函數(shù)的操作不影響state variables,我們需要考慮到revert.

在函數(shù)開始時就需要進行判斷條件并revert的,能使用require就使用它(如果是經(jīng)常需要判斷的情況,請抽取到modifier中).

在函數(shù)執(zhí)行時需要根據(jù)條件進行revert的,使用assert而不是if,assert能直接幫我們使事務(wù)無效,并進行revert.

如果有異常發(fā)生,assert會消耗當(dāng)前調(diào)用函數(shù)的所有g(shù)as,而require從Metropolis版本開始不會消耗任何gas

變量聲明

默認(rèn)值

所有聲明出來的變量都有一個初始默認(rèn)值,是它們這個類型對應(yīng)的bytes32的0,例如:bool的默認(rèn)值是false,uintint的默認(rèn)值是0.

對于靜態(tài)長度的數(shù)組以及從byte1byte32這些類型來說,它們存儲的每個元素的默認(rèn)值都對應(yīng)它們類型的0.

對于一個動態(tài)長度的數(shù)組,bytes以及string來說,默認(rèn)值是空數(shù)組(長度為0的數(shù)組)或空字符串.

范圍

Solidity繼承了JS的范圍規(guī)則—變量聲明后它就屬于整個函數(shù)而不是當(dāng)前代碼塊.例如:

pragma solidity ^0.4.16;

contract ScopingErrors {
    function scoping() public {
        uint i = 0;

        while (i++ < 1) {
            uint same1 = 0;
        }

        while (i++ < 2) {
            uint same1 = 0;// 非法, 第二次聲明 same1
        }
    }

    function minimalScoping() public {
        {
            uint same2 = 0;
        }

        {
            uint same2 = 0;// 非法, 第二次聲明 same2
        }
    }

    function forLoopScoping() public {
        for (uint same3 = 0; same3 < 1; same3++) {
        }

        for (uint same3 = 0; same3 < 1; same3++) {// 非法, 第二次聲明 same3
        }
    }
}

但是,從v0.5.0開始,Solidty的變量范圍會更改成遵循C99標(biāo)準(zhǔn),變量的可見性在它聲明出來直到{}代碼塊結(jié)束.作為此規(guī)則的一個例外,在for循環(huán)的初始化部分中聲明的變量只有在for循環(huán)結(jié)束前可見(和大多數(shù)語言一致).

下面這個例子不會產(chǎn)生任何警告,因為這兩個變量雖然有同樣的名字但是范圍不相交.在非0.5.0的模式,他們有同樣的scope(在function minimalScoping中)并且不會編譯通過.

pragma solidity ^0.4.0;
pragma experimental "v0.5.0";
contract C {
    function minimalScoping() pure public {
        {
            uint same2 = 0;
        }

        {
            uint same2 = 0;
        }
    }
}

Getter

所有state variables聲明為public時,編譯器都會為他們自動生成getter函數(shù),該函數(shù)與state variables的名稱一致.

對于普通類型(非數(shù)組,非mapping)的元素來說,如果聲明成public,直接調(diào)用與variables名稱相同的函數(shù)即可獲取它的值,

對于數(shù)組來說,會生成variableName(uint index)的函數(shù)來獲取對應(yīng)角標(biāo)的值:

例如:

pragma solidity ^0.4.0;
contract Getter{
    uint public x =20;
    uint[] public arr = [10,20,30];
    mapping (uint => uint) public map;
    
    constructor() public{
        map[3] = 13;
        map[4] = 14;
    }
}
//編譯器自動生成的getter

function x() view public{
    return x; 
}

function arr(uint index) view public returns (uint){
    return arr[index];
}

function map(uint key) view public returns (uint){
    return map[key];
}

再看編譯器會如何為一個復(fù)雜結(jié)構(gòu)的變量生成getter:

pragma solidity ^0.4.0;

contract Complex {
    struct Data {
        uint a;
        bytes3 b;
        mapping (uint => uint) map;
    }
    mapping (uint => mapping(bool => Data[])) public datas;
}

//例如上面的public datas,會自動生成如下的getter:
function datas(uint arg1, bool arg2, uint arg3) public returns (uint a, bytes3 b) {
    a = datas[arg1][arg2][arg3].a;
    b = datas[arg1][arg2][arg3].b;
}

提供Fallback Function

每個合約可以有并且只有一個沒有名稱的函數(shù),該函數(shù)就是Fallback Function(備用函數(shù)),它會在其他函數(shù)都匹配不上所給出的method selector時被調(diào)用(或者沒有提供任何數(shù)據(jù)時).

這個函數(shù)可以用于函數(shù)接受Ether,但它如果不被標(biāo)記為payable的話是無法接受Ether的,記得給每個Fallback Function提供payable標(biāo)記.

函數(shù)重載

在Solidity中也有函數(shù)重載的概念(函數(shù)名稱相同的參數(shù)類型不同或數(shù)量),例如:

pragma solidity ^0.4.16;

contract A {
    function f(uint _in) public pure returns (uint out) {
        out = 1;
    }

    function f(uint _in, bytes32 _key) public pure returns (uint out) {
        out = 2;
    }
}

如果兩個外部可見函數(shù)中的Solidity類型不同但是外部類型相同,則會有錯(見下面示例:Baddress雖然在Solidity中聲明出來是不同的,但實際上是一致的).

// This will not compile
pragma solidity ^0.4.16;

contract A {
    function f(B _in) public pure returns (B out) {
        out = _in;
    }

    function f(address _in) public pure returns (address out) {
        out = _in;
    }
}

contract B {
}

兩個f函數(shù)重載最終都接受了ABI的address類型,盡管他們在Solidity中是不同類型的.

注意

函數(shù)的返回參數(shù)不能作為重載的考慮因素.

主要類型相同的參數(shù)盡量不要重載,可能會引起一些不必要的錯誤,例如:

pragma solidity ^0.4.16;

contract A {
    function f(uint8 _in) public pure returns (uint8 out) {
        out = _in;
    }

    function f(uint256 _in) public pure returns (uint256 out) {
        out = _in;
    }
}

調(diào)用f(50)會創(chuàng)建一個類型異常,因為50可以被隱式轉(zhuǎn)換為uint8uint256.

合約繼承的構(gòu)造函數(shù)參數(shù)傳遞

所有基本合約的構(gòu)造函數(shù)調(diào)用需要遵循以下的線性規(guī)則.如果base構(gòu)造函數(shù)有參數(shù),派生合約需要指定這些參數(shù).能以以下兩種方式來完成:

pragma solidity ^0.4.22;

contract Base {
    uint x;
    //base contract的有參構(gòu)造
    constructor(uint _x) public { x = _x; }
}

//在聲明合約的時候指定base contract的構(gòu)造參數(shù)
contract Derived1 is Base(7) {
    constructor(uint _y) public {}
}

//在聲明構(gòu)造函數(shù)的時候?qū)ase contract constructor寫上
contract Derived2 is Base {
    constructor(uint _y) Base(_y * _y) public {}
}

第一種方式:直接在聲明繼承時傳入.如果構(gòu)造函數(shù)的參數(shù)是常量,那么使用這種更方便.第二種方式:像modifier一樣直接聲明在constructor后面.如果base contract構(gòu)造函數(shù)的參數(shù)依賴于derived contract構(gòu)造函數(shù)的參數(shù),那需要使用第二種.參數(shù)一定需要在繼承列表或以修飾符風(fēng)格寫在派生構(gòu)造函數(shù)后.同時在這兩個地方指定參數(shù)是錯誤的.

如果派生合約沒有將所有base contracts' constructors的參數(shù)指定出來,那么它將會是abstract的.

多繼承同名錯誤

如果一個合約多繼承中出現(xiàn)了function和modifier同名,這會視為是錯誤.這個錯誤同樣會因為一個event和一個modifier名字一樣引起,一個function和event也是不能同名的.作為一個例外,一個state variable的getter可以override一個pulic function.

Mapping

Mapping可以被看成hash tables,但EVM中的Mapping是被虛擬初始化出來的,并且擁有所有可能的key,以及這些key都映射到了其字節(jié)表示全為零的值.(ps:mapping中的key并不保存key本身,而是其keccak256的hash).因此,mapping并沒有長度以及類似key/value這種形式的set.

mapping只允許存在在state variables(或者內(nèi)部函數(shù)的storage引用中).

mapping是不支持遍歷的,但是可以基于它創(chuàng)建一個可遍歷的數(shù)據(jù)結(jié)構(gòu),例如 [iterable mapping](iterable mapping),另外也可以考慮自己設(shè)計一個與數(shù)組相關(guān)的數(shù)據(jù)結(jié)構(gòu)來記錄key以獲取value.

return struct

在合約中,內(nèi)部函數(shù)可以返回struct,外部函數(shù)(或public函數(shù))不能返回struct,如果想要返回,可以返回一組值,如下面示例:

contract MyContract {
  struct MyStruct {
    string str;
    uint i;
  }

  MyStruct myStruct;

  constructor() public{
    myStruct = MyStruct("foo", 1);
  }
  
  //internal function可以返回struct
  function returnStruct() internal view returns (MyStruct){
      return myStruct;
  }
    
  //目前版本public函數(shù)不能返回struct,但是可以使用`pragma experimental ABIEncoderV2;`讓編譯通過
  //function returnStructPublic() public view returns (MyStruct){
  //    return myStruct;
  //}

  function myMethod() external view returns (string, uint) {
    return (myStruct.str, myStruct.i);
  }
}

改變數(shù)組長度

我們可以使用arrayName.length = uint來改變storage中一個動態(tài)數(shù)組的長度,如果改變長度的時候報了一個錯誤:lvalue,我們可能因為下面兩個原因讓這個錯誤出現(xiàn):

1.在對memory中的array進行resize;

2.對一個非動態(tài)長度的數(shù)組進行resize;

int8[] memory memArr;       // Case 1
memArr.length++;            // illegal(memory的動態(tài)長度array不能resize)
int8[5] storageArr;         // Case 2
somearray.length++;         // legal
int8[5] storage storageArr2; // Explicit case 2
somearray2.length++;         // legal
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掠械,一起剝皮案震驚了整個濱河市猾蒂,隨后出現(xiàn)的幾起案子是晨,更是在濱河造成了極大的恐慌,老刑警劉巖蚊逢,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烙荷,死亡現(xiàn)場離奇詭異檬寂,居然都是意外死亡,警方通過查閱死者的電腦和手機匾旭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門亩码,熙熙樓的掌柜王于貴愁眉苦臉地迎上來描沟,“玉大人,你說我怎么就攤上這事泞遗∠玻” “怎么了佩伤?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵生巡,是天一觀的道長。 經(jīng)常有香客問我甸陌,道長盐股,這世上最難降的妖魔是什么疯汁? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任幌蚊,我火速辦了婚禮,結(jié)果婚禮上估蹄,老公的妹妹穿的比我還像新娘沫换。我一直安慰自己,他們只是感情好垮兑,可當(dāng)我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布系枪。 她就那樣靜靜地躺著私爷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捌浩。 梳的紋絲不亂的頭發(fā)上工秩,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天助币,我揣著相機與錄音眉菱,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛叉抡,可吹牛的內(nèi)容都是我干的褥民。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼撵颊!你這毒婦竟也來了倡勇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤夸浅,失蹤者是張志新(化名)和其女友劉穎帆喇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體预皇,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡深啤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年溯街,在試婚紗的時候發(fā)現(xiàn)自己被綠了呈昔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片友绝。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡迁客,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粘室,到底是詐尸還是另有隱情卜范,我是刑警寧澤海雪,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布奥裸,位于F島的核電站,受9級特大地震影響迷郑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜焚碌,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一十电、第九天 我趴在偏房一處隱蔽的房頂上張望鹃骂。 院中可真熱鬧罢绽,春花似錦、人聲如沸寝殴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽溯革。三九已至,卻和暖如春冈闭,著一層夾襖步出監(jiān)牢的瞬間豺裆,已是汗流浹背臭猜。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工蔑歌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揽碘,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓裸违,卻偏偏與公主長得像本昏,于是被迫代替她去往敵國和親涌穆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容