http://www.www.tnmanning.com

减少智能合约代码复杂性并提高可维护性

在本文中,我将分享一些示例,说明消除特殊情况如何减少代码复杂性并提高可维护性。

特殊最大值

常见的特殊情况是使用0表示“无最大值”。这种特殊情况通常很容易消除。

Special Expirations

看下面的代码


uint256 expiration;

// Use 0 to mean "no expiration".
function setExpiration(uint256 newExpiration) external {
    expiration = newExpiration;
}

function doSomething() external {
    require(expiration == 0 || now < expiration, "Error: expired");
    ...
}

在这段代码中,0是一种特殊情况,表示“没有过期”。这种特殊情况是不直观的,它增加了require语句的复杂性。

然而,真正的危险是团队中的一个新开发人员忽略了这个微妙之处,无法处理expiration==0的特殊情况。这很容易导致资金损失或其他严重问题。

这样代码就更简单更明显了:

// Default to 2**256-1 instead.
uint256 expiration = 2**256-1;

// Use 2**256-1 to mean "no expiration".
function setExpiration(uint256 newExpiration) external {
    expiration = newExpiration;
}

function doSomething() external {
    require(now <= expiration, "Error: expired");
    ...
}

这里,我使用的是uint256允许的最大值的expiration,而不是0,当涉及到时间戳时,expiration实际上是无限的。

特殊最大以太币数量

这是一个非常相似的示例,但这次涉及以太币:

uint256 maxWithdrawal;

// Use 0 to mean "no maximum".
function setMaxWithdrawal(uint256 newMax) external {
    maxWithdrawal = newMax;
}

function withdraw(uint256 amount) external {
    require(maxWithdrawal == 0 || amount <= maxWithdrawal, "Error: too much");
    ...
}

同样,我们有一个非直观的特例,我们可以通过使用一个有效的无限值来解决这个问题:

// Default to 2**256-1 instead.
uint256 maxWithdrawal = 2**256-1;

// Use 2**256-1 to mean "no maximum".
function setMaxWithdrawal(uint256 newMax) external {
    maxWithdrawal = newMax;
}

function withdraw(uint256 amount) external {
    require(amount <= maxWithdrawal, "Error: too much");
    ...
}

2256-1是最大值

注意,同样的技巧可以概括为令牌数量或任何值。由于Solidity不能表示大于2256-1的值,因此它始终可以与uint256进行比较,成为“有效无限”值

解决gas成本问题

通常,在gas成本方面需要进行权衡。人们最终将默认值设为0的一个典型原因是存储非零值会耗费大量gas。

如果存储成本对于您的用例而言是很高的,请考虑以下技巧:

uint256 _expiration; // 0 still means "no expiration"

...

// Properly handle the special cases in one place.
function expiration() internal view returns (uint256) {
    return _expiration > 0 ? _expiration : 2**256-1;
}

function doSomething() external {
    require(now < expiration(), "Error: expired");
}

在此代码中,写入存储的_expiration值默认情况下为0,与以前的特殊含义相同。但是,我介绍了一个辅助函数expiration(),它将0转换为不太特殊的值2256-1。这意味着我的其余代码无需处理这种特殊情况。

考虑将此技术与自定义的linter规则配对使用,以确保您不会在expiration()函数之外的任何地方直接读取_expiration。

特殊地址

关于地址,我经常看到两种特殊情况:

1. 地址0通常是不允许的。
2. 不允许使用特定地址(通常是特权角色)。

特别地址0

这是一些熟悉的代码,其中使用0作为特殊情况:

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。