Перейти к содержимому
PortunaPortunaPortunaДокументацияTestnet

Проверка делегата

Депозитные адреса подписывают авторизацию на адрес delegate, который возвращает API. До первой такой подписи убедитесь, что по этому адресу лежит открытый код контракта, собранный для вашего горячего кошелька (котла) и вашего газ-кошелька. Доверять для этого ни Portuna, ни обозревателю блоков не нужно.

Все контракты создаются через CREATE2. Адрес такого контракта — хэш от адреса создателя, соли и полного кода создания: байткода с аргументами конструктора. Изменится хоть один байт кода или горячий кошелёк внутри — изменится и адрес. Поэтому:

  • адрес фабрики следует из её байткода и стандартного деплоера 0x4e59b44847b379578588920cA78FbF26c0B4956C с нулевой солью — он одинаков во всех сетях;
  • адрес вашего батчера — из фабрики, байткода батчера и вашего газ-кошелька;
  • адрес делегата — из фабрики, байткода делегата, горячего кошелька и вашего батчера.

Если адрес, посчитанный из кода, совпадает с delegate из API и по нему есть код, значит, делегат — это именно этот код: он платит только в ваш горячий кошелёк и слушается только вашего батчера.

В 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>
chain 11155111
factory 0x66f63A7246dF0eD76098dD95ad0F53d8e7bed456
batcher 0xD501D131204CaDD7EF8ec07a2d15FfAc05770a63 of gas wallet 0xf0259b04f5D336E624A84780d1B365fAFc3f5A85
delegate 0xE60fdf70a794C76094f86b990a821D55e3096AA5 of hot wallet 0x2Cd50979a8A33e8CA37DA85F8517fb148a67b769
PASS factory code 3671 bytes at 0x66f63A7246dF0eD76098dD95ad0F53d8e7bed456
PASS batcher code 1195 bytes at 0xD501D131204CaDD7EF8ec07a2d15FfAc05770a63
PASS delegate code 955 bytes at 0xE60fdf70a794C76094f86b990a821D55e3096AA5
PASS 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.
ФлагЧто значит
-rpcRPC сети; по умолчанию — $RPC_URL. В выводе RPC назван только хостом: в пути у провайдеров ключи.
-gas-walletВаш газ-кошелёк — gas_wallet в GET /v1/balance.
-hot-walletГорячий кошелёк.
-expect-delegatedelegate, который вернул 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")
}

Чтобы проверить без SDK, соберите контракты из исходников в Foundry. Компилятор и его настройки закреплены (solc 0.8.33, оптимизатор на 200 прогонов, EVM prague; см. Контракты), поэтому одни и те же исходники дают один и тот же байткод. В дереве исходников выполните cd contracts && forge build, затем:

  1. Посчитайте адрес фабрики и сравните с опубликованным:

    Окно терминала
    SALT=0x0000000000000000000000000000000000000000000000000000000000000000
    cast compute-address 0x4e59b44847b379578588920cA78FbF26c0B4956C --salt $SALT \
    --init-code-hash $(cast keccak $(jq -r .bytecode.object out/SweepFactory.sol/SweepFactory.json))
  2. Посчитайте адреса своего батчера и делегата (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.

  3. Убедитесь, что по этим адресам есть код, а после первого sweep — что код депозитного адреса — это метка делегации 0xef0100 и адрес делегата:

    Окно терминала
    cast codesize <адрес> --rpc-url <RPC>
    cast code <депозитный адрес> --rpc-url <RPC>

Компилятор дописывает в байткод хэш метаданных, куда входят и исходники: собирайте ровно из тех исходников, ничего в них не меняя. Даже изменённый комментарий даст другой байткод и другой адрес.