домашки / домашка 1
сдача через SDK · lab.hw.submit

«Сломай и почини»

домашка 1
оценку ставят на защите

Три части. Сдаёшь через платформу на странице /hw/1. Код и отчёт — это билет на защиту, а не сама оценка: оценку ставят по защите (см. в конце). На защите ты один.

Инструмент — Python SDK defilab. Ставится одной строкой, нужен Python 3.11 или новее:

pip install "defilab[plot] @ https://defilab.insights-forge.com/sdk/defilab-0.1.5-py3-none-any.whl"

[plot] — это matplotlib, он нужен для графика в части 1. Без него всё остальное работает, а график просто не нарисуется.

Код класса, имя и PIN — те же, что для входа на сайт; код класса называет преподаватель.

from defilab import Lab, sim
lab = Lab("https://api.defilab.insights-forge.com", class_code="DEFI-01", name="Твоё Имя", pin="твой PIN")
lab.wallet()   # проверка входа: адрес, баланс, сколько транзакций ждёт блока
/01Часть 1. Атака: double spend в симуляторе

Тебе выдаётся игрушечный чейн с настраиваемыми параметрами. Свои параметры возьми с сервера — у каждого они свои:

p = lab.hw.params()
# block_time_s — среднее время блока; latency_ms — задержка сети;
# give_up_deficit — на сколько блоков может отстать атакующий, прежде чем сдаётся;
# shares — доли хэшрейта для ответа; target_probability — какую вероятность торговец считает приемлемой

Что сделать:

  1. Провести атаку в симуляторе и найти условия, при которых double spend удаётся:
   sim.attack(hashrate_share=0.3, confirmations=1, params=p, trials=5000)
  1. Ответить на вопрос: сколько подтверждений нужно ждать при доле атакующего 10%, 30%, 45%, чтобы вероятность успешной атаки была не выше target_probability. Используй shares из своих параметров (там же есть четвёртая, твоя личная доля).
  2. Построить график «подтверждения × доля хэшрейта» (по оси X — число подтверждений, по оси Y — вероятность, лог-шкала; по одной линии на каждую долю):
   table = sim.sweep(p["shares"], range(0, 13), params=p, trials=2000)
   sim.plot(table, "attack.png")

LLM может написать симулятор или график. Но параметры ты подбираешь сам, график объясняешь сам и защищаешь на защите. Приложи график и ответы (число подтверждений для каждой доли) в отчёт, а сами числа — в поле parts.part1.

Полезно сравнить свою кривую с формулой из статьи Накамото (sim.nakamoto(q, z)) и объяснить, почему они совпадают или расходятся (задержка сети, конечные ресурсы атакующего).

/02Часть 2. Свой контракт с подвохом

Написать и задеплоить в класс-чейн один контракт из списка:

  • эскроу с арбитром — деньги держит контракт, арбитр решает спор;
  • аукцион — ставки, победитель, возврат проигравшим;
  • лотерея — участники, розыгрыш, выплата;
  • копилка с дедлайном — вносишь, снимаешь только после срока.

Условие: контракт должен содержать одну уязвимость, которую ты знаешь и описал заранее. Описание кладёшь «в конверт»: на сервер уходит только его sha256, сам текст остаётся у тебя до конца окна.

src = open("MyContract.sol").read()
d = lab.deploy(src, args=[...])          # компиляция на сервере: один файл, импорты только @openzeppelin
lab.wait(d)
lab.hw.arena_register(d.address, envelope="здесь я описываю свою дыру словами",
                      kind="piggybank", source=src, abi=d.abi)

Адреса для конструктора (продавец, арбитр, кто угодно) — это адреса кошельков класс-чейна: свой даёт lab.wallet()["address"], чужой попроси у одноклассника. Для проверки можно поставить свой адрес во все роли. Деплой и вызовы стоят комиссию в CLASS; если lab.deploy отвечает insufficient CLASS for value + fee, монет на кошельке нет — попроси преподавателя пополнить.

У арены нет страницы и кнопки: это доска в SDK, lab.hw.arena(). Общий зал в кабинете — другое место, там майнинг стадий 4–6, к домашке он не относится. Другие ученики (и агенты-атакующие) читают исходники с этой доски и пытаются найти дыры. Твоя задача — ещё и найти чужие: провести эксплойт-транзакцию в класс-чейне и засчитать её.

board = lab.hw.arena()                    # чужие контракты, исходники, лидерборд
entry = board["entries"][0]               # берёшь любую чужую запись
target, target_abi = entry["contract"], entry["abi"]   # адрес и ABI лежат прямо в ней
print(entry["source"])                    # исходник — читать его и есть задача

tx = lab.call(target, "нужная_функция", [...], abi=target_abi)
lab.wait(tx)
lab.hw.arena_claim(target, tx.hash)       # засчитывается, если tx реально увела CLASS или сработала на цели

Очки: за то, что твою уязвимость не нашли к концу окна, и за каждую найденную чужую (первому, кто сломал контракт, — бонус). Правила и подсчёт — в hw1-attack-arena.md. Это учит читать чужой код — в эпоху LLM это важнее, чем писать.

После окна открой конверт (lab.hw.arena_open_envelope(текст)) — платформа сверит его с зарегистрированным sha256.

/03Часть 3. Обмен без посредника

У тебя есть токены A, у друга — B. Опиши механизм обмена, при котором никто не может кинуть.

  1. Сначала — для одной конкретной сделки между вами двумя (атомарный своп: двумя контрактами или одним, который держит обе ноги). Опиши по шагам, что происходит и почему нельзя забрать чужое, не отдав своё.
  2. Потом — сложнее: а если ты хочешь обменять A на B в любой момент, ни с кем конкретным не договариваясь? Ответа тут не требуется. Нужна попытка и список проблем, в которые ты упёрся (кто назначает цену? откуда берётся вторая сторона? что если её нет?). С разбора этих попыток начнётся лекция 2.

Текст части 3 — в поле parts.part3 или в отчёте.

/04Формат сдачи
lab.hw.submit(1,
    parts={
        "part1": {"confirmations": {"0.1": 4, "0.3": 24, "0.45": None}, "notes": "..."},
        "part2": {"contract": d.address, "kind": "piggybank"},
        "part3": {"text": "..."},
    },
    report_md=open("report.md").read(),
    files=[{"path": "MyContract.sol", "content": src}, {"path": "attack.py", "content": "..."}],
)

Отчёт (report.md) — свободный текст: что ты сделал в каждой части, график части 1 с объяснением, какой контракт и почему, попытки части 3.

/05Про LLM

Использовать LLM разрешено и поощряется — мы учим работать с инструментом, а не запрещаем его. Но:

  • В отчёте обязателен раздел «Что делал LLM, что делал я, где LLM ошибся». Без него работа считается несданной.
  • На защите ты один, без LLM. Защита проверяет, что ты понимаешь свой код и можешь его быстро менять.
/06Как проходит защита

Защита письменная, в чате на /hw/1, 15–20 минут. Её ведёт агент-экзаменатор. Он:

  • задаёт вопросы по твоему коду с цитированием строк («на строке N ты берёшь X — почему?»);
  • просит изменить поведение прямо сейчас, под таймером (например: «сделай, чтобы снять могли только двое, а не кто угодно — покажи, где правишь»). Новый код ты присылаешь через SDK, агент его компилирует и проверяет. Это главный фильтр: кто не понимает свой код, не может его быстро править, а копировать в LLM под таймером — медленно и заметно;
  • задаёт контрфактуальные вопросы («что было бы, если бы дедлайн был в прошлом?»);
  • проверяет связи с лекцией (двойная трата, подтверждения, атомарность, залог).

После защиты агент выставляет предварительную оценку, флаги и сводку. Итоговую оценку подтверждает преподаватель. Если не согласен — апелляция в один клик (lab.hw.defense_appeal("...")), решает преподаватель. Что именно проверяет экзаменатор и по какой шкале — в rubric.md.

/07За что ставят оценку
A. Атака и подтверждения (часть 1)вес 25
  • Понимает, что такое double spend и почему подтверждения снижают его вероятность.
  • Может объяснить свой график: почему кривая падает с числом подтверждений и растёт с долей хэшрейта; почему при доле ≥ 50% никакое число подтверждений не спасает.
  • Знает, откуда взял числа «сколько подтверждений при 10/30/45%», и что меняется от задержки сети и от того, что у атакующего конечные ресурсы (give_up).
  • Контрфактуал: «а если бы блок был вдвое быстрее / задержка вдвое больше — что с ответом?»
B. Свой контракт и живая правка (часть 2)вес 40
  • Главный критерий. Здесь ловится вайбкодинг.
  • Объясняет, что делает каждая существенная функция его контракта, построчно по запросу.
  • Знает свою заложенную уязвимость и почему она уязвимость (какой инвариант нарушается, кто и как выигрывает). Может отличить её от случайных багов.
  • Живая правка под таймером (2–4 минуты): «измени поведение вот так прямо сейчас, покажи, где». Примеры: закрыть заложенную дыру; добавить проверку msg.sender; поменять условие дедлайна; запретить повторный вызов. Ученик присылает новый код через SDK, экзаменатор компилирует его (инструмент compile) и проверяет, что правка действительно меняет поведение и компилируется. нерелевантный сгенерированный кусок → не понимает.
  • Читает чужой код: может рассказать, как нашёл (или искал) чужую уязвимость на арене.
C. Обмен без посредника (часть 3)вес 20
  • Объясняет, почему наивный обмен «в два перевода» позволяет кинуть (первый отдал — второй не прислал), и как атомарность это чинит.
  • Может описать свой механизм свопа по шагам и назвать, что его держит честным.
  • Для открытого вопроса (обмен «в любой момент») — называет реальные проблемы: цена, поиск контрагента, ликвидность. Не требуется решение, требуется осмысленная попытка.
D. Связи и честность про LLMвес 15
  • Связывает домашку с лекцией: подтверждения ↔ раунд майнеров, атомарность ↔ раунд контрактов, залог/slash ↔ раунд The Merge.
  • Раздел отчёта про LLM заполнен честно: видно границу «что делал я / что делал LLM / где LLM ошибся», и на защите это подтверждается (ученик знает, где инструмент ошибся, и почему).
/08Арена взаимных атак — домашка 1, часть 2

Страница /hw/1 → «Арена». Здесь ученики деплоят контракты с заложенной уязвимостью и ищут дыры друг у друга. Смысл — научиться читать чужой код: в эпоху LLM это важнее, чем писать.

Как участвовать

  1. Задеплой свой контракт в класс-чейн через SDK (lab.deploy). Он должен содержать одну уязвимость, которую ты знаешь.
  2. Опиши уязвимость словами в «конверте». На сервер уходит только sha256(текста) — сам текст остаётся у тебя. Так ты фиксируешь, что знал дыру заранее, но не подсказываешь её другим.
  3. Зарегистрируй контракт: lab.hw.arena_register(address, envelope="...", kind=..., source=src, abi=...). Исходник виден всем участникам — это то, что они читают.
  4. Ищи чужие дыры. Провёл эксплойт-транзакцию — засчитай её: lab.hw.arena_claim(target, tx.hash).

Один контракт на ученика (перерегистрация обновляет запись). Контракт можно зарегистрировать только свой — тот, что задеплоен с твоего кошелька (платформа проверяет по индексеру).

Окно

Окно арены — 5 дней с момента, как в классе зарегистрирован первый контракт. Пока окно открыто:

  • чужие конверты закрыты (виден только sha256 и исходник);
  • взломы засчитываются и появляются в ленте.

После закрытия окна каждый открывает свой конверт (lab.hw.arena_open_envelope(текст)); платформа сверяет его с зарегистрированным хэшем. Тогда же начисляются очки за «не нашли».

Как засчитывается «взлом»

Взлом — это не заявка словами, а эксплойт-транзакция в класс-чейне, засчитанная индексером по эффекту на контракт-цель. arena_claim(target, tx_hash) принимается, если:

  • транзакция отправлена с твоего кошелька (через chain-service);
  • она замайнена и не завершилась revert;
  • она либо увела CLASS из контракта-цели (баланс цели в блоке транзакции меньше, чем в предыдущем), либо вызвала цель и породила её событие (взаимодействие с контрактом, изменившее его состояние).

Нельзя засчитать взлом собственного контракта. Один зачёт на пару (атакующий → цель): повторная claim обновляет транзакцию-доказательство, но не даёт вторых очков.

Спорные случаи (например, «сработало, но CLASS не двигался») индексер помечает, разбирает преподаватель.

Очки

  • +30 — твою уязвимость не нашли к концу окна (никто не засчитал взлом твоего контракта).
  • +20 — за каждый засчитанный взлом чужого контракта.
  • +10 — бонус тому, кто сломал контракт первым (в дополнение к +20).

Свой контракт сломать нельзя, очков за это нет. Если твой контракт сломали — за «не нашли» ты не получаешь ничего, но очки за чужие взломы остаются.

Пример: ты сломал два чужих контракта (один — первым), а твой никто не тронул до конца окна: 30 (не нашли) + 20 + (20 + 10) = 80.

Лидерборд арены публичный внутри класса (lab.hw.arena()["leaderboard"]), обновляется в реальном времени. Он же — вход для экзаменатора: на защите тебя спросят, как ты искал чужие дыры и почему твоя (не) устояла.

/09Сдача

Сдача и защита — из кода, кнопок на этой странице нет. Порядок такой:

pip install https://defilab.insights-forge.com/sdk/defilab-0.1.5-py3-none-any.whl

from defilab import Lab
lab = Lab("https://api.defilab.insights-forge.com",
          class_code="DEFI-01", name="Твоё Имя", pin="твой PIN")

# сдать работу (можно пересдавать, пока не начал защиту)
lab.hw.submit(1, parts={...}, report_md=open("report.md").read(),
              files=[{"path": "MyContract.sol", "content": src}])

# защита: начинается один раз, после старта пересдача закрывается
lab.hw.defense_start()
lab.hw.defense()                 # прочитать вопрос экзаменатора
lab.hw.defense_say("ответ", code=None)
lab.hw.defense_stop()            # закончить и получить предварительную оценку

Что писать в parts — в разделе «Формат сдачи» выше. Пока защита не начата, каждый новый submit заменяет предыдущий.