Немного подробнее.
Если атакующий делает проигрышную ставку с контракта, а в контракте есть receive, которая всегда возвращает ошибку, то попытки возместить проигравшую ставку заведомо обречены. В коде возврат делался стандартным образом:
if (refund > 0) {
(bool sent, ) = bidData.bidder.call{value: refund}("");
require(sent, "Failed to refund bidder");
}А коль скоро возвратов много и их надо обрабатывать в цикле, то как только попадается адрес контракта злоумышленника, то и вся цепочка обрывается. Правда потом злоумышленник всё-таки вышел на связь и сказал, что просто хотел показать ненадёжность аукциона и этот блок убрал (у него в receive просто стояло элементарное условие вида "возвращать ошибку/не возвращать ошибку"). Если ошибку не возвращать, то просто пробрасывать принятые деньги на кошелёк атакующего, что вернуть свою ставку. Ну, либо там мог быть бесконечный цикл, вот пример
https://twitter.com/0xInuarashi/status/1517677597978599425Вроде бы, всё ок, то есть возвраты стали работать и участники аукциона свои деньги назад таки получили.
Но есть и другой неприятный момент. В коде для функции
claimProjectFunds, которая выводила деньги на счёт устроителей, было условие
refundProgress >= totalBids (то есть нужно, чтобы было выплачено возвратов столько, сколько было ставок, видимо) - только после этого сами владельцы контракта могут забрать вырученные средства, то есть победившие ставки.
Однако дополнительно была введена переменная
bidIndex, которая тоже была связана с числом ставок. В функцию
bid, приходила не только денежная сумма, а ещё и количество ставок (amount), которое могло варьироваться от 1 до 3. Но владельцы контракта это не учли и вместо того, чтобы написать
bidIndex += amount написали
bidIndex++ (ну, либо им надо было ещё одну переменную выделять на это).
А дальше самое интересное. В функции для обработки возвратов стояло ещё одно условие
require(_refundProgress < _bidIndex, "Refunds already processed");. То есть кол-во возвратов не могло превышать кол-во bidIndex. Но если вспомнить, что bidIndex считается неправильно (а многие юзеры делали сразу много ставок), то refundProgress всегда будет меньше, чем
totalBids (то есть кол-во ставок в принципе, которое как раз считалось правильно). А теперь мы вспомним условие в функции "забрать себе деньги за аукцион":
require(refundProgress >= totalBids, "Refunds not yet processed");.
Фактически, это условие истинным не будет никогда, то есть устроители аукциона обманули самих себя, и деньги застряли на их же контракте.