题目
存款页用 convertToShares 显示预计份额,确认时直接调用 deposit(assets, receiver)。赎回页用相同换算逻辑显示到账,用户却发现数量有偏差,有额度时也可能回滚。请审查这套流程,包括报价、额度、精度和最低到账保护。
考察目标
- 区分参考换算与具体操作的预览。
- 识别预览没有覆盖的余额、授权和限额。
- 说明前端报价与链上保护的职责。
区分金库换算、操作预览、限额与链上滑点保护,并处理资产和份额的精度差异。
存款页用 convertToShares 显示预计份额,确认时直接调用 deposit(assets, receiver)。赎回页用相同换算逻辑显示到账,用户却发现数量有偏差,有额度时也可能回滚。请审查这套流程,包括报价、额度、精度和最低到账保护。
convert 系列提供不含费用的理想换算,preview 系列对应具体存取操作;max 系列另管协议限额。页面按用户输入方向选择函数,再检查余额、授权和精度。preview 只反映调用时状态,不保证提交后的结果;标准 deposit 没有最低份额参数,需要支持该校验的 router 或扩展合约才能在同一交易里约束最低到账。确认后从事件核对实际份额。
份额本身是 ERC-20,份额 decimals 可能与底层资产不同。输入资产数量用 previewDeposit,目标份额用 previewMint;取出指定资产用 previewWithdraw,销毁指定份额用 previewRedeem。四者依次预估获得份额、支付资产、销毁份额、获得资产,包含相应操作费用。预览的保守方向分别是不高估获得量、不低估支付量;convert 两个方向向下取整,不能替代具体操作报价。
preview 不替用户检查余额、allowance 或 max 限额;一次预览成功不代表可以提交。协议上限使用 max 系列,余额与授权另外读取。为他人赎回时还需检查 owner、receiver、实际 caller 与 share allowance。将相关读数固定到同一区块,确认时刷新并模拟调用;超时或回滚应保留输入,让用户能重新报价。
新金库的 donation/inflation 风险取决于实际实现;OpenZeppelin 的虚拟份额等防护不能推定任意金库都已采用。警惕零份额、小额存款和异常份额价格。刷新报价只能缩短窗口,不能阻止报价后状态变化。标准 deposit 不接收 minShares;前端计算下限后还需要受信任的 router/扩展在交易内部检查。没有这项能力就不能承诺“滑点已保护”。
从对应金库、receiver 的 Deposit/Withdraw 事件及状态变化核算,不能把交易回执当作 Solidity 返回值。确认前的模拟结果仍是预估;重组后撤回确认状态并重新对账。净值展示和年化计算是另一条数据链路,不把可操纵的 preview 当成安全价格预言机。
以下函数只读取同一区块的存款报价与协议上限,不执行存款,也不提供滑点保护;RPC 和真实金库地址由调用方传入。
import { createPublicClient, http, parseAbi, type Address } from "viem";
import { mainnet } from "viem/chains";
const abi = parseAbi([
"function previewDeposit(uint256 assets) view returns (uint256)",
"function maxDeposit(address receiver) view returns (uint256)",
]);
export async function quoteDeposit(
rpcUrl: string,
vault: Address,
receiver: Address,
assets: bigint,
) {
if (assets <= 0n) throw new Error("存款数量应大于零");
const client = createPublicClient({
chain: mainnet,
transport: http(rpcUrl),
});
const blockNumber = await client.getBlockNumber();
const [shares, maxAssets] = await Promise.all([
client.readContract({
address: vault,
abi,
functionName: "previewDeposit",
args: [assets],
blockNumber,
}),
client.readContract({
address: vault,
abi,
functionName: "maxDeposit",
args: [receiver],
blockNumber,
}),
]);
return { blockNumber, shares, maxAssets, withinLimit: assets <= maxAssets };
}
previewRedeem 回答铸造相同份额要支付多少资产。minShares 变量当作标准 deposit 的链上保护。previewDeposit 非零、maxDeposit 为零,页面怎样处理?发现这道题有问题? 反馈此题