// Pull Pattern: Much Safer!
mapping(address => uint) public withdrawableBalances;
// Step 1: Admin marks funds as withdrawable without actually sending them
function startBatchWithdrawal() public {
address[] memory users = getUsers(); // imaginary function to get users
for (uint i = 0; i < users.length; i++) {
uint amount = balances[users[i]];
if (amount > 0) {
balances[users[i]] = 0;
withdrawableBalances[users[i]] += amount;
}
}
}
// Step 2: Each user withdraws their own funds individually
function withdraw() public {
uint amount = withdrawableBalances[msg.sender];
require(amount > 0, "No funds to withdraw");
withdrawableBalances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
Теперь, если вывод средств пользователя не удается, это затрагивает только его самого. Контракт продолжает функционировать для всех остальных. Это и есть правильная децентрализация! Оператор require проверяет, был ли перевод успешным. В случае неудачи транзакция отменяется только для запрашивающего пользователя, что оказывает минимальное влияние на текущие операции.
В примере, приведенном в пункте чеклиста, подробно описан сценарий, в котором комиссии переводятся владельцу до вывода средств пользователем. Если адрес владельца случайно установлен на нулевой адрес или если владелец является, э-э-э, злонамеренным контрактом, который ревертит перевод токенов, вывод средств пользователями не удастся. Это неприятная ошибка для отладки! Минимальный пример и PoC, написанные в Foundry, доступны здесь.
Далее разберем еще два случая атаки.
#dos