
Проверенные контракты, прокси и ключ обновления, о котором никто не упоминает
В обозревателе блоков стоит зелёная галочка с надписью Исходный код контракта проверен, и огромное число людей читает её как «этот контракт безопасен».
Она означает не это. Она означает нечто гораздо более узкое и гораздо более полезное, и путаница между этими двумя вещами — тот самый путь, которым осторожные люди в итоге одобряют то, на что никогда бы не согласились.
Что на самом деле удостоверяет «проверено»
Контракты разворачиваются в виде байткода — машинных инструкций, на практике нечитаемых. Проверка — это действие, при котором загружают исходный код и заставляют обозреватель подтвердить, что его компиляция с заявленной версией компилятора и заявленными настройками даёт ровно тот байткод, который уже живёт по этому адресу.
Вот и всё утверждение: исходник, который вы читаете, действительно тот исходник, который исполняется.
Это настоящая и ценная гарантия. Без неё у вас не было бы способа узнать, имеет ли красиво выглядящий код на GitHub проекта хоть какое-то отношение к тому, чего вот-вот коснётся ваша транзакция.
Но заметьте всё, о чём она не говорит. Проверка — не аудит. Никто не оценивал, верен ли код, есть ли в нём ошибки и делает ли он хоть что-нибудь похожее на то, что подсказывают имена его функций. Контракт с функцией claimRewards, которая переводит весь ваш баланс тому, кто его развернул, может быть проверен, и проверка была бы совершенно честной: она правдиво удостоверяла бы, что кража — это ровно то, что код и заявляет.
Проверено значит читаемо, а не заслуживает доверия. Это переносит вопрос с «что это за штука?» на «понимаю ли я то, что только что прочитал?» — что есть прогресс, но на второй вопрос за вас не отвечает.
Обратное тоже важно. Непроверенный контракт не доказательство злого умысла: множество законных развёртываний так и не доходит до проверки. Но это значит, что никто — ни один инструмент, которым вы пользуетесь, ни один исследователь, который мог бы вас предупредить, — не может прочесть то, с чем вы собираетесь взаимодействовать. Настораживаться тут разумно.
А потом прокси делают всё хуже
Здесь почва уходит из-под ног, и это та часть, о которой чаще всего молчат.
Большинство нетривиальных контрактов, с которыми вы будете иметь дело, — не одиночные контракты. Это прокси. Прокси — тот адрес, который вы видите, и он хранит все данные: остатки, разрешения, состояние. Но логики в нём почти нет. Вместо этого он передаёт каждый вызов отдельному контракту реализации и просто исполняет то, что тот скажет.
Причина — обновляемость. Неизменяемые контракты нельзя залатать, поэтому ошибка в них постоянна, а недостающая возможность отсутствует навсегда. Направление прокси на новую реализацию позволяет команде чинить ошибки и выпускать улучшения, не прося каждого пользователя мигрировать. С учётом того, сколько ценности потеряно из-за неисправимых ошибок, это защитимое инженерное решение.
У него есть легко упускаемое следствие. Когда вы смотрите прокси в обозревателе и видите, что он проверен, вы проверили логику пересылки, а не поведение. Код, который на самом деле решает, что станет с вашими деньгами, живёт по адресу реализации — который обозреватель может показать заметно, а может и нет, и который может быть заменён.
Значит, настоящим вопросом никогда не было «проверен ли этот контракт?». Он звучит так:
Кто может изменить поведение этого контракта и как быстро?
Административный ключ и есть подлинная модель безопасности
У всякого обновляемого контракта есть тот, кто может выполнить обновление. Эти полномочия и составляют модель безопасности, каким бы хорошим ни был нынешний код.
Разброс идёт от тревожного до разумного:
Единственный внешний счёт. Один закрытый ключ на чьей-то машине может подменить логику контракта, хранящего ваши средства. Если его выудят фишингом, украдут или принудят владельца, контракт станет тем, чем захочет нападающий. Такое устройство встречается чаще, чем следовало бы.
Мультиподпись. Несколько подписантов должны согласиться. Существенно лучше — убирает единственную точку отказа, — хотя стоит знать, сколько подписантов и действительно ли это независимые люди, а не четыре ноутбука одной и той же команды.
Мультиподпись за временной задержкой. Обновление объявляется в цепочке и может исполниться лишь по истечении фиксированного срока, часто от 24 до 72 часов. Вот это и есть значимый случай, потому что он полностью меняет ваше положение: вы получаете заблаговременное предупреждение и окно, чтобы вывести средства до вступления изменений в силу. Временная задержка не предотвращает злонамеренное обновление. Она делает его переживаемым.
Никакого пути обновления. Контракт неизменяем. Максимальная предсказуемость, никакой возможности исправить найденную ошибку. Законный выбор, обычно делаемый протоколами, обкатанными достаточно долго, чтобы на него поставить.
Ни один из вариантов не верен автоматически. Но разница между «одним ключом» и «мультиподписью плюс двухдневной задержкой» — это разница между надеждой, что никто не ошибётся, и наличием времени среагировать, когда ошибутся.
Почему это кусает именно за одобрения
Это та часть, которая связана с тем, что вы действительно делаете регулярно.
Когда вы выдаёте одобрение токенов, вы уполномочиваете адрес перемещать ваши токены. Не кусок кода — адрес. Одобрение лежит в хранилище контракта токена и сохраняется бессрочно.
Если этот адрес — обновляемый прокси, то логика, которой позволено двигать ваши токены, может быть заменена после того, как вы её одобрили, без вашего участия и без какой-либо новой подписи с вашей стороны. Вы изучили одно, а остаётесь уязвимы к другому.
Вот почему постоянный совет отзывать одобрения, которыми вы больше не пользуетесь, весит больше, чем кажется поначалу. Дело не только в том, что у контракта может обнаружиться ошибка. Дело в том, что неограниченное разрешение обновляемому контракту — это разрешение, выданное тому, кто распоряжается ключом обновления, на всё то время, пока вы оставляете его открытым.
Это же и самый резкий предел моделирования транзакций. Моделирование перед подписью сообщает, что делает транзакция применительно к нынешнему состоянию и нынешней реализации. Это по-настоящему полезно, и оно не может сказать, что то же самое одобрение позволит через месяц.
Что показывает вам SSP и где останавливается
Когда SSP Key показывает одобрение, он расшифровывает данные вызова, а не показывает сырой шестнадцатеричный код. Он распознаёт стандартные селекторы ERC-20 — approve, transfer, transferFrom, increaseAllowance, setApprovalForAll — и излагает их простым языком, в том числе помечая неограниченные разрешения, определённые в коде как любая сумма от 2^255 и выше.
Расшифровщик намеренно осторожен. Его собственный заголовок описывает его как исключительно слой представления: он никогда не меняет того, что подписывается, и отказывает в закрытую сторону. Неизвестный селектор, неверная длина, искажённый шестнадцатеричный код или нестандартное дополнение адреса — всё это заставляет его ничего не возвращать и отступать к обобщённому действию с сырым кодом за видом «Дополнительно». Он скорее признает, что не знает, чем уверенно угадает и ошибётся. Обозначения и десятичные знаки токенов прилагаются только тогда, когда известны из реестра на самом устройстве, и никогда не выводятся догадкой.
Теперь честная граница. У расшифровщика нет понятия прокси. Идея контракта реализации в нём не встречается нигде. Он может сказать вам верно и ясно: «вы выдаёте неограниченное разрешение USDC адресу 0xABC…» — и не может сказать, что 0xABC является прокси, у кого его ключ обновления и что его поведение через неделю может стать совершенно иным.
Это не столько упущение, сколько граница, которую не в силах перейти ни один расшифровщик кошелька. Расшифровка описывает вызов, стоящий перед вами. Обновляема ли контрагентская сторона и кто ею управляет — свойство более широкого мира, которое приходится проверять отдельно. Мы предпочитаем назвать границу, чем позволить уверенно выглядящему экрану одобрения намекать на полноту, которой у него нет.
Практическая проверка перед одобрением чего-то значимого
Проверен ли он? Если нет, понимайте, что вы полагаетесь на одну только репутацию.
Прокси ли это? Обозреватели это помечают — ищите уведомление о прокси или пункт «Read as Proxy». Если он есть, значение имеет реализация.
Кто может его обновить и есть ли задержка? Самый ценный вопрос в списке и тот, который почти никто не задаёт. Проект, который об этом подумал, это задокументирует. Тот, который не подумал или не говорит, уже кое-что вам сообщил.
Обязательно ли одобрение должно быть неограниченным? Обычно нет. Одобрение той суммы, которую вы действительно намерены потратить, ограничивает вашу уязвимость ровно ею, во что бы контракт ни превратился потом.
Отзывайте, когда закончили. Открытое разрешение — это постоянное полномочие, и оно переживает ваше внимание.
Мысленная поправка, которую стоит сделать, невелика, но меняет многое. Проверка говорит, какой код исполняется прямо сейчас. Обновляемость говорит, кто решает, что будет исполняться завтра. Почти весь риск живёт во втором вопросе, а почти всё предлагаемое успокоение обращено только к первому.


