1. 合约A中引用合约B,是根据 import和路径 引用。一旦合约A编译完成,那么意味着合约B此时此刻的abi“骨架”也已经在合约A中“定型”,因而合约A后续对合约B的使用都是基于其内已经“定型的骨架”。合约A对合约B使用之前需要给合约A传入合约B(某个版本的)address 以构造出合约B的实例对象。
这里考虑一种意外情景:公司A的合约A编译完成后,而公司B未告知公司A的情况下临时升级合约B代码并部署然后将 合约B的address告诉公司A。公司A在部署完合约A后初始化了这个合约B的地址。
"临时升级合约B代码",有下面情况:
1)没有涉及到 合约ABI骨架 的改动:如 a. 仅仅修改了function中的逻辑而没有修改到function 的名字、返回值、可见性、modifier等。b. 合约B增加了function、modifier、变量,这些是兼容 之前版本骨架的
合约A引用合约B的function时,不会抛异常。
2)涉及到 合约ABI骨架 的改动。
合约A引用合约B的function时,会抛异常。
其实合约之间的引用调用,本质也是跟我们手动发起交易调用合约函数是一样的:只要骨架未变或者升级后骨架兼容之前版本的骨架,那么一般不会抛异常。
由上述可见:合约abi骨架尤其重要,否则会导致大量合约不得不升级,这是非常折腾的。这个“骨架”,就犹如 java中的接口,我们应该如何才能驾驭好?
2. 常见的中心化系统,“业务逻辑和数据存储隔离” 以及 “可重启来升级系统”,这非常方便系统升级。
而dapp,拿以太坊solidity合约编程做例子,是将数据放在合约中存储,即 业务逻辑和数据存储一体化。而且合约一旦部署则不可更改。这样子为后续合约系统升级增加了好多麻烦,如不得不设计复杂的架构模式以达到方便系统升级的目的,可是设计复杂起来,又产生 性能、调用链过长、gas消耗过多以及 代码冗余庞大 诸如此类的与业务无关的顾虑。
solidity中有没有一种成熟点的架构模式或框架使用下?可以让我们更专注业务开发?
3. 合约A中引用其他合约B,我应该保留的是 合约B地址,还是 合约B引用? 哪种好?
其实现在的我觉得,没区别。因为对于 已经编译部署的 合约A 而言,合约B的“abi骨架”已经定型的了。
4. 合约之间的函数外部调用,目前EVM不支持 外部函数调用接收 变长数组。
如上图,this.f()属于 外部函数调用,而f()返回的是 变长数组uint[] (Runtime才知道), 所以无法通过编译。没办法,EVM目前能力有限。但是,只要不是 合约之间的外部调用,而是 web3j之类的调用f(),可以返回变长数组uint[]。
因此,涉及到外部调用时候,如果可以提前确定数组长度,则应该写死。可是如果真有变长数组的情况,那么我们应该怎么办?有些前辈的做法是: 构造一个超长的数组并写死长度 作为返回值。这样就可以包含我们想要返回的数组的所有元素了。可是这样有点笨.........
5. array\mapping等集合类的声明,默认采取的是storage类型,即指针引用。然而对于重要敏感的集合类型变量,我们不可以暴露指针给外部合约,防止作恶。用memory则意味着复制一份集合类型变量再返回。
6. 对于public变量,默认有getter方法,我们可以直接获取其值。
那么,合约a 在function中可否 修改合约b的 public变量??
如: function afunc(){
contractB.publicParam = x ; // 和java一样,直接修改了 合约B的public成员变量
}
答:不会,如下:
因为getter的使用是要 以function的形式。所以无法被第三方赋值。
7. solidity中不可以在struct结构体中直接定义自己作为一个类型。
//Error: Recursive struct definition.
原因如下: 如果将 自定义类型 又作为 自己的成员变量,那么此结构体的大小是无限的。
由上图,可以在 array\mapping 中引用自己类型,这是因为,array\mapping 作为 结构体的成员变量,数量是用限的,不会出现递归。
8. solidity中不允许直接暴露struct结构体给外部合约使用。
9. solidity中没有null的概念,那么我们如何针对 结构体、字符串、数组、mapping进行“判空”?
答案在此:https://ethereum.stackexchange.com/questions/11618/how-to-test-if-a-struct-state-variable-is-set
https://ethereum.stackexchange.com/questions/871/what-is-the-zero-empty-or-null-value-of-a-struct?rq=1
https://ethereum.stackexchange.com/questions/5683/what-is-the-zero-value-for-a-string
总结如下:
总得来说,“空”在 solidity中指各类型的各自的zero-value,如 uint 类型的zero-value为0 。
1. 如何对 string 判空,通常是将其字节化然后判断其length是否为0;
其实,是不是说 string类型的 zero-value 为 “” ??
string x ;
if( bytes(x).length == 0 ){......}
另外也可以如此:
if (sha3(myVariable) != sha3("")) {
// is not empty string
}
不知道可否:
if( x.length == 0 ){...}
2. 对于结构体,solidity中不需要new 也可以直接访问其成员变量,可是其成员变量均为空。
如:
Transaction private tx;
struct Transaction {
address to;
uint value;
bytes data;
}
即使 tx 从没有赋值,我们也可以直接 访问其成员变量: tx.to ,不过值为 本类型的zero-value;
3. 我们知道,在solidity中 array, mapping似乎是 声明了就可以直接使用。
mapping(string =>Transaction ) txMapping ;
...
//key 为 “xx” 对应的 value (结构体Transaction,见上)其实从来没有赋值。然而我们依然可以直接访问 这个“虚无的”value结构体 的成员。
if (txMapping["xx"].value == 0) {...}
10. 在solidity中,结构体、array、mapping 似乎都是只需要声明完就可以直接访问其属性了??
在 function 内,不可以声明 mapping , 原因是 memory空间是 数组,而mapping是storage空间的专属类型。??????
生成一个 struct时,入参会忽略跳过 mapping成员。因为mapping成员默认是自动创建和初始化的??????
evm的存储模型是怎么样的? 在代码中要如何结合思考?
11. 什么时候用 memory ,什么时候用storage ???
左右为难啊.......
解决方案:不知其解??
12. 入参太多,编译出错??
13. struct 不可见,如何返回批量(如数组)??
我本想返回
14.library 一旦部署,可以被任何组织机构、项目的合约delegateCall而引用。这样可以避免大量同质的lib部署,一来节省大量同类重复部署需要的gas,二来减少evm中合约的泛滥污染,三是增强安全性:事实上,多个合约依赖于一个现存的代码,可以让营造一个更安全的环境。因为一方面这些方面有良好的代码审查(比如,Zepelin所做的伟大的工作),以及这些代码经过线上实际运行的验证。比如在这个案例中,一个打算最大融资五千万的项目,ERC20代币的问题被查出来了。
1)library 中的struct 可以被引用它的合约引用,可以作为 function的入参。
困惑??
如果说是因为库被delegateCall方式调用而允许 库中的struct 被引用在 合约中的话,那么 库中的struct 用在 function做入参(见 库incremented(Counter storage self ))不会有问题吗??
不会有问题。因为 合约调用库function,都是在 合约本身的function中调用。库function不会直接暴露。库被引用,可以理解为 运行时合约自身的代码,这也是为何库struct 也可以在 合约中直接访问的原因。也因为这个原因 库incremented(Counter storage self ) 声明internal的意义不大。
库中的 this,都是指 delegateCall的合约实例。虽然说 using 库 for xType, 可是xType 没有实例的概念,因此也没有this的概念,所以 this 就指 所在的合约实例。
2)library 直接没有继承概念,不过可以 类似“合约引用库”这样 互相引用。"库引用库"
3) 我想引用 建设银行在evm部署好的一个 library,请问我应该怎么做??
猜想??:整个以太坊看做一台电脑,那么它也是有统一的文件系统。这样子的话, 那么在Evm上的部署的项目都是在这个文件系统中。类比pc的文件系统,其实evm的项目目录也是有统一的根目录这样子。如果真是这样的话,library的共享delegateCall就合理了。我们只需要将library开源代码目录结构按照 路径放在我们自己的项目中,这样目录就和 evm中的对上了。可是还是有很多问题:既然同目录为何没有覆盖替换的概念?
4) 库的设计,是为了项目代码复用、甚至整个Evm共用同一套库代码。
调用library库函数有两个方式:
a. lib.func(**)
类似java中的静态方法的调用格式:类.静态方法(***)
b. using lib for xType ; 或 using lib for *;
将lib的function附着到 xType类型中,从而达到代码复用的目的。
注意:这种“附着”方式,默认原则是 将 调用类型实例对象(基本类型则是自己) 作为 function的第一个参数。如果参数不匹配,都会报错。因而这里有一个盲区:下图中,i.toUint() 是 调用了 toUint(uint a)!! 因为 i 是会默认作为第一个参数传入的,这是必须的。如果 Utils中没有 toUint(uint a) ,那么就无法 将 i 作为第一个参数传入 了,就会报错!!
也是因为这个语法糖的特点,所以建议对 库函数的 第一个参数 命名为 self 。
15. storage 和memory
复杂类型变量占空间大,占用的空间通常超过256位, 拷贝时开销很大,同时需要考虑及时回收的问题。因此evm设计了 两种存储:memory 和 storage机制。
1) storage 是永久存储,memory是临时存储。
2) evm两种类型:值类型 和引用类型。值类型变量之间赋值,都是 值传递(拷贝出一份独立)。而复杂类型变量,如array\struct\mapping变量之间赋值则有两种:值传递(拷贝出一份独立) 和 引用传递。
3)所有的复杂类型如数组(arrays)和结构体(struct)有一个额外的属性:数据的存储位置(data location)。可为memory和storage。基本类型则类似java等语言的机制。
a. 函数入参和返参,复杂类型默认是memory。
b. 局部复杂类型变量(local variables)和 状态变量(state variables) 默认是storage。
??不明白为何 局部复杂类型变量(local variables) 会设计成 storage ??
4) storage 变量 之间赋值:引用传递;
memory 变量 之间赋值:引用传递;
storage 变量 和 memory 变量 之间赋值:传值,就是拷贝出独立一份,之后就各不相关;
memory 变量 不可以赋值给局部复杂类型变量(storage local variables) !!??
这点真是令我感觉不解?!!为何 memory变量 可以赋值给 状态复杂类型变量(storage),却不可以赋值给 局部复杂类型变量(storage)
我的尝试,针对 复杂类型(array\struct):
a. 复杂类型 作为函数入参和返参 时,复杂类型默认也是memory,可以显式声明为storage 。
b. 复杂类型 作为函数体内的局部变量 时,复杂类型默认也是storage 。
c. memory复杂类型变量 ,不可以赋值给局部复杂类型变量(storage) 和 作为函数入参的storage 复杂类型变量。
d. 也就是说,memory复杂类型变量,仅仅可以赋值给状态复杂类型变量(storage) !!
这些坑爹的限制,会经常在 复杂类型赋值环节 绊倒我们,因为我们不仅仅要考虑是否可以成功赋值,还要顾虑 调用栈里的各函数在入参时会拷贝过多而导致的性能、浪费问题!!!我觉得,只要一个 memory复杂类型 最终是要 永久存储到evm的 (如 新增一条业务数据最终是需要存储的 ),那么我们很应该一开始就将之 “storage化”后再进入 调用栈中。这样“一开始就将之storage化”其实更方便了我们编程,因为storage变量 是可以无限制地 赋值给 memory变量的。因此,大胆storage化 吧!!
当然也有人问:函数返参struct,我们应该显式声明storage,还是直接默认memory?
顾虑这问题的开发们,往往是担心返回 storage变量 会被恶意修改存储。这是因为开发的思维还停留在java语言。
其实这不用顾虑,因为 既然 struct作为返参,那么 此函数肯定是 internal了,即别的合约无法external访问的。
注意:struct中含有mapping时,当storage 的struct变量赋值给memory struct变量,其mapping是不会拷贝的。因此当访问 memory struct变量 中的mapping 是会编译出错的。
5) 我们在声明 复杂类型变量时,有些情形可以修改 其默认数据位置。如下图,
1)函数入参回参 默认是memory ,可是我们可以通过声明 storage 改变其默认位置。
2)复杂类型的局部变量,默认是 storage,可是我们可以通过 memory 声明改变其默认位置。这样,就可以将 memory复杂类型入参 赋值给 memory复杂类型局部变量 了 。
详细介绍如下:
memory赋值给局部变量
综上,storage复杂类型变量,只能被storage复杂类型变量赋值 。
16. 定长字节数组,非常好用哦!
1) byte 是基本数据类型,bytesN 是数组,复杂类型。
byte == bytes1
2) 任何基本类型 本质都是 二进制字节组合。所以 任何基本类型 都可以 赋值给 bytesN,只要N足够大(即字节数组空间足够大)。
3) 定长字节数组 和 其他基本数据类型 之间交互案例:
可以看到,bytesN 的设计 和 其他语言中的字节数组不一样的地方:基本数据类型 可以直接赋值 给 字节数组!
4)
18.Solidity封装了两种函数的调用方式internal和external。
Solidity 中函数 提供了 external \ public \ internal \ private 这四种可见性声明。
注意:1. 别弄混了 调用方式(internal和external) 和 可见性声明(internal和external)。
2. 可见性声明的函数中,external 和 public 函数是采用消息调用的方式;public \ internal \ private 函数 是 internal调用方式。
3. internal调用,实现时转为简单的EVM跳转,所以它能直接使用上下文环境中的数据,对于引用传递时将会变得非常高效(不用拷贝数据)。
4. external调用,实现为合约的外部消息调用。所以在合约初始化时不能external的方式调用自身函数,因为合约还未初始化完成。
疑问??为何external不可以让 当前合约内的函数直接访问??
19. 数组:
a.编译期定长数组 ,如uint[12], bytes3 等等;
b.变长数组,如uint[]\bytes\string 等等;
20.
疑问??
stateVar[stateVar.length++] = a;
是否是拆解成:
stateVar[stateVar.length] = a;
stateVar.length++ ;
21. 对于memory的变长数组,不支持修改length属性,来调整数组大小。memory的变长数组虽然可以通过参数灵活指定大小,但一旦创建,大小不可调整。
22. 多维数据的定义与非区块链语言类似。如,我们要创建一个长度为5的uint数组,每个元素又是一个变长数组。将被声明为uint[][5](注意,定义方式对比大多数语言来说是反的,使用下标访问元素时与其它语言一致)。
//一个变长的数组,里面的每个元素是一个长度为2的数组。
//定义方式与常规语言相反
bool[2][] flags;
23. bytes 等同于 byte[], length自增\push()使用 和 byte[]无异。
string 虽然是变长数组,可是 无法通过 length自增、push() 来扩容。
24. 由于bytes与string,可以自由转换,你可以将字符串s通过bytes(s)转为一个bytes。但需要注意的是通过这种方式访问到的是UTF-8编码的码流,并不是独立的一个个字符。比如中文编码是多字节,变长的,所以你访问到的很有可能只是其中的一个代码点。
25. 状态变量:struct数组,为何就会出错??
难道
26. struct 类型变量,目前还不可以跨合约传递,所以折中方案是拆解到 一个Bean性质的合约中。
27. 我对 Mapping 非常无语!
mapping 只需要是 状态变量!!
由上,mapping 仅仅被允许storage声明!! 如果显式声明为memory 则肯定错!
然而这样为何又报错?
storage 声明只应用在 数组和struct 类型的变量上,不允许显式应用在 mapping变量。但是局部变量mapping 的的确确又是 storage ,为何就不允许显式声明呢?真是奇怪
??死记先: mapping 只能是状态变量,且不可以显式声明storage、memory !!
28. 我觉得应该是:
Type is required tobe able tolive outside storage.
29. 简单总结下 :(采坑死记,原因不解?)
a. solidity语言中“容器”性质的类型,就三个:array、struct、mapping。由于 数据位置特性 导致的转换问题 都是发生在它们身上。
b. mapping 只能声明为 状态变量 或 局部变量,不可以声明在 函数入参和返参 上。且当 局部变量mapping 还不可以 显式声明为 storage !(原因不解??)
c. 从b可以总结出,
1. storage显式声明,只能用在 array、struct 上了。
2. 函数入参和返参 ,只允许是 基本数据类型 和 array、struct 。struct作为 入参和返参 还强制需要将函数定义为 internal 。
对于传值的基本数据类型,爱咋搞就咋搞,反正是传值。
30. solidity中变量声明后都会有初始值,就是“零态”。solidity中没有 null 或 undefined 的语义。下面是各基本类型的初始值,就是“零态":
在上述例子中,
对于基本数据类型,bool的默认值为false,bytes32的默认值为32字节长的0。
疑惑:那么 uint8[5] x ; x[0] 默认是0 吗??
对于引用类型,动态数组bytes类型默认值为空字节数组,string为默认值为空串,动态数组uint8[] memory arr为空数组。都是空字节!运行时异常:Exception during execution. (invalid opcode)
31.引用类型变量初始化:
1. 动态数组:需要new 分配空间才可使用,否则报错:Exception during execution. (invalid opcode)
2. mapping \ enum\ stuct不需要显式通过 new 分配空间,可以直接使用!
其中,
mapping声明即可用,没有任何k-v; 访问m[k],只会返回 v类型的 ”零值“
enum 声明即可用,默认值将为0。即顺位第一个值。
struct 声明即可用,默认值将为基本类型的默认值。
32.delete 的用法
使用delete操作符会将对象重置为默认值。
1)对于基本类型,使用delete会设置为对应的初始值
2)删除枚举类型时,会将其值重置为序号为0的值。
3)删除一个结构体,会将其中的所有成员变量一一置为初值
4)删除 mapping,无效果! 删除 mapping中的一个k-v,有效果!!
5)删除 struct中的 mapping,也是无效果!删除动作会直接跳过mapping成员。
6)删除 定长数组:会将各数组元素 重置为 零值。
7)删除 变长数组:则是将长度变 0 !(所有元素都砍掉!不要了)
这里有一个性能优化的方案:定长数组重用 可以通过delete后直接重用,因为空间不变;而变长数组,delete后长度是置0 的,也就是说 又要重新new了,干脆就不重用变长数组了。
8) 删除数组的一个元素,会留个坑那里。
删除的注意事项:
就是说,deletestorage局部变量(动态),需要先判空咯!
??? storage局部引用,是什么一个存在?? 和 传统语言中的 局部引用的理解 完全不同啊??
33.函数作用域
变量无论在函数内什么位置定义,其作用域均为整个函数,而非大多数据语言常见的块级作用域。下面的例子会报错Identifier already declared。
34.var 可以 被赋值 变长类型;变长类型 无法接收 var。
可是, var 和 基本
这也可以看出 solidity的不灵活。
35. solidity中没有null/undefined 之类的概念,所以都是返回默认值(零态值)。例如 uint , 函数返回0。这种情况下,我们要注意 零态值 和 业务值 重合时的区分。
36. 函数修改器 使用一览
注意点:1. 第一个 Testx1 , 由于 false, 所以永远无法进入函数体。这种情况下,solidity 是默认返回 所调用函数的 return 值类型 的 “零态值”的。
2. _; 意思是:向下传递 的意思。当只有一个 modifier 时,则是指函数体;当有多个时,则是指按顺序的下一个modifier。
3. 子合约可以重写父合约的modifier。
37. Event
event luoEvent(string indexed idcard, string indexed name, uint indexed age, string blogContent);
1. event , 在执行过程中,被称为:事件。可是事件发生时,就作为 日志 永久存在区块链上。
2. 一个合约发生过的所有event 是跟合约一起storage在evm上的。
3. event 声明中的 indexed, 是指 为修饰的字段建立索引,以太坊中称其为: topic。因为Topic是用于快速查找的,不能存任意长度的数据。
solidity中event最多只有3个indexed 字段,即 最多3个索引字段,或 最多3个主题topic。
4. 日志, 分为两部分存储: topic主题部分 和 data。
topic 部分,对于固定大小的类型数据字段,是直接存储的。可是对于 string, bytes,数组 这些非固定大小的类型字段,则是存储其字段字节的hash值(固定大小了)。如上面案例中的 idcard 和 name 字段,都是存hash值而已。业内案例:https://ethereum.stackexchange.com/questions/6840/indexed-event-with-string-not-getting-logged
data部分, 可以直接存储任意长度的类型。如案例中的 blogContent,一遍博文都可以。可是注意的是,记录日志 也是消耗gas的,只是极小量而已:日志每字节花费8gas,然而合约存储每32字节花费20000gas。
也由于 非固定大小的类型字段在topic存储hash,所以,当我们在 web3.js 中过滤 非固定大小的类型字段 时,是要以其 hash 值作为过滤条件的;否则无法过滤。
5. event的应用场景:1) 充当异步的数据触发器:用户提交了transaction后,界面UI的脚本一直监测合约的事件日志,一旦监测到(交易被打包入块了),则UI提示成功。如(当用户存款后,UI进行对应的更新)
2)将事件记录为日志,以一种低成本的存储的形式使用。去中心化交易所的用户登录时,通过检索过滤出用户的所有历史操作日志,比 “将用户操作日志记录存放到智能合约” 节省成本。
38.继承
继承中,容易忽略一种情况: 状态变量的getter 函数,刚好 和 某个函数 同名了。这是不允许的。
39.多继承
注意:1)出现重名函数,按照“最远继承原则”
2)指定调用父合约方法
虽然存在最远继承原则,但是我们仍可以在子合约中主动调用父合约被重写的方法来触发被覆盖的函数。
3)super 关键字
和 java中的不同。super.func(), 意味着:将 多继承中的所有父合约中的 和 func 函数同名的 都执行一遍。具体执行顺序不清楚??需要谨慎使用!如果出现不可把握的情况,可以直接 “指定调用父合约方法” 的方式。
40. 如何判断一个address上是否部署了合约?
41. interface 在 合约设计 环节是非常重要的。
1. 使用方式:Interface(address).doSth();
2.当合约设计出现“交叉引用”时会无法编译和部署,而接口 可以很好地帮我们解决这个问题。“ContractA引用ContractB”的设计,转化成 “ContractA引用ContractB的接口”,然后通过传入ContractB的地址来按照:Interface(address).doSth() 来间接调用ContractB。
其实这个和java的接口使用有点类似,只是java中只需要往 “主引用类中注入被引用类的对象一次即可”,而合约中则是每次都要以Interface(address).doSth() 的方式 调用 “被引用合约”。
酱紫设计, 主引用合约 不需要写死 被引用合约,因此 被引用合约 的升级时,我们只需要往 主引用合约 中设置 被引用合约 的新地址即可。
案例:https://ethereum.stackexchange.com/questions/9189/contract-design-interfaces
设计可升级合约项目:https://blog.imaginea.com/interfaces-make-your-solidity-contracts-upgradeable/
https://ethereum.stackexchange.com/questions/2404/upgradeable-smart-contracts
3. interface往往会定义好 event。这就犹如 java接口中我们往往会定义好常量 一般。
42. 合约public函数,无法返回struct;
合约函数,可以返回动态数组;
合约调用合约,无法接收动态数组;
js调用合约,可以接收到动态数组;
那么,对于需要批量返回struct(数组)的场景,我们如何处理??
1)返回一个struct时,可以将struct字段作为函数返参;
返回批量struct时,可以将struct字段数组作为函数返参;
2)将 struct字段字节化,以特殊字符分隔,存入一个大字节数组中;
43. solidity中,字符串拼接 不是用 + !!
https://ethereum.stackexchange.com/questions/729/how-to-concatenate-strings-in-solidity
先将各个string转化成字节数组,再合并到一个更大的字节数组,然后转会string 。
44. 可以考虑用js、java写一个工具,方便将struct 的字段拼接成json格式。
按照网上说法,不建议在 evm 中进行 struct->json之类的操作,因为非常expensive !!
不过,在 bcos平台,没有gas成本负担,可以随便搞!
https://ethereum.stackexchange.com/questions/6121/parse-json-in-solidity
45. json字符串 小军刀:
https://github.com/eBusinessMan/jsmnSol
Usage: 对 json格式的string 进行操作。
在 evm里面对string json操作非常费gas!不过对于一小段json的操作 ,还是可以接受的!
46. solidity中,抽象合约 和 普通合约 的声明是一样的,不跟java般得显式声明 abstract。不过,当我们错误地new 一个抽象合约时,就会出现编译错误。
47. solidity中,interface的用法,跟java中的很类似:控制反转。合约A 引用 合约B的接口,而不是 合约B本身: ContractBInterface( contractBAddress ),我们只需要传入 contractBAddress 即可 。
这种用法的好处明显:1) ContractBInterface( contractBAddress ) 的方式可以方便 具体合约 的升级; 2)如果不借用接口,当 合约之间出现交叉引用时,是无法部署的。3)接口的出现,也方便了 分工开发嘛!(定义了接口,就各自开发即可。)
48. 定长数组 为何不可以赋值给 不定长数组变量??
49. solidity 没有 += ???
50. 对于默认生成的Getter,在合约交互时,我们要留意返回值中是否有变长类型。
这种情况尤其出现在 对struct的getter访问时要考虑的。
上面的struct 如下:
下面是我的探索:
就是说,默认的getter中,struct中的string\string[]\mapping 是无法返回给调用合约的!而且 即使是struct中的定长数组都是没法直接返回的。这非常尴尬。
面对这种情况,我们能解决的是,在原合约中再写一个Getter的Function 将原始getter封装并返回需要的数据。(我也很无奈。。。)