贡献方式及常见问题——Solidity中文文档(13)

image

写在前面:HiBlock区块链社区成立了翻译小组,翻译区块链相关的技术文档及资料,本文为Solidity文档翻译的第十三部分《贡献方式及常见问题》,特发布出来邀请solidity爱好者、开发者做公开的审校,您可以添加微信baobaotalk_com,验证输入“solidity”,然后将您的意见和建议发送给我们,也可以在文末“留言”区留言,有效的建议我们会采纳及合并进下一版本,同时将送一份小礼物给您以示感谢。

贡献方式

对于大家的帮助,我们一如既往地感激。

你可以试着 从源代码编译 开始,以熟悉 Solidity 的组件和编译流程。这对精通 Solidity 上智能合约的编写也有帮助。

我们特别需要以下方面的帮助:

1

怎样报告问题

请用 GitHub issues tracker(https://github.com/ethereum/solidity/issues) 来报告问题。汇报问题时,请提供下列细节:

  • 你所使用的 Solidity 版本

  • 源码(如果可以的话)

  • 你在哪个平台上运行代码

  • 如何重现该问题

  • 该问题的结果是什么

  • 预期行为是什么样的

将造成问题的源码缩减到最少,总是很有帮助的,并且有时候甚至能澄清误解。

2

Pull Request 的工作流

为了进行贡献,请 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 之前,请测试你的代码并确保它能在本地进行编译。

感谢你的帮助!

3

运行编译器测试

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

image

4

通过 AFL 运行 Fuzzer

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 工具来过滤重复的错误。

5

Whiskers 模板系统

Whiskers 是一个类似于 Mustache(https://mustache.github.io/) 的模板系统。编译器在各种各样的地方使用 Whiskers 来增强可读性,从而提高代码的可维护性和可验证性。

它的语法与 Mustache 有很大差别:模板标记 {{ 和 }} 被替换成了 < 和 > ,以便增强语法分析,避免与 内联汇编 的冲突(符号 < 和 > 在内联汇编中是无效的,而 { 和 } 则被用来限定块)。另一个局限是,列表只会被解析一层,而不是递归解析。未来可能会改变这一个限制。

下面是一个粗略的说明:

任何出现 的地方都会被所提供的变量 name 的字符串值所替换,既不会进行任何转义也不会迭代替换。可以通过 <#name>… 来限定一个区域。该区域中的内容将进行多次拼接,每次拼接会使用相应变量集中的值替换区域中的 项,模板系统中提供了多少组变量集,就会进行多少次拼接。顶层变量也可以在这样的区域的内部使用。

译者注:对于区域<#name>…的释义,译者参考自:https://github.com/janl/mustache.js#sections

常见问题

这份清单最早是由 fivedogit 收集整理的。

1 基本问题

可以在特定的区块上进行操作吗?(比如发布一个合约或执行一笔交易)

鉴于交易数据的写入是由矿工决定的而不是由提交者决定的,谁也无法保证交易一定会发生在下一个或未来某一个特定的区块上。这个结论适用于函数调用/交易以及合约的创建。

如果你希望你的合约被定时调用,可以使用: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);
   }

}

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 = ; 来调整 存储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官方文档(2)

根据例子学习Solidity-Solidity官方文档(3)

深入理解Solidity之源文件及合约结构——Solidity中文文档(4)

安全考量——Solidity中文文档(5)

合约的元数据——Solidity中文文档(6)

应用二进制接口(ABI) 说明——Solidity中文文档(7)

使用编译器——Solidity中文文档(8)

Yul语言及对象说明——Solidity中文文档(9)

风格指南——Solidity中文文档(10)

通用模式——Solidity中文文档(11)

已知bug列表——Solidity中文文档(12)

点击“阅读原文”即可查看完整中文文档

image

:本文为solidity翻译的第十三部分《贡献方式及常见问题》,特发布出来邀请solidity爱好者、开发者做公开的审校,您可以添加微信baobaotalk_com,验证输入“solidity”,然后将您的意见和建议发送给我们,也可在文末“留言”区留言,或通过原文链接访问我们的Github。有效的建议我们会收纳并及时改进,同时将送一份小礼物给您以示感谢。

本文内容来源于HiBlock区块链社区翻译小组,感谢全体译者的辛苦工作。点击“阅读原文”即可查看完整中文文档。

以下是我们的社区介绍,欢迎各种合作、交流、学习:)

image

猜你喜欢

转载自blog.csdn.net/HiBlock/article/details/81569998