我写了一份用于提取cantract余额的实体合约。有人可以告诉它是否有效。 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}("")
转账。
根据建议的改进,您的合同将如下所示:
// 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 {
uint256 amount = balance[msg.sender];
require(amount > 0, "Insufficient balance");
// Update balance before making the external call
balance[msg.sender] = 0;
// Use call to transfer funds and handle response
(bool success, bytes memory data) = holder.call{value: amount}("");
require(success, "Transfer failed");
emit Response(success, data);
}
function withdrawUnsafe(address payable holder) external {
uint256 amount = balance[msg.sender];
require(amount > 0, "Insufficient balance");
// Update balance before making the external call
balance[msg.sender] = 0;
// Use call to transfer funds and handle response
(bool success, bytes memory data) = holder.call{value: amount}("");
require(success, "Transfer failed");
emit Response(success, data);
}
}
这应该有助于确保
withdrawSafe
函数能够安全地抵御重入攻击。