一.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)
ifTi
is dynamic for some1 <= 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
,如果我們想傳入69
和true
來調(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ù) - booleantrue
, 用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 signaturesam(bytes,bool,uint256[])
. Note thatuint
會被替換成規(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)類型uint256
和bytes10
,他們會直接將他們自己穿進去,而對于動態(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é)下來就是:
- method ID
- 依據(jù)參數(shù)順序依次進行編碼,如果是定長類型,直接encode,如果是動長類型,存入其開始的偏移位置
- 依次展示動長類型的長度以及他們的數(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);
中的from
和to
) - 長度不定的二進制數(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ù)蒿涎。
-
storage
和memory
關(guān)鍵字用于在存儲和存儲器分別引用數(shù)據(jù)惦辛。 - 合同存儲在合同構(gòu)建期間預(yù)先分配,不能在函數(shù)調(diào)用中創(chuàng)建玻淑。畢竟呀伙,如果要保留函數(shù)剿另,在函數(shù)存儲中創(chuàng)建新變量沒有多大意義。
- 在合同構(gòu)建期間不能分配內(nèi)存谚攒,而是在函數(shù)執(zhí)行中創(chuàng)建戚篙。合同狀態(tài)變量始終在存儲中聲明岔擂。同樣浪耘,擁有不能持久的狀態(tài)變量也沒有意義。
- 將
memory
引用數(shù)據(jù)分配給storage
引用變量時七冲,我們將數(shù)據(jù)從內(nèi)存復(fù)制到存儲规婆。沒有創(chuàng)建新存儲抒蚜。 - 將
storage
引用數(shù)據(jù)分配給memory
引用變量時,我們將數(shù)據(jù)從存儲復(fù)制到內(nèi)存嗡髓。分配了新內(nèi)存。 - 當(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é)論:
-
memory
與storage
指定data location
變量引用的內(nèi)容 -
storage
不能在函數(shù)中新創(chuàng)建。storage
函數(shù)中任何引用的變量總是指在合同存儲(狀態(tài)變量)上預(yù)先分配的一段數(shù)據(jù)声功。函數(shù)調(diào)用后任何突變都會持續(xù)存在先巴。 -
memory
只能在函數(shù)中新創(chuàng)建伸蚯。它可以是新實例化的復(fù)雜類型,如array / struct(例如通過new int[...]
)摇幻,也可以從storage
引用的變量中復(fù)制绰姻。 - 由于引用是通過函數(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
,uint
和int
的默認(rèn)值是0
.
對于靜態(tài)長度的數(shù)組以及從byte1
到byte32
這些類型來說,它們存儲的每個元素的默認(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類型不同但是外部類型相同,則會有錯(見下面示例:B
和address
雖然在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)換為uint8
和uint256
.
合約繼承的構(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