Escrevi um contrato de solidez para retirada do saldo do contrato. Alguém pode dizer se está funcionando. O saqueSafe deve impedir ataques de reentrada, mas não sei se é. Apenas verifique se está tudo bem e sugira como melhorá-lo. Poderia ter cometido alguns erros estúpidos.
// 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);
}
}
Além disso, se alguém souber como testar contratos no Remix sem nenhuma rede de teste ou rede principal, ou talvez recomendar outro IDE, ficarei muito feliz em ouvir sugestões.
Encontrei alguns problemas que podem ser melhorados:
O
nonReentrant
modificador está implementado corretamente, mas também deve ser usado para proteger a lógica de atualização de saldo dentro dawithdrawSafe
função.O saldo deve ser atualizado antes de fazer a chamada externa para evitar reentrada. Isso garante que mesmo que a chamada externa faça uma chamada recursiva, o saldo já foi zerado, evitando reentrada.
A implementação atual não atualiza o saldo do titular. Isso é muito necessário para evitar a reentrada.
O limite de gás especificado (5000) pode não ser suficiente para o sucesso da chamada, dependendo da implementação do receptor. Você pode querer garantir que seja suficiente para o destinatário lidar com a transação.
Não é necessário uma simples transferência de Ether. Você pode transferir fundos diretamente usando
holder.transfer
ouholder.call{value: amount}("")
.Com as melhorias sugeridas, seu contrato ficará mais ou menos assim:
Isto deve ajudar a garantir que a
withdrawSafe
função esteja segura contra ataques de reentrada.