手机版

当前位置:XMR门罗币 > 技术 >

撰写智能合约时一般应遵循的安全模式-part1

时间:2021-07-25 06:22:42|浏览:

本次课程主要展示在撰写智能合约时一般应遵循的安全模式。

策略建议

以下建议适用于ETH上任何智能合约系统的开发。

外部调用

用外部调用时需要格外注意

调用不受信赖的智能合约可能会带来一些意料之外的风险或Bug。外部调用可能在该合约或它依靠的任何其他合约中实行恶意代码。因此,每一个外部调用都应视为潜在的安全风险。 假如没办法或不期望删除外部调用,请用本节课程的建议将危险降至最低。

标记不受信赖的合约

当与外部合约进行交互时,请以了解表明与它们进行交互不安全的方法命名变量,办法和合约接口,适用于你我们的调用外部合约的函数。

// bad
Bank.withdraw; // Unclear wh以太币er trusted or untrusted

function makeWithdrawal { // Isn't clear that this function is potentially unsafe
Bank.withdraw;
}

// good
UntrustedBank.withdraw; // untrusted external call
TrustedBank.withdraw; // external but trusted bank contract maintained by XYZ Corp

function makeUntrustedWithdrawal {
UntrustedBank.withdraw;
}

防止外部调用后的状况更改

无论用原始调用(形式为someAddress.call())还是合约调用(形式为ExternalContract.someMethod()),都可能存在实行恶意代码的风险。 即便ExternalContract不是恶意的,恶意代码也可以通过其调用的任何合约实行。

一种特别的危险是恶意代码可能会劫持控制流,从而致使因为可重入而产生的漏洞。

假如要调用不受信赖的外部合约,请防止在调用后更改状况。这种模式有时也被叫做检查成效交互模式。

防止用transfer()和send()

.transfer()和.send()都会将2300gas转发给收件人。这一硬编码gas津贴的目的是预防重入漏洞,但这只有在gas本钱不变的假设下才有意义。近期的EIP 1283(在最后一刻退出了君士坦丁堡硬叉)和EIP 1884(预计将在伊斯坦布尔硬叉中到达)表明此假设无效。

为了防止以后gas本钱发生变化时会产生问题,最好改用.call.value(amount)(“”)。请注意,这无助于减轻重入攻击,因此需要采取其他预防手段。

处置外部调用中的Bug

Solidity提供了适用于原始地址的低级调用办法:address.call(),address.callcode(),address.delegatecall()和address.send()。 这部分低级办法从不抛出异常,但假如调用遇见异常,则将返回false。 另一方面,合同调用(比如,ExternalContract.doSomething())将自动传播一个引发(比如,假如doSomething()引发,则ExternalContract.doSomething()也将引发)。

假如选择用低级调用办法,请确保通过检查返回值来处置调用失败的可能性。

// bad
someAddress.send;
someAddress.call.value; // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result
someAddress.call.value"))); // if deposit throws an exception, the raw call will only return false and transaction will NOT be reverted

// good
= someAddress.call.value;
if {
// handle failure code
}

ExternalContract.deposit.value;

支持外部调用push

外部调用可能发业务外或者恶意BUG。为了最大限度地降低此类问题导致的损害,一般最好将每一个外部调用隔离到我们的事务中,该事务可以由调用的接收者发起。这与支付特别有关,在支付中,最好让用户提取资金,而不是自动向他们推送资金。(这也减少了GAS限制出现问题的可能性)防止在一个事务中合并多个ETH转移。

// bad
contract auction {
address highestBidder;
uint highestBid;

function bid payable {
require;

if ) {
= highestBidder.call.value;
require; // if this call consistently fails, no one else can bid
}

highestBidder = msg.sender;
highestBid = msg.value;
}
}

// good
contract auction {
address highestBidder;
uint highestBid;
mapping refunds;

function bid payable external {
require;

if ) {
refunds[highestBidder] += highestBid; // record the refund that this user can claim
}

highestBidder = msg.sender;
highestBid = msg.value;
}

function withdrawRefund external {
uint refund = refunds[msg.sender];
refunds[msg.sender] = 0;
= msg.sender.call.value;
require;
}
}

不要将调用委托给不受信赖的代码

delegateCall函数用于从其他合约调用函数,就仿佛它们是调用方合约一样。因此调用方可以改变调用地址的状况,这是存在风险。下面的示例演示了用delegatecall怎么样致使合约的破坏和资金损失。

contract Destructor
{
function doWork external
{
selfdestruct;
}
}

contract Worker
{
function doWork public
{
// unsafe
_internalWorker.delegatecall")));
}
}

假如用已部署的Destructor合约的地址作为参数调用Worker.doWork(),则Worker合约将自毁。 仅将实行委托给受信赖的合约,而不委托给用户提供的地址。

不要假设合约是用零余额创建的,攻击者可以在创建合约之前将ETH发送到该合约的地址。

请记住,可以强制将ETH发送到一个帐户

小心撰写严格检查智能合约的余额的不变量。

攻击者可以强行将ETH发送到任何帐户,并且这是不可回避的(即便用实行revert()的回退函数也没办法阻止)。

攻击者可以通过创建合约,用1 wei资助该合约并调用selfdestruct(victimAddress)来达成此目的。在victimaddress中没调用任何代码,因此没办法阻止它。发送到矿工的地址的区块奖励也是这样,该地址可以是任意地址。

除此之外,因为可以预先计算合约地址,因此可以在部署合约之前将ETH发送到某个地址。

请记住,链上数据是公开的

很多应用程序需要提交的数据在某个时间点之前都是隐匿的。游戏(如链上剪刀石头布)和拍卖机制(如推广竞价拍卖)两大类例子。假如你在构建隐私问题的应用程序,请确保防止用户过早公布信息。最好的方案是用具备不同阶段的承诺策略:第一用值的哈希值进行提交,然后在后续阶段中显示值。

例子:

在剪刀石头布上,需要两个玩家先提交其预期动作的哈希值,然后需要两个玩家均提交其动作;假如提交的动作与散列不匹配,则将其丢弃。

在拍卖中,需要玩家在初始阶段提交其出价值的哈希值(与大于其出价值的保证金),然后在第二阶段提交其拍卖出价。

开发依靠于随机数生成器的应用程序时,顺序应一直为(1)玩家提交动作,(2)生成随机数,(3)玩家支付。产生随机数的办法本身就是积极研究的范围。目前相同种类最好的解决方法包括BTC区块头(通过http://btcrelay.org验证),哈希提交显示策略(即,一方生成数字,发布其哈希值以“提交”给该值,与然后显示价值)和RANDAO。因为ETH是确定性协议,因此协议中的任何变量都不可以用作不可预测的随机数。还应注意,矿工在某种程度上控制着block.blockhash()值*。

注意某些参与者可能“下线”而不上线的可能性

不要依靠于由特定方实行特定操作的退款或索赔程序,而没其他办法将资金取出。比如在石头剪刀布游戏中,一个容易见到的错误是在两个玩家都提交动作之前不进行支付。 但恶意的玩者可以通过根本不提交我们的举动来“困扰”他们-事实上,假如一个玩者看到了他们显示的举动并确定自己输了,则根本没理由提源于己的举动。

(1)提供一种避免未参与参与者的办法,可能会在肯定时限内进行;
(2)考虑为参与者在其所处的所有状况下提交信息提供额外的经济勉励。

注意负整数取反

solidity提供了几种处置有符号整数的种类。与大部分编程语言一样,在solidity中,带n位的有符号整数可以表示从-2^(n-1)到2^(n-1)-1的值。这意味着MIN_INT没正等价物。求反是通过找到一个数字的两个补数达成的,因此,最负数的求反将得出相同的值。

contract Negation {
function negate8 public pure returns {
return -_i;
}

function negate16 public pure returns {
return -_i;
}

int8 public a = negate8; // -128
int16 public b = negate16; // 128
int16 public c = negate16; // -32768
}

处置此问题的一种办法是,在求反之前检查变量的值,假如该值等于最小整数,则抛出。另一种选择是确保用容量更大的种类(比如int32而不是int16)从来不会达到最大负数。

当min_int乘以或除以-1时,int种类也会出现类似的问题。

出处: 区块链研究实验室 作者:链三丰

Copyright © 2002-2021 XMR门罗币 (http://www.dgxiehuazdh.com) 网站地图 TAG标签