跳到主要内容

池、事件与错误

Uniswap v4 池​

每个毕业的发射项目在一个 Uniswap v4 池里交易:代币对它的股票,费率 0.20%,tick spacing 60,运行在 HedgeFunV2Hook 上。一个 hook 实例服务所有池;每个池在它内部有自己的账本。

若已集成过其他 v4 发射 hook,请注意以下三点不同,先核对再作假设:费用一直存在 hook 里,直到有人 sweep;池子有两个锁定头寸,不是 一个;买入税在交换之前收取,所以带价格限制的买入会回滚。

池子​

货币代币和股票;哪个是 currency0 取决于地址顺序,从 vault 读 poolKey() 或从金库读 stockIsCurrency0InTokenPool()
费率lpFee 2000(0.20%),按池冻结;factory 为之后的发射接受 1 到 3000
tick spacing60
hookHedgeFunV2Hook,version() 3,地址标志位 0x28CC
创建毕业时由 factory 创建,按曲线终点价初始化
注册factory 在初始化前用 registerGraduatedWithVault 注册池子和它的 vault;未经 factory 注册的池子不能在这个 hook 上初始化

两个锁定头寸​

V2LiquidityVault,每个池一个,直接持有两个头寸;没有 LP 代币。

头寸内容区间
全区间,salt 0募资 70% 的股票,加参考设置下约 8.20% 供应量的代币可用的两端极值
单边,salt 1约 12.49% 供应量的代币,没有股票从现价代币一侧的下一个可用 tick 到可用的极端 tick

vault 没有撤出流动性、任意调用或升级的函数。collectFees() 是它唯一的流动性操作,以零流动性变动运行。取整剩余留在 lockedSeedTokens,从不报告为手续费。价格进入单边头寸的区间后,两个头寸都计入池子深度。

hook 的回调​

回调作用
beforeInitialize只对 factory 注册的池子、只对已注册的 vault 放行。任何人都无法在这个 hook 上创建非 factory 发射的池子
beforeAddLiquidity只放行该池的 vault(否则 NotSeeder)。第三方头寸会成为不缴税卖出的通道
beforeRemoveLiquidity对所有人回滚 HookNotImplemented。锁定即在此实现
beforeSwap金库以外任何人的指定输入买入(股票进、代币出):在交换前从股票里收 floor(paid × taxBps / 10000),铸成 hook 的股票 claim,发出 Taxed,让 paid − tax 进入池子。其他情况返回零 delta
afterSwap卖出:从毛股票产出里收税。固定费率下的指定输出买入:从股票输入里收 r / (1 − r)。指定输入买入:核对交换是否刚好成交 paid − tax,否则回滚 PartialFillRefused。金库自己的交换免税通过

权限位:BEFORE_INITIALIZE、BEFORE_ADD_LIQUIDITY、BEFORE_SWAP、BEFORE_SWAP_RETURNS_DELTA、AFTER_SWAP、 AFTER_SWAP_RETURNS_DELTA。指定输出的卖出被拒绝(ExactOutputRefused)。

费用的存放​

税记为 hook 持有的 ERC-6909 股票 claim,按池分账(accruedStock、owedProtocol、owedCreator、owedTreasury)。没有任何 费用按笔付出。任何人可调的 sweep(poolId) 一次性支付协议、创建者和金库的份额,每种货币各走自己的一腿;sweepTipBps 为 0。 vault 池上从来没有费用以代币形式持有,所以 Swept.tokenBurned 为 0。

池子自己的 0.20% 手续费按每笔交换的输入货币累计在 vault 的头寸上。collectFees() 把股票一侧转入金库的 buybackStock,把 代币一侧销毁。

hook 的职责范围​

  • 不限制谁能交换。任何人都可以通过任何 v4 router 交易这个池子。
  • 不对代币转账收税。HedgeFunToken 是普通 ERC20,hook 只看交换。
  • 毕业池上不运行开盘窗口:registerGraduatedWithVault 拒绝窗口,买入费率从第一笔交换起就是固定税率,buyRateBps(poolId) 等于 sellRateBps(poolId)。
  • 回购之后不抬高卖出费率:v2 上 spikeBps 为 0。
  • 不转换费用。没有以代币计的费用库存,也没有 owner 转换的步骤。

会回滚的指定输入买入​

beforeSwap 已按全额付款收取了税,因此在调用者的 sqrtPriceLimitX96 处提前停止的买入,会为未成交的股票缴税。afterSwap 以 PartialFillRefused() 拒绝它,PoolManager 包装为 CustomRevert.WrappedError(hook, afterSwap.selector, PartialFillRefused(), HookCallFailed())。像 HedgeFunV2TradeRouter 那样用最低产出限定价格,即可避免这个回滚。卖出按实际成交收税,可以在限价处 停止。解码方法见错误。

需索引的事件​

合约事件对索引器的意义
factoryLaunched(id, symbol, token, treasury, hook, stock, creator)新发射
factoryCurveLaunched(id, curve, saleBps, virtualStock)曲线及其固定目标
factoryGraduated(id, sqrtPriceX96, liquidity, stockSeeded, tokenSeeded, tokenBurned)跨线;v2 上 tokenBurned 为 0
factoryGraduationCapitalSplit(id, lpStock, treasuryStock, treasuryBooked)70/30 的金额,以及金库是否当场记账
factoryListed、ListingGatesSet、DefaultsSet、PublicLaunchSet、LauncherSet、BandCeilingSetowner 操作
curveBought(caller, recipient, stockSpent, tokensOut, burned)曲线买入;burned 是开盘加价
curveSold(caller, recipient, tokenIn, stockOut, fee)曲线卖出
curveTradeFeesAccrued(buying, fee, protocolFee, creatorFee, treasuryFee)一笔交易税款的分配
curveFeesClaimed(recipient, amount)、Ready()、Released(stockAmount, tokenAmount)、OpeningTaxExemptionsFixed(recipients)领取;毕业交接;冻结的免除地址
curve deployerCurveConfigSet、OpeningTaxExemptionsSet创建者的窗口和免除地址
treasury deployerStrategyKindSet、EngineConfigSet、KindRegistered、EngineKindRegistered、PolicyRegistered、PolicyDisabled、LpBpsSet、TreasuryCodeBound创建者的类型选择和注册表
hookRegistered(id, token, stock, treasury, protocol, creator, rates)池子冻结的费率
hookTaxed(id, selling, inToken, moved, tax, rateBps)一笔池子交易的税。moved 是交换实际移动的股票;买入时交易者付的是 moved + tax
hookSwept(id, tokenBurned, stockToTreasury, stockToProtocol, stockToCreator)一次付出;vault 池上 tokenBurned 为 0
hookClaimed、CreatorProposed、CreatorVetoed、CreatorChanged、ProtocolChanged收款地址管理
vaultSeeded(liquidity, amount0, amount1)、SurplusSeeded(lower, upper, liquidity, tokens)、SeedTokensLocked(amount)两个头寸
vaultFeesCollected(stockToTreasury, tokenBurned)池子手续费:股票进回购预算,代币销毁
treasury,分批止盈一族LotBooked(id, qty, cost, fromTax)、ProfitTaken(qty, cost, price, usdgToReserve, stockToBurn)、Stopped(qty, cost, price)、Buyback(stockSpent, tokenBurned)lot 规则
treasury,固定仓位和再平衡StrategyExecuted、KeeperRewardPaid、GainToBuyback(gain, toBuyback, avgCost, price)一次引擎动作、它的报酬,以及送去销毁的收益份额
trade routerBought(id, buyer, paymentAsset, recipient, paymentIn, tokensOut, stockRefund)、Sold(id, seller, outputAsset, tokensSpent, outputAmount, tokenRefund)经 router 的交易
native routerNativeBought(id, buyer, nativeIn, tokensOut, stockRefund)、NativeSold(id, seller, nativeOut, tokenRefund)同一笔交易,归到用户名下
launch routerNativeLaunchedAndBought(id, creator, launchFee, nativeBuyIn, tokensOut, stockRefund)一次付款的发射
升级控制器UpgradeScheduled(treasury, implementation, codeHash, dataHash, readyAt)、UpgradeExecuted、UpgradeCancelled两天公示及其结果

曲线、router 和 hook 的事件是同一次执行的不同层,成交量只计一次。原生币交易底层 router 事件里的调用者是 native router,归到 NativeBought.buyer 名下。供应量图表里三种销毁要分开:开盘加价(Bought.burned)、回购(Buyback.tokenBurned)和池子手续费 销毁(FeesCollected.tokenBurned)。毕业时不发生任何销毁,锁定的 LP 也不计为供应量减少。topic 哈希见合约仓库的 abi/SURFACE.md; 请针对实际部署的提交重新生成。

错误​

错误来源场景
Restated()factorypredict 之后默认值、上架、门限或已登记选择有变动
NotListed()、NotOpen()、BadRequest()factory股票未上架;公开发射关闭且调用者不是已授权的 launcher;某个字段超出范围
Unseedable()、SeedOverspent()factory这组条件会让 v4 注入为空、过小或无法定价;注入超出预算
NotReady()factory对不是 Ready 的曲线调 graduate(id)
BadCurveConfig()curve deployersaleBps 不是 7931,或窗口超过 180 秒
BadEngineConfig()、StopInsideExecutionFriction()、BadPolicy()treasury deployer引擎字段超出下限;止损落在 滑点 + 池费率 + 报酬 以内;policy 已停用或不匹配
TreasuryDeployFailed()treasury deployer构造函数拒绝了注册表无法具名检查的内容(循环的档位下限)
StageChanged(actual)router报价和调用之间曲线已毕业;重新报价
Expired()、TooLittle(got)、TooLittleStock(got)router、curve超过 deadline;低于 minFinalOut;低于 minStockReceived
PartialFill(spent)router有未用输入且 allowPartialFill 为 false
BadPath()、WrongPool()、BadAmount()router路线不连续或重复;池子不是 factory 指定的 V3 factory 创建的;金额为零
Closed()、Slippage()、Insolvent()、GraduationFailed()、UnsupportedTransfer()curve毕业后交易;低于调用者下限;偿付检查失败;毕业回调失败且买入回滚;转账收费或加收的股票
NotSeeder()、WrongPool()、HookNotImplemented()、ExactOutputRefused()hookvault 以外的地址添加流动性;未经 factory 注册的池子;撤出流动性;指定输出的卖出
PartialFillRefused()hook,被 v4 包装在价格限制处停止的指定输入买入
NotDue()、Unhealthy()、Cooldown()、Slippage()treasury没有待执行的动作;health() 为 false;回购限速;一笔未卖出任何资产的卖出
UseBuyback()、UseExecute()treasury回购销毁没有 execute();继承的 v1 选择器在 v2 上关闭
AlreadySeeded()、NotSeeded()、Busy()、InexactTransfer()vault第二次注入;注入前领取;重入回调;转账金额不精确
NotReady()、InvalidUpgrade()、NotOwner()升级控制器未到 readyAt;候选的代码哈希、calldata 哈希、配置哈希或 schema 不匹配;调用者不是 owner

被 v4 包装的 PartialFillRefused​

对于指定输入的买入,hook 在 beforeSwap 已按全额付款收取了税。如果交换随后在调用者的 sqrtPriceLimitX96 处停止,afterSwap 以 PartialFillRefused() 回滚,买家因此不会为未成交的股票缴税。PoolManager 会把它包装一层,调用者看到的回滚是:

CustomRevert.WrappedError(address hook, bytes4 afterSwap.selector, bytes PartialFillRefused(), bytes HookCallFailed())

解码内层原因,或按 WrappedError 选择器加 hook 地址匹配。用最低产出限价的 router(HedgeFunV2TradeRouter 即属此类)不受 影响;在买入时用价格限制的第三方 router 会遇到。卖出按实际成交收税,可以在限价处停止。

每个错误的选择器见合约仓库实际部署提交处的 REFERENCE.md 和 abi/SURFACE.md。