Midnight 的 Compact 语言与 EVM 世界截然不同,构建屏蔽流动性 DeFi 合约时极易踩坑。作者在数月实践中整理了 6 个最致命的错误及正确模式,每个错误都曾导致电路中断或证明服务器失败。
背景:Zswap 协议与屏蔽代币的运作机制
当用户调用receiveShielded向合约转账时,Compact 运行时记录接收义务,证明服务器随后生成 ZK 证明。但合约只描述了自己的行为,交易还需平衡:收到的代币必须来自用户钱包。钱包根据ShieldedCoinInfo(币种颜色和金额)在用户隐私币集中找到对应 UTXO,生成 Zswap 所有权证明。ShieldedCoinInfo必须作为电路参数传入,因为钱包需要它在构造交易时确定待平衡的 UTXO。
错误 1:为每种资产分别创建独立QualifiedShieldedCoinInfo账本字段
这会导致证明构造问题和账本状态不一致,且无法扩展新资产。正确做法:使用Map<Bytes<32>, QualifiedShieldedCoinInfo>按币种颜色统一管理所有屏蔽资产。
错误 2:在同一个电路中同时接收和发送同一币种的屏蔽代币
这会导致公开输入不匹配错误。receiveShielded和sendShielded操作会修改同一个 UTXO 槽位,证明服务器无法平衡等式。正确做法:拆分为两个电路——一个只接收并更新状态为待处理,另一个只发送并验证状态。
例外情况:接收一种代币并铸造另一种(如 LP 代币)或接收后立即通过sendImmediateShielded销毁,因不涉及同一QualifiedShieldedCoinInfo可合并。
错误 3:将屏蔽代币余额当作非屏蔽余额对待
合约不会自动追踪屏蔽 UTXO。若收到存款后不显式存入contractShieldedBalance账本,该 UTXO 将永久丢失。每次receiveShielded后必须调用insertCoin。
最佳实践 4:始终将ShieldedCoinInfo作为电路参数传入
不要在电路内部推导代币信息。钱包需要读取参数以选择 UTXO 并生成所有权证明。前端从钱包 API 获取可用币列表,构造ShieldedCoinInfo后传入。
错误 5:将每笔存款作为独立 UTXO 存储
用Set<QualifiedShieldedCoinInfo>积累 UTXO 会导致发送时需手动筛选合并,逻辑复杂且脆弱。正确做法:每次存款用mergeCoinImmediate合并到同一颜色的单一条目中。
错误 6:发送后未处理找零sendShielded不会自动返回找零。必须检查返回的result.change,若有则insertCoin存回,若余额完全耗尽则remove该条目。否则下次发送将因 UTXO 已被花费而失败。
总结:可靠模式
状态设计:一个Map<Bytes<32>, QualifiedShieldedCoinInfo>作为所有屏蔽持有的单一事实来源;其他记账用独立非屏蔽账本。
接收:接受ShieldedCoinInfo参数 →receiveShielded→mergeCoinImmediate合并
发送:查找余额 →sendShielded→ 处理找零或移除
同一QualifiedShieldedCoinInfo的收发必须拆分电路。
安全例外:接收+铸造不同代币,或接收+立即销毁。
这些模式来自大量试错,希望帮助后来者节省时间。
#开发者 #工具 #Midnight #Compact #Zswap #ShieldedTokens #DeFi #ZK
📢 频道:@DevToolboxHub