21条以太坊solidity智能合约开发最佳实践
1.处理合约调用的方法
call:最常用的调用方式,调用后内置变量 msg 的值会修改为调用者,执行环境为被调用者的运行环境。
delegatecall:调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境。(常用)
callcode: 调用后内置变量 msg 的值会修改为调用者,但执行环境为调用者的运行环境。
跨合约调用,最好先实例化一个合约
A contractA = new A(xxx);
contractA.doSomething();
call,delegatecall 的返回值都是bool。并没有具体执行后的返回值
2.关于assert,require, if 使用
最佳实践是使用if判断。assert() require() 如果调用不成立合约就会抛出异常。
使用if判断可以将错误的参数信息记入log日志中方面查看
3.标注函数,变量的可见性
public : 修饰的变量和函数,任何用户或者合约都能调用和访问
private : 修饰的变量和函数,只能在其所在的合约中调用和访问,即使是其子合约也没有权限访问
internal : 如果某个合约继承自其父合约,这个合约也可以访问父合约中定义的internal函数。(internal可继承性访问)
external : external修饰的函数只能在合约之外调用 - 不能被合约内的其他函数调用
4.区分函数和事件
//不友好的写法
event Transfer() {} function transfer() {}
//友好形象的写法
event LogTransfer() {} function transfer() external {}
事件尽量以Log为前缀与函数区分开
5.智能合约日志设计最佳实践
log事件记录尽量存入一两个日志合约中。并且需要被查询的字段尽量保证在事件相同的索引处。
如:要跟踪查询一笔资产的流转步骤,那么每笔流转过程中写入资产编号的日志,确保在该字段是位于同一个位置索引。如下
contract A{
event LogCreateA(bool boo,bytes32 indexed assetId,string msg);
}
contract B{
event LogCreateB(uint u,bytes32 indexed assetId,bytes32 other);
}
注意:一个事件方法中,最多只能有三个参数可接受indexed事件参数的属性
6.带流程编码的智能合约设计
合约设计资产流转过程中,最好保存所有资产流转过的节点编码,而不是修改单一的修改状态。如:Apply,Accept,Audit,Agree ,End。
使用bytes32[] public flows; 使用数组存储,这样便于回归查询出资产的流转轨迹过程。
7.solidity合约方法参数受限
solidity <0.4.25版本 合约传入和返回参数不能超过11个。高版本的合约目前还不清楚。
8.合约方法中创建数组的正确方式
//错误 bytes32[] memory types = new bytes32[array.length]; //正确 bytes32[] memory types = new bytes32[](array.length);
9.删除数组的方式
delete array[k]; array[k]=array[array.length-1]; array.length--;
10.合约中引入其他合约时,最好为其添加set方法。方便后期合约修改或升级调整
LogAsset public logAsset function setLogAsset(address _log) isOwner public{ logAsset = _log; }
11.合约继承中子类继承父类,先继承父类构造,在写modifier修饰器
constructor(address _addr, uint _num, address[] _voters) Voteable(_num, _voters) Base(_addr) isOwner public { owner = tx.origin; creator = msg.sender; }
注意:子类继承父类,必须要继承父类的构造
12.solidity 基础类型简称
uint<==>uint256 byte <==>bytes1 sha3<==>keccak256 now <==> block.timestamp uint160 <==> address
13.合约中被public 修饰的变量,evm自身对外生成get方法
如: address public localAddr;
合约编译部署上链后,自动生成localAddr() 方法,函数签名: 597d0a2f
14.solidity 基本类型默认值
uint t; //0 int n; // 0 string str; // "" address addr; // 0x0000000000000000000000000000000000000000 bool b; // false
15. solidity abi内置编码函数
abi.encode(...) returns (bytes) //encode编码给定参数
abi.encodePacked(...) returns (bytes) //简单编码,对于动态类型不会填充补齐64
abi.encodeWithSelector(bytes4 selector, ...) returns (bytes) //计算函数选择器和参数的ABI编码
abi.encodeWithSignature(string signature, ...) returns (bytes) //获取函数签名,等价于* abi.encodeWithSelector(bytes4(keccak256(signature), ...)
keccak256(abi.encodePacked("xx1","xx2")) //对"xx1","xx2"进行加密
16. string[] 可以在合约中静态定义变量数组,但是不能从外部传参string[]或返回strnig[]
17. solidity 可以返回对象,只不过是在合约内部使用internal
struct Info{ bytes32 name; bytes32 code; bytes32 phone; } function abcd()internal view returns(Info){ Info memory info = Info("name","code","123423234"); return info; }
//内部函数 internal 可以在接受或返回结构体对象。 function saveVote(Voter v) internal view returns(Voter fs){ v.weight = v.weight +1; if(!v.voted){ v.voted = true; } fs = v; }
18.在合约中含有结构体时,需要判断结构体是否存在,最好加一个bool类型。
//bool 标识该结构体对象是否存在,确保每次赋值时都要给结构体boo赋值true struct License{ bytes32 ltype; bool boo; //是否存在 } mapping(bytes32 => License) public map_license; if(map_license[_licenses].boo)
19.结构体赋值 两种方式
struct SettleInfo{ uint stype; string reason; bool ifSettle; } map_settle[bytes32(_loan)] = SettleInfo({reason:_reason,ifSettle:true,stype:_type}); //指定了结构体属性名称,可以无序赋值 map_settle[bytes32(_loan)] = SettleInfo(1,"reason",true); //按结构体顺序赋值
20. constant,view,pure 关键字
constant: 常量字段的定义修饰符
view: 只能读取状态变量但是不能改
pure:pure修饰的函数不能改也不能读状态变量。
//没有更改任何状态变量,或读取状态变量。仅仅是对输入值做了操作返回 function f(uint a, uint b) public pure returns (uint) { return a * (b + 42); }
21. 提升可读性,尽量将默认返回值补全
//bad function compareNumber(uint num1, uint num2, bytes32 symbol) internal pure returns (bool){ if (symbol == ">=" && num1 >= num2) return true; else if (symbol == "<=" && num1 <= num2) return true; } //good function compareNumber(uint num1, uint num2, bytes32 symbol) internal pure returns (bool){ if (symbol == ">=" && num1 >= num2) return true; else if (symbol == "<=" && num1 <= num2) return true;
return false; }
关注公众号"刻意链习",获取更多区块链技术知识分享, 欢迎转载,转载请注明出处!