bath: recognize Hipo liquid staking - #939
Conversation
|
Update: Hipo re-enabled instant minting today, so a fresh instant-stake trace landed within public-liteserver retention. Re-pinned the |
|
Thanks for approving the workflow run! The CI failure is unrelated to this PR — it's the fork-secrets limitation:
If it helps, I can add a |
|
Thank you for the PR! I keep in mind it, but it may take few more days for me to understand all changes |
Hipo (https://hipo.finance) is a liquid-staking protocol for the native coin: a staker sends GRAM to the treasury (0:8bc991cfe177bc7e9721433efa3befd199485a55cffd040a06c89af026b71bcf) and receives hGRAM jettons from the parent (0:cf76af318c0872b58a9f1925fc29c156211782b9fb01f56760d292e56123bf87); the pooled GRAM is lent to validators one validation round at a time. Today its traces are shown as a chain of unclassified smart-contract calls, and an unstake shows up as a bare jetton burn followed by unrelated-looking bill transactions. This adds straws for the four user-facing shapes: instant stake deposit_coins -> treasury -> proxy_tokens_minted -> parent -> tokens_minted -> transfer_notification deferred stake deposit_coins -> treasury -> {proxy_save_coins -> save_coins} + {mint_bill -> assign_bill -> ownership_assigned} instant unstake burn -> proxy_reserve_tokens -> reserve_tokens -> proxy_tokens_burned -> tokens_burned -> withdrawal_notification (carries the GRAM) deferred unstake same head, then mint_bill -> assign_bill -> ownership_assigned Instant vs deferred depends on whether the treasury holds enough liquid GRAM: instant settles in the same trace, deferred mints an SBT ("bill") that is redeemed when the round is finalized. Action shape: these build the pool-shaped DepositStake / WithdrawStakeRequest / WithdrawStake and add a "hipo" PoolImplementationType rather than reusing DepositTokenStake with a free-form core.Protocol. The token-stake shape was introduced for jetton-in vaults (Ethena, Affluent, FFVault) and does not fit here: - Hipo stakes the native coin and returns a jetton receipt, which is structurally the same as Tonstakers/liquidTF. - DepositStakeAction renders as "Deposit N GRAM to staking pool" and carries a nanoGram amount; DepositTokenStakeAction renders as "Staked with Hipo protocol" with no amount in the sentence, and WithdrawTokenStakeRequestAction hardcodes its preview value to "ALL", which is wrong for a partial unstake. - The token-stake actions have no Pool field, so SubjectAccounts() returns only the staker and the action would never be attributed to the treasury in account-scoped event queries. - There is no completed WithdrawTokenStake action, so an instant unstake - which pays the GRAM out in the same trace - could not be reported as a finished withdrawal. The enum change is one line in api/openapi.yml plus regenerated pkg/oas (verified reproducible: regenerating from the unmodified spec is a no-op). Clients pinned to the old spec will reject "hipo" in PoolImplementationType.Validate() until they regenerate, the same one-time cost "ffvault" already imposed. Hipo pools are not served by /v2/staking/pools yet; that would be a separate change. A rolled-back unstake is the trap here. When the treasury cannot honour an unstake it answers proxy_rollback_unstake and then throw(0), which keeps the outgoing message but leaves storage untouched, so the transaction succeeds and the trace is shaped exactly like a deferred unstake. hipoUnstakeNotRolledBack rejects it so it stays a plain jetton burn instead of becoming a withdraw request that never happened. Only deposit_coins, proxy_tokens_minted and tokens_minted are declared in the pinned tongo release, so the other ops are matched by raw opcode with HasOpcode. Once tongo ships the full abi/schemas/hipo_finance.xml these can use abi.HipoFinance*MsgOp names, and the exact payout can be read from withdrawal_notification instead of being derived from the message value minus the staker's gas prepayment (the same netting WithdrawLiquidStake does for Tonstakers). Tests: golden traces for deferred stake, instant unstake and deferred unstake. The instant-stake and rollback cases carry real mainnet hashes and block ids but are skipped: both shapes are rare - one occurrence each in the last month of treasury history - and their blocks are past the retention window of the public liteservers, so their golden files have to be recorded against an archive liteserver. TestHipoUnstakeNotRolledBack and TestHipoBillAssigned cover the two tricky predicates without network access. No existing golden output changed.
Hipo re-enabled instant minting on 2026-08-05, so a recent instant-stake trace is now available within public-liteserver retention. Re-pin the previously skipped case to it and record the golden the usual way; no archive liteserver needed anymore. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
027a05b to
cefc0c9
Compare
|
Thanks — no rush at all. Heads up that the branch had gone conflicting against master, so I've rebased it onto The diff against master is unchanged at +731/-1 across 15 files, so nothing was lost or duplicated in the rebase. |
What
Adds
pkg/bathstraws for Hipo, the liquid staking protocol on TON (treasuryEQCLyZHP4Xe8fpchQz76O-_RmUhaVc_9BAoGyJrwJrcbz2eZ, jetton hGRAM, formerly hTON). Today a Hipo unstake renders as a plain jetton Burn and a deposit as generic contract calls; with this PR the four user-facing patterns collapse into staking actions, the same way Tonstakers (liquidTF) traces do. I'm from the Hipo team — I understand straws are usually written by you, so treat this as a proposal; happy to rework it however you prefer (tracking issue with full context: #938).New straws in
pkg/bath/hipo.go, registered at the end ofDefaultStraws:DepositHipoStakeStrawdeposit_coins→proxy_tokens_minted→tokens_mintedDepositStakeDepositHipoStakeDeferredStrawdeposit_coins→proxy_save_coins+ bill SBT mintDepositStake(settles at round end)WithdrawHipoStakeRequestStrawreserve_tokens→ bill SBT mintWithdrawStakeRequestWithdrawHipoStakeStrawreserve_tokens→proxy_tokens_burned→withdrawal_notificationWithdrawStakeMessage schemas:
contracts/schema.tlb; trace diagrams:graphs/.Design notes
hipoStakingImplementationrather thanDepositTokenStake/WithdrawTokenStakeRequest: Hipo stakes the native coin with a jetton receipt, structurally like Tonstakers. The token-stake actions carry no pool account (SubjectAccounts()would never attribute the treasury),convertWithdrawTokenStakeRequesthardcodesValue: "ALL"(wrong for partial unstakes), and there is no completedWithdrawTokenStake, so an instant unstake couldn't render at all. The oas diff is generated:go generatefrom the unmodified spec is a byte-for-byte no-op, so the enum addition is exactly the 11 generated lines.pkg/references/hipo.go), not the jetton master — the master can be replaced by a protocol upgrade (old_parentsmechanism), the treasury cannot. New ops are matched by opcode constants; only ops already present in the pinned tongo release useabi.*MsgOp(abi: complete Hipo Finance message schemas tongo#499 completes the ABI separately, no dependency between the PRs).NftTransferNotifyStrawruns first and merges the bill'sassign_bill/ownership_assignedinto aBubbleNftTransfer, so the bill matcher accepts both shapes.proxy_rollback_unstakeand thenthrow(0)— the tx reports success and the trace looks exactly like a deferred unstake.hipoUnstakeNotRolledBackrejects it so it stays a plain jetton Burn (covered by a no-network unit test inhipo_test.go).Testing
bath_test.gofor deferred stake, deferred unstake, and instant unstake, pinned to real mainnet traces; amounts verified against the on-chain values.hipo-instant-stake, block 85114134, andhipo-rollback-unstake, block 62680498) are committed with real trace hashes butskip: true: both blocks are past public-liteserver retention, and no newer occurrence of either shape exists (instant stake only happens between validation rounds; scanned ~1000 recent treasury txs). If you can pointLITE_SERVERSat an archive node, un-skipping and copying the.output.jsonrecords the goldens.go build ./...clean; fullpkg/bathsuite shows no golden regressions (the only failures are the pre-existing environmentalnot found txones that also affect existing cases on master).Not in scope here: round-end settlement legs (they can't misclassify — verified) and listing Hipo in
/v2/staking/pools, which needs pool-info plumbing; can follow up on either if you want them.🤖 Generated with Claude Code