无论是开发

1、了解重入攻击

一种常见的

在这个函数中,我们用msg.sender.call调用另一个账户。我们要记住的是,这可能是另一个

转移、调用和发送

长期以来,Solidity安全专家建议不要使用上述方法。他们建议不使用call函数,而是使用transfer,像下面这样:

payable(msg.sender).transfer(shares[msg.sender]);

我们之所以提到这一点,是因为你可能会看到外面有一些相互矛盾的资料,它们的建议与我们的建议相反。此外,你也会听到send函数。每一个函数都可以用来发送ETH,但都有轻微的差异。

transfer: 最多需要2300个gas,失败时会抛出一个错误

send: 最多需要2300个gas,失败时返回false

call: 将所有gas转移到下一个合约,失败时返回false

transfer和send在很长一段时间内被认为是 “更好 “的做法,因为2300个gas真的只够发出一个事件或其他无害的操作;接收合约除了发出事件不能回调或做任何恶意操作,因为如果他们尝试这样做的话,他们会耗尽gas。

然而,这只是目前的设置,由于不断变化的基础设施生态,gas成本在未来可能会发生变化。我们已经看到有EIP改变了不同操作码的gas成本。这意味着未来可能有一段时间,你可以以低于2300个gas的价格调用一个函数,或者事件的成本将超过2300个gas,这意味着任何现在要发出事件的接收函数会在未来会失败。

这意味着最好的做法是在调用项目外的任何合约之前更新状态。另一个可能的缓解措施是对关键函数施加一个互斥锁,例如ReentrancyGuard中的非重入修改器。采用这样的互斥锁将阻止交易合约被重入。这实质上是增加了一个“锁”,所以在合约执行过程中,任何调用合约的人都不能“重新进入”该合约。

重入攻击的另一个版本是跨函数重入。下面是一个跨函数重入攻击的例子,为了便于阅读,使用了transfer函数:

mapping (address => uint) private userBalances;function transfer(address _recipient, uint _amount) { require(userBalances[msg.sender] >= _amount); userBalances[_recipient] += _amount; userBalances[msg.sender] -= _amount;}function withdrawBalance() public { uint amountToWithdraw = userBalances[msg.sender]; msg.sender.transfer(amountToWithdraw); userBalances[msg.sender] = 0;}

有可能在另一个函数完成之前调用一个函数。这应该是一个明确的提醒,在你发送ETH之前一定要先更新状态。一些协议甚至在他们的函数上添加了互斥锁,这样如果另一个函数还没有返回,这些函数就不能被调用。

除了常见的重入漏洞外,还有一些重入攻击可以由特定的EIP机制触发,如ERC777。ERC-777(EIP-777)是建立在ERC-20(EIP-20)之上的

该协议有一个从

上面的代码是实现访问Chainlink价格预言机的全部内容,你可以阅读文档,开始在你的应用程序中实现它们。如果你是

myAddress可以通过碰撞读取otherContractAddress的

4.myAddress()函数的返回值只是部署的实现合约中的myAddress变量的值,它与部署的代理合约中的otherContractAddress变量相碰撞,可以在那里获得otherContractAddress变量的值。

为了避免代理存储碰撞,我们建议开发者为存储变量选择伪随机槽来实现非结构化的存储代理。

一种常见的做法是为项目采用一个可靠的代理模式。最广泛采用的代理模式是通用可升级代理标准(UUPS)和透明代理模式。它们都提供了具体的存储偏移offset,以避免在代理合约和实现合约中使用相同的存储槽。

下面是一个使用透明代理模式实现随机存储的例子:

bytes32 private constant implementationPosition = bytes32(uint256( keccak256('eip1967.proxy.implementation')) - 1));

通证转移计算的准确性

通常情况下,对于一个普通的ERC20通证,收到的通证数量应该等于用函数调用的原始数量;例如,下面的函数retrieveTokens()。

function retrieveTokens(address sender, uint256 amount) public { token.transferFrom(sender, address(this), amount); totalTokenTransferred += amount;}

然而如果通证是通缩的,即每次转让都有费用,那么实际收到的通证数量将少于最初要求转让的通证数量。

在下面修改后的函数retrieveTokens(address sender, uint256 amount)中,金额是根据转移操作前后的余额重新计算的。无论通证转移机制如何,这都能准确地计算出已经转移到address(this)的通证数量。

function retrieveTokens(address sender, uint256 amount) public { uint256 balanceBefore = deflationaryToken.balanceOf(address(this)); deflationaryToken.transferFrom(sender, address(this), amount); uint256 balanceAfter = deflationaryToken.balanceOf(address(this)); amount = balanceAfter.sub(balanceBefore); totalTokenTransferred += amount;}

正确的数据删除

有很多情况下需要删除合约中不再需要的某个对象或值。在像Java这样的成熟语言中,有一个垃圾回收机制,可以自动和安全地处理这个问题。然而在Solidity中,开发者必须手动处理“垃圾”。因此,不正确地处理垃圾可能给

函数_mint()的可见性被设置为internal,这正确地保护了它不能被外部调用。为了给mint函数设置一个适当的访问授权,可以使用下面的代码片段:

function mint(address account, uint256 amount) public onlyOwner { _mint(account, amount); require(MaxTotalSupply >= _totalSupply, "over mint");}

函数mint()只允许合约的所有者进行铸造,require()语句防止所有者铸造过多的通证。

正确使用可见性和限制有利于合约管理。也就是说,一方面缺乏这样的设置可能会让恶意攻击者调用管理配置功能来操纵项目,另一方面,过度的限制设置可能会给合约带来中心化的担忧,也可能会引起社区的质疑。

6、在部署到主网前做好外部审计

可以将代码审计视为以安全为中心的同行评审。审计员将逐行检查你的整个代码库,并使用形式化验证技术来检查你的

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注