Solidity:语言的再学习
1. String
pragma solidity ^0.5.0;
contract DynamicString{
string name = 'yuanshanshan';
function getStrLength() view public returns(uint){
return bytes(name).length;
//长度的获取,只能通过bytes(name).length
}
function getName() view public returns(bytes memory){
// return name[0]; 不能通过下标的形式获得字符串里面的内容
return bytes(name);
}
function changName() public {
bytes(name)[0] = "l";
//改变其中的一个char,必须通过上述的方式
}
}
一个汉字在string中占用三个字节;特殊字符占用一个字节。
bytes 转换成string
pragma solidity ^0.5.0;
contract fixToDanamic{
//0xe8a281e5a797e5a797
bytes gender = new bytes(3);
function init() public{
gender[0] = 0xe8;
gender[1] = 0xa2;
gender[2]= 0x81;
//袁
}
function bytesToString() view public returns(string memory){
return string(gender);
}
}
不可以通过bytes2 name = oxe8a281,直接通过string 来实现。
更为通用的方法是:
bytes9 name = 0xe8a281e5a797e5a797;
function bytesToString2() view public returns(string memory){
bytes memory newName = new bytes(name.length);
for(uint i = 0; i < name.length; i++){
newName[i] = name[i];
}
return string(newName);
}
2. modifer
modifer 函数修饰器,也就是函数体运行之前,先执行,所以叫修饰器。
修饰器是一种可被继承合约的属性,同时还可以被继承合约重写(Override)。
可以看成是大于号路线执行。
3.Solidity中的Revert(),Assert()和Require(),以及EVM中的新REVERT操作码
Solidity版本0.4.10的发行版引入了assert(),require()和revert()函数。
特别是,assert()和require()“保护”函数提高了合同代码的可读性,但区分它们可能会造成很大的困扰。
在本文中,我将:
- 解释这些功能解决的问题。
- 讨论Solidity编译器如何处理新的assert(),require()和revert()。
- 给出一些经验法则来决定如何以及何时使用每一个。
Solidity中错误处理的模式旧方法:
throw和if…throw模式
假设您的合同有一些特殊功能,只能由指定为所有者的特定地址调用。
在Solidity 0.4.10之前(以及一段时间之后),这是强制执行权限的常见模式:
contract HasAnOwner {
address owner;
function useSuperPowers(){
if (msg.sender != owner) {
throw; }
// do something only the owner should be allowed to do
}
}
如果useSuperPowers()函数由所有者以外的任何人调用,则该函数将引发返回无效的操作码错误,撤消所有状态更改并用尽所有剩余gas的情况。
现在已弃用throw关键字,最终将其完全删除。幸运的是,新函数assert(),require()和revert()提供了相同的功能,并且语法更加简洁。
投掷后的生活
让我们看看如何使用我们的新防护功能更新… throw模式。 这行:
if(msg.sender != owner) {
throw; }
当前的行为与以下所有行为完全相同:
if(msg.sender != owner) {
revert(); }
assert(msg.sender == owner);
require(msg.sender == owner);
请注意,在assert()和require()示例中,条件语句是if块条件的反转,将比较运算符=切换为==。
区分assert()和require():
首先,为了在您的脑海中分离出这些“防卫”功能,请将assert()想象为过于自信的恶霸,他会窃取您的所有gas。然后,将require()想象为一种礼貌的管理类型,他会指出您的错误,但会更宽容。
有了助记符,这两个功能之间的真正区别是什么? 在Byzantium网络升级之前,require()和assert()实际上表现相同,但是它们的字节码输出略有不同。
- assert()使用0xfe操作码导致错误情况
- require()使用0xfd操作码导致错误情况
如果您在黄纸上查找这些操作码中的任何一个,则找不到它们。这就是为什么您会看到无效的操作码错误的原因,因为没有关于客户端处理方式的规范。 但是,这将在Byzantium和以太坊虚拟机中实施EIP-140:REVERT指令后发生变化。然后,0xfd操作码将被映射到REVERT指令。
这是我真正着迷的地方:
自0.4.10版以来,已经部署了许多合同,其中包括处于休眠状态的新操作码,直到不再有效为止。在指定的时间,它将醒来,并变为REVERT!
注意:throw andrevert()也使用0xfd。之前是0.4.10。抛出使用0xfe。
REVERT操作码会做什么
REVERT仍将撤消所有状态更改,但是与“无效操作码”的处理方式有两种:
- 它将允许您返回一个值。
- 它将把剩余的gas退还给caller。
1. It will allow you to return a value
大多数精明的合约开发人员对众所周知的无用的无效操作码错误非常熟悉。幸运的是,我们很快将能够返回错误消息或与错误类型相对应的数字。
**也就是说我们可以在revert(“errr”)插入信息,能够提醒开发者出现错误。**如下代码:
revert(‘Something bad happened’);
or
require(condition, ‘Something bad happened’);
//上面一行,相当于这么写:
if(!condition) {
require("Something bad happened")}
2. Refund the remaining gas to the caller
当前,当您的合同抛出时,它会耗尽所有剩余的天然气。这会导致对矿工的慷慨捐赠,并且常常最终使用户付出大量金钱。
一旦在EVM中实现了REVERT,将不再使用原先的坏习惯,他将会来退还多余的天然气。
重点来了,看了半天了
Choosing between revert(), assert() and require()
因此,如果 revert()和require()都退还任何剩余的gas,并允许您返回一个值,为什么要使用assert()消耗gas?
为了稍微澄清一下:require()语句失败(与revert()相同)应被视为正常且健康的情况。当assert()语句失败时,就会发生非常错误和意外的错误,因此您需要修复代码。
通过遵循此指南,静态分析和形式验证工具将能够检查您的合同,以发现并证明可能破坏您的合同的条件,或证明您的合同按设计无缺陷地运行。
在实践中,我使用一些试探法来帮助我确定哪种方法合适。
Use require()to:
- Validate user inputs ie. require(input<20);
- Validate the response from an external contract ie.
require(external.send(amount));
- Validate state conditions prior to execution, ie.
require(block.number > SOME_BLOCK_NUMBER) or require(balance[msg.sender]>=amount)
- Generally, you should use require most often
- Generally, it will be used towards the beginning of a function。
我们的智能合约最佳做法中有很多将require()用于此类事情的示例。
https://github.com/ConsenSys/smart-contract-best-practices
Use assert() to:
- Check for overflow/underflow, ie. c = a+b; assert(c > b)
- Check invariants, ie. assert(this.balance >= totalSupply);
- Validate state after making changes
- Prevent conditions which should never, ever be possible
- Generally, you will probably use assert less often
- Generally, it will be used towards the end of a function.
基本上,require()应该是检查条件的函数,assert()只是用来防止发生真正不好的事情,但是条件不能评估为false。
另外:“仅当您认为先前的检查(使用if或require)将使不可能发生溢出时,才应盲目地使用assert进行溢出检查”。 — @chriseth的评论
也就是说确保先前的操纵没有发生溢出的时候,才会使用assert()。
结论
这些功能是用于安全工具箱的非常强大的工具。知道如何以及何时使用它们,不仅有助于防止漏洞,而且还可以使您的代码更加用户友好,并且可以防止将来发生更改。