区块链合约小技巧
d45a6aa987204ad9a933e10250e33222
节省 Gas 的小技巧
结构封装(Struct packing)
基础类型 uint 其实是 uint256,除此之外还有其他变种 uint : uint8 , uint16, uint32 等。
通常情况下我们不会考虑使用 uint 变种,因为无论如何定义 uint的大小,Solidity 为它保留256位的存储空间。例如,使用 uint8 而不是uint(uint256)不会为你节省任何 gas。
除非,把 uint 绑定到 struct 里面。
如果一个 struct 中有多个 uint,则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起,从而占用较少的存储空间。例如:
struct NormalStruct {
uint a;
uint b;
uint c;
}
struct MiniMe {
uint32 a;
uint32 b;
uint c;
}
// 因为使用了结构打包,`mini` 比 `normal` 占用的空间更少
NormalStruct normal = NormalStruct(10, 20, 30);
MiniMe mini = MiniMe(10, 20, 30);
因此,当 uint 定义在一个 struct 中的时候,尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置),这样 Solidity 可以将存储空间最小化。
“view” 函数不花 “gas”
当玩家从外部调用一个view
函数,是不需要支付一分 gas 的。
这是因为 view 函数不会真正改变区块链上的任何数据 - 它们只是读取。因此用 view 标记一个函数,意味着告诉 web3.js,运行这个函数只需要查询你的本地以太坊节点,而不需要在区块链上创建一个事务(事务需要运行在每个节点上,因此花费 gas)。
在所能只读的函数上标记上表示“只读”的“external view 声明,就能为你的玩家减少在 DApp 中 gas 用量。
注意:如果一个 view 函数在另一个函数的内部被调用,而调用函数与 view 函数的不属于同一个合约,也会产生调用成本。这是因为如果主调函数在以太坊创建了一个事务,它仍然需要逐个节点去验证。所以标记为 view 的函数只有在外部调用时才是免费的。
存储非常昂贵
Solidity 使用storage(存储)是相当昂贵的,存储数据比单纯的加法运算更消耗资源,”写入“操作尤其贵。 这是因为,无论是写入还是更改一段数据, 这都将永久性地写入区块链。需要在全球数千个节点的硬盘上存入这些数据,随着区块链的增长,拷贝份数更多,存储量也就越大。这是需要成本的!
为了降低成本,不到万不得已,避免将数据写入存储。这也会导致效率低下的编程逻辑 - 比如每次调用一个函数,都需要在 memory(内存) 中重建一个数组,而不是简单地将上次计算的数组给存储下来以便快速查找。
函数修饰符
概要:
Name | Content |
---|---|
private | 只能被合约内部调用 |
internal | 能被合约内部调用,也能被继承的合约调用 |
external | 只能从合约外部调用 |
public | 可以在任何地方调用,不管是内部还是外部。 |
view | 运行这个函数不会更改和保存任何数据 |
pure | 工具函数,这个函数不会往区块链读取/写数据 |
view | 运行这个函数不会更改和保存任何数据 |
modifier 自定义装饰器
使用 modifier 定义的装饰器, 对于这些修饰符我们可以自定义其对函数的约束逻辑,它不能像函数那样被直接调用,只能被添加到函数定义的末尾,用以改变函数的行为。
注意 likeABoss 函数上的 onlyOwner 修饰符。 当你调用 likeABoss 时,首先执行 onlyOwner 中的代码, 执行到 onlyOwner 中的 _; 语句时,程序再返回并执行 likeABoss 中的代码。
可见,尽管函数修饰符也可以应用到各种场合,但最常见的还是放在函数执行之前添加快速的 require检查。
/**
* @dev 调用者不是‘主人’,就会抛出异常
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
contract MyContract is Ownable {
event LaughManiacally(string laughter);
//注意! `onlyOwner` :
function likeABoss() external onlyOwner {
LaughManiacally("Muahahahaha");
}
}
payable 修饰符
payable 方法是让 Solidity 和以太坊变酷的一部分 —— 它们是一种可以接收以太的特殊函数。
contract OnlineStore {
function buySomething() external payable {
// 检查以确定0.001以太发送出去来运行函数:
require(msg.value == 0.001 ether);
// 如果为真,一些用来向函数调用者发送数字内容的逻辑
transferThing(msg.sender);
}
}
在这里,msg.value 是一种可以查看向合约发送了多少以太的方法,另外 ether 是一个內建单元。
这里发生的事是,一些人会从 web3.js 调用这个函数 (从DApp的前端), 像这样 :
// 假设 `OnlineStore` 在以太坊上指向你的合约:
OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))
注意这个 value 字段, JavaScript 调用来指定发送多少(0.001)以太。如果把事务想象成一个信封,你发送到函数的参数就是信的内容。 添加一个 value 很像在信封里面放钱 —— 信件内容和钱同时发送给了接收者。
- 节省 Gas 的小技巧
- 结构封装(Struct packing)
- “view” 函数不花 “gas”
- 存储非常昂贵
- 函数修饰符
- modifier 自定义装饰器
- payable 修饰符