Проверка делегата
Депозитные адреса подписывают авторизацию на адрес delegate, который возвращает API. До первой такой подписи
убедитесь, что по этому адресу лежит открытый код контракта, собранный для вашего горячего кошелька (котла) и вашего
газ-кошелька. Доверять для этого ни Portuna, ни обозревателю блоков не нужно.
Почему адрес доказывает код
Заголовок раздела «Почему адрес доказывает код»Все контракты создаются через CREATE2. Адрес такого контракта — хэш от адреса создателя, соли и полного кода
создания: байткода с аргументами конструктора. Изменится хоть один байт кода или горячий кошелёк внутри — изменится
и адрес. Поэтому:
- адрес фабрики следует из её байткода и стандартного деплоера
0x4e59b44847b379578588920cA78FbF26c0B4956Cс нулевой солью — он одинаков во всех сетях; - адрес вашего батчера — из фабрики, байткода батчера и вашего газ-кошелька;
- адрес делегата — из фабрики, байткода делегата, горячего кошелька и вашего батчера.
Если адрес, посчитанный из кода, совпадает с delegate из API и по нему есть код, значит, делегат — это именно этот
код: он платит только в ваш горячий кошелёк и слушается только вашего батчера.
Командой verify-delegate
Заголовок раздела «Командой verify-delegate»В SDK есть команда verify-delegate. Она считает адреса фабрики, вашего батчера и делегата из байткода контрактов,
встроенного в SDK, проверяет, что по каждому есть код в сети, сравнивает делегат с ответом API, а с -deposit —
проверяет код депозитных адресов после их первого sweep.
В каталоге Go SDK:
go run ./cmd/verify-delegate -rpc https://ethereum-sepolia-rpc.publicnode.com \ -gas-wallet <gas_wallet из GET /v1/balance> \ -hot-wallet <ваш горячий кошелёк> \ -expect-delegate <delegate из POST /v1/hot-wallets>В каталоге TypeScript SDK, после npm ci:
npx verify-delegate --rpc https://ethereum-sepolia-rpc.publicnode.com \ --gas-wallet <gas_wallet из GET /v1/balance> \ --hot-wallet <ваш горячий кошелёк> \ --expect-delegate <delegate из POST /v1/hot-wallets>chain 11155111factory 0x66f63A7246dF0eD76098dD95ad0F53d8e7bed456batcher 0xD501D131204CaDD7EF8ec07a2d15FfAc05770a63 of gas wallet 0xf0259b04f5D336E624A84780d1B365fAFc3f5A85delegate 0xE60fdf70a794C76094f86b990a821D55e3096AA5 of hot wallet 0x2Cd50979a8A33e8CA37DA85F8517fb148a67b769
PASS factory code 3671 bytes at 0x66f63A7246dF0eD76098dD95ad0F53d8e7bed456PASS batcher code 1195 bytes at 0xD501D131204CaDD7EF8ec07a2d15FfAc05770a63PASS delegate code 955 bytes at 0xE60fdf70a794C76094f86b990a821D55e3096AA5PASS delegate from the API 0xE60fdf70a794C76094f86b990a821D55e3096AA5 from the API is the computed one
PASS: the delegate 0xE60fdf70a794C76094f86b990a821D55e3096AA5 is built from the open code for hot wallet 0x2Cd50979a8A33e8CA37DA85F8517fb148a67b769 and gas wallet 0xf0259b04f5D336E624A84780d1B365fAFc3f5A85.| Флаг | Что значит |
|---|---|
-rpc | RPC сети; по умолчанию — $RPC_URL. В выводе RPC назван только хостом: в пути у провайдеров ключи. |
-gas-wallet | Ваш газ-кошелёк — gas_wallet в GET /v1/balance. |
-hot-wallet | Горячий кошелёк. |
-expect-delegate | delegate, который вернул API, — сравнивается с посчитанным. |
-factory | Опубликованный адрес фабрики — сравнивается с посчитанным и используется вместо него. |
-deposit | Депозитный адрес, который уже должен быть делегирован на этот делегат; можно указать несколько раз. |
Флаги у обеих команд одинаковые, с одним дефисом или с двумя. Код выхода — 0, если все проверки прошли, 1 — если хоть одна не прошла, 2 — при неверных аргументах, так что команду удобно ставить в CI или в скрипт подключения горячего кошелька.
Из своего кода
Заголовок раздела «Из своего кода»Те же проверки — функцией VerifyDeployment (verifyDeployment):
eth, err := ethclient.DialContext(ctx, os.Getenv("RPC_URL"))if err != nil { log.Fatal(err)}report, err := portuna.VerifyDeployment(ctx, eth, portuna.Deployment{ GasWallet: common.HexToAddress(os.Getenv("GAS_WALLET")), HotWallet: common.HexToAddress(os.Getenv("HOT_WALLET")), ExpectDelegate: common.HexToAddress(os.Getenv("DELEGATE")),})if err != nil { log.Fatal(err)}for _, c := range report.Checks { fmt.Println(c.OK, c.Name, c.Detail)}if !report.OK() { log.Fatal("do not sign authorizations for this delegate")}const chain = createPublicClient({ transport: http(process.env.RPC_URL) })const report = await verifyDeployment(chain, { gasWallet: process.env.GAS_WALLET as Address, hotWallet: process.env.HOT_WALLET as Address, expectDelegate: process.env.DELEGATE as Address,})for (const c of report.checks) console.log(c.ok ? 'PASS' : 'FAIL', c.name, c.detail)if (!report.ok) throw new Error('do not sign authorizations for this delegate')Вручную, в Foundry
Заголовок раздела «Вручную, в Foundry»Чтобы проверить без SDK, соберите контракты из исходников в Foundry. Компилятор и его
настройки закреплены (solc 0.8.33, оптимизатор на 200 прогонов, EVM prague; см. Контракты),
поэтому одни и те же исходники дают один и тот же байткод. В дереве исходников выполните cd contracts && forge build, затем:
-
Посчитайте адрес фабрики и сравните с опубликованным:
Окно терминала SALT=0x0000000000000000000000000000000000000000000000000000000000000000cast compute-address 0x4e59b44847b379578588920cA78FbF26c0B4956C --salt $SALT \--init-code-hash $(cast keccak $(jq -r .bytecode.object out/SweepFactory.sol/SweepFactory.json)) -
Посчитайте адреса своего батчера и делегата (
GAS— ваш газ-кошелёк,HOT— горячий кошелёк):Окно терминала FACTORY=<адрес из шага 1>BATCHER=$(cast compute-address $FACTORY --salt $SALT --init-code \$(jq -r .bytecode.object out/SweepBatcher.sol/SweepBatcher.json)$(cast abi-encode 'f(address)' $GAS | cut -c3-) | awk '{print $3}')cast compute-address $FACTORY --salt $SALT --init-code \$(jq -r .bytecode.object out/SweepDelegate.sol/SweepDelegate.json)$(cast abi-encode 'f(address,address)' $HOT $BATCHER | cut -c3-)Результат должен совпасть с
delegateизPOST /v1/hot-wallets. -
Убедитесь, что по этим адресам есть код, а после первого sweep — что код депозитного адреса — это метка делегации
0xef0100и адрес делегата:Окно терминала cast codesize <адрес> --rpc-url <RPC>cast code <депозитный адрес> --rpc-url <RPC>
Компилятор дописывает в байткод хэш метаданных, куда входят и исходники: собирайте ровно из тех исходников, ничего в них не меняя. Даже изменённый комментарий даст другой байткод и другой адрес.