我写了一个 solidity 合约,用于提取合约余额。有人能告诉我它是否有效吗?withdrawSafe应该可以防止重入攻击,但我不知道它是否有效。只需检查一切是否正常,然后请提出改进建议。可能会犯一些愚蠢的错误。
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19;
event Response(bool success, bytes data);
interface IVault {
function deposit() external payable;
function withdrawSafe(address payable holder) external;
function withdrawUnsafe(address payable holder) external;
}
interface IAttacker {
function depositToVault(address vault) external payable;
function attack(address vault) external;
}
contract Vault is IVault {
bool private _entered;
modifier nonReentrant {
require(!_entered, "re-entrant call");
_entered = true;
_;
_entered = false;
}
mapping(address => uint256) public balance;
function deposit() external payable {
balance[msg.sender] += msg.value;
}
function withdrawSafe(address payable holder) external nonReentrant {
(bool success, bytes memory data) = holder.call{value: balance[msg.sender], gas: 5000}
(abi.encodeWithSignature("withdrawSafe(string,uint256)", "call WS", 123));
emit Response(success, data);
}
function withdrawUnsafe(address payable holder) external {
(bool success, bytes memory data) = holder.call{value: balance[msg.sender], gas: 5000}
(abi.encodeWithSignature("withdrawSafe(string,uint256)", "call WS", 123));
emit Response(success, data);
}
}
此外,如果有人知道如何在没有任何测试网或主网链的情况下在 Remix 中测试合约,或者可能推荐另一个 IDE,我会很高兴听到任何建议。
我遇到了一些可以改进的问题:
修饰符
nonReentrant
已正确实现,但也应该使用它来保护函数内部的余额更新逻辑withdrawSafe
。在进行外部调用之前应该更新余额,以防止重入。这样可以确保即使外部调用进行递归调用,余额也已被设置为零,从而防止重入。
当前实现不会更新持有者的余额。这对于防止重入非常必要。
指定的 gas 限制(5000)可能不足以使调用成功,具体取决于接收方的实现。您可能希望确保它足以让接收方处理交易。
对于简单的以太币转账来说,这不是必需的。您可以直接使用
holder.transfer
或转账holder.call{value: amount}("")
。根据建议的改进,你的合同将如下所示:
这有助于确保该
withdrawSafe
功能能够抵御重入攻击。