feat: 添加 LP 流动性池边界测试

新增 LP 边界测试功能:
- 添加流动性边界测试:
  - 添加金额为0 (InvalidAmount)
  - 添加超过代币余额 (InsufficientBalance)

- 移除流动性边界测试:
  - 移除金额为0 (InvalidAmount)
  - 移除超过ytLP余额 (InsufficientBalance)
  - 移除时minOut过高 (InsufficientOutput)

- 代币互换边界测试:
  - 互换金额为0 (InvalidAmount)
  - 相同代币互换 (SameToken)
  - 互换超过余额 (InsufficientBalance)

UI功能:
- 可折叠的边界测试区域
- 显示冷却时间和ytLP余额状态
- 中英文翻译支持

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-16 07:35:35 +00:00
parent c16d846858
commit d3ca908d69
3 changed files with 235 additions and 2 deletions

View File

@@ -60,6 +60,7 @@ export function LPPanel() {
slippage: '0.5', slippage: '0.5',
}) })
const [activeTab, setActiveTab] = useState<'add' | 'remove' | 'swap'>('add') const [activeTab, setActiveTab] = useState<'add' | 'remove' | 'swap'>('add')
const [showBoundaryTest, setShowBoundaryTest] = useState(false)
// Read pool data // Read pool data
const { data: ytLPBalance, refetch: refetchBalance } = useReadContract({ const { data: ytLPBalance, refetch: refetchBalance } = useReadContract({
@@ -295,6 +296,88 @@ export function LPPanel() {
return token?.symbol || 'Unknown' return token?.symbol || 'Unknown'
} }
// Boundary test function
const runBoundaryTest = (testType: string) => {
if (!address) return
recordTx('test', undefined, 'LP')
switch (testType) {
case 'add_zero':
// 添加流动性金额为0
writeContract({
address: CONTRACTS.YT_REWARD_ROUTER,
abi: YT_REWARD_ROUTER_ABI,
functionName: 'addLiquidity',
args: [CONTRACTS.VAULTS.YT_A, BigInt(0), BigInt(0), BigInt(0)],
})
break
case 'add_exceed_balance':
// 添加超过余额
writeContract({
address: CONTRACTS.YT_REWARD_ROUTER,
abi: YT_REWARD_ROUTER_ABI,
functionName: 'addLiquidity',
args: [CONTRACTS.VAULTS.YT_A, parseUnits('999999999', 18), BigInt(0), BigInt(0)],
})
break
case 'remove_zero':
// 移除流动性金额为0
writeContract({
address: CONTRACTS.YT_REWARD_ROUTER,
abi: YT_REWARD_ROUTER_ABI,
functionName: 'removeLiquidity',
args: [CONTRACTS.VAULTS.YT_A, BigInt(0), BigInt(0), address],
})
break
case 'remove_exceed_balance':
// 移除超过ytLP余额
const exceedAmount = ytLPBalance ? ytLPBalance + parseUnits('999999', 18) : parseUnits('999999999', 18)
writeContract({
address: CONTRACTS.YT_REWARD_ROUTER,
abi: YT_REWARD_ROUTER_ABI,
functionName: 'removeLiquidity',
args: [CONTRACTS.VAULTS.YT_A, exceedAmount, BigInt(0), address],
})
break
case 'remove_high_minout':
// 移除时minOut过高
writeContract({
address: CONTRACTS.YT_REWARD_ROUTER,
abi: YT_REWARD_ROUTER_ABI,
functionName: 'removeLiquidity',
args: [CONTRACTS.VAULTS.YT_A, parseUnits('1', 18), parseUnits('999999999', 18), address],
})
break
case 'swap_zero':
// 互换金额为0
writeContract({
address: CONTRACTS.YT_REWARD_ROUTER,
abi: YT_REWARD_ROUTER_ABI,
functionName: 'swapYT',
args: [CONTRACTS.VAULTS.YT_A, CONTRACTS.VAULTS.YT_B, BigInt(0), BigInt(0), address],
})
break
case 'swap_same_token':
// 相同代币互换
writeContract({
address: CONTRACTS.YT_REWARD_ROUTER,
abi: YT_REWARD_ROUTER_ABI,
functionName: 'swapYT',
args: [CONTRACTS.VAULTS.YT_A, CONTRACTS.VAULTS.YT_A, parseUnits('1', 18), BigInt(0), address],
})
break
case 'swap_exceed_balance':
// 互换超过余额
writeContract({
address: CONTRACTS.YT_REWARD_ROUTER,
abi: YT_REWARD_ROUTER_ABI,
functionName: 'swapYT',
args: [CONTRACTS.VAULTS.YT_A, CONTRACTS.VAULTS.YT_B, parseUnits('999999999', 18), BigInt(0), address],
})
break
}
}
// Calculate cooldown remaining time // Calculate cooldown remaining time
const getCooldownRemaining = () => { const getCooldownRemaining = () => {
if (!lastAddedAt || !cooldownDuration) return 0 if (!lastAddedAt || !cooldownDuration) return 0
@@ -615,6 +698,122 @@ export function LPPanel() {
</div> </div>
)} )}
{/* 边界测试区域 */}
<div className="section">
<div className="section-header" onClick={() => setShowBoundaryTest(!showBoundaryTest)} style={{ cursor: 'pointer' }}>
<h3>{t('test.boundaryTests')} {showBoundaryTest ? '[-]' : '[+]'}</h3>
</div>
{showBoundaryTest && (
<>
<div className="test-hint">{t('lp.boundaryHint')}</div>
{/* 冷却时间状态 */}
<div className="redeem-status">
<span>{t('lp.cooldownRemaining')}: </span>
<strong className={getCooldownRemaining() > 0 ? 'text-warning' : 'text-success'}>
{formatCooldown(getCooldownRemaining())}
</strong>
<span style={{ marginLeft: '16px' }}>{t('lp.yourBalance')}: </span>
<strong>{ytLPBalance ? formatUnits(ytLPBalance, 18) : '0'} ytLP</strong>
</div>
<div className="test-grid">
{/* 添加流动性边界测试 */}
<div className="test-card">
<div className="test-header">
<span className="test-name">{t('lp.testAddZero')}</span>
<span className="test-error">InvalidAmount</span>
</div>
<p className="test-desc">{t('lp.testAddZeroDesc')}</p>
<button onClick={() => runBoundaryTest('add_zero')} disabled={isPending || isConfirming} className="btn btn-warning btn-sm">
{t('test.run')}
</button>
</div>
<div className="test-card">
<div className="test-header">
<span className="test-name">{t('lp.testAddExceed')}</span>
<span className="test-error">InsufficientBalance</span>
</div>
<p className="test-desc">{t('lp.testAddExceedDesc')}</p>
<button onClick={() => runBoundaryTest('add_exceed_balance')} disabled={isPending || isConfirming} className="btn btn-warning btn-sm">
{t('test.run')}
</button>
</div>
{/* 移除流动性边界测试 */}
<div className="test-card">
<div className="test-header">
<span className="test-name">{t('lp.testRemoveZero')}</span>
<span className="test-error">InvalidAmount</span>
</div>
<p className="test-desc">{t('lp.testRemoveZeroDesc')}</p>
<button onClick={() => runBoundaryTest('remove_zero')} disabled={isPending || isConfirming} className="btn btn-warning btn-sm">
{t('test.run')}
</button>
</div>
<div className="test-card">
<div className="test-header">
<span className="test-name">{t('lp.testRemoveExceed')}</span>
<span className="test-error">InsufficientBalance</span>
</div>
<p className="test-desc">{t('lp.testRemoveExceedDesc')}</p>
<button onClick={() => runBoundaryTest('remove_exceed_balance')} disabled={isPending || isConfirming} className="btn btn-warning btn-sm">
{t('test.run')}
</button>
</div>
<div className="test-card">
<div className="test-header">
<span className="test-name">{t('lp.testRemoveHighMinout')}</span>
<span className="test-error">InsufficientOutput</span>
</div>
<p className="test-desc">{t('lp.testRemoveHighMinoutDesc')}</p>
<button onClick={() => runBoundaryTest('remove_high_minout')} disabled={isPending || isConfirming} className="btn btn-warning btn-sm">
{t('test.run')}
</button>
</div>
{/* 代币互换边界测试 */}
<div className="test-card">
<div className="test-header">
<span className="test-name">{t('lp.testSwapZero')}</span>
<span className="test-error">InvalidAmount</span>
</div>
<p className="test-desc">{t('lp.testSwapZeroDesc')}</p>
<button onClick={() => runBoundaryTest('swap_zero')} disabled={isPending || isConfirming} className="btn btn-warning btn-sm">
{t('test.run')}
</button>
</div>
<div className="test-card">
<div className="test-header">
<span className="test-name">{t('lp.testSwapSame')}</span>
<span className="test-error">SameToken</span>
</div>
<p className="test-desc">{t('lp.testSwapSameDesc')}</p>
<button onClick={() => runBoundaryTest('swap_same_token')} disabled={isPending || isConfirming} className="btn btn-warning btn-sm">
{t('test.run')}
</button>
</div>
<div className="test-card">
<div className="test-header">
<span className="test-name">{t('lp.testSwapExceed')}</span>
<span className="test-error">InsufficientBalance</span>
</div>
<p className="test-desc">{t('lp.testSwapExceedDesc')}</p>
<button onClick={() => runBoundaryTest('swap_exceed_balance')} disabled={isPending || isConfirming} className="btn btn-warning btn-sm">
{t('test.run')}
</button>
</div>
</div>
</>
)}
</div>
{/* Transaction History */} {/* Transaction History */}
<TransactionHistory transactions={transactions} onClear={clearHistory} /> <TransactionHistory transactions={transactions} onClear={clearHistory} />
</div> </div>

View File

@@ -178,6 +178,23 @@
"swap": "Swap", "swap": "Swap",
"cooldownNotPassed": "Cooldown not passed, please try later", "cooldownNotPassed": "Cooldown not passed, please try later",
"insufficientOutput": "Insufficient output amount", "insufficientOutput": "Insufficient output amount",
"cooldownWarning": "Cooldown remaining {{time}}, cannot remove liquidity yet" "cooldownWarning": "Cooldown remaining {{time}}, cannot remove liquidity yet",
"boundaryHint": "These tests are designed to trigger LP contract errors. Expected errors are shown for each test.",
"testAddZero": "Add Liquidity Zero",
"testAddZeroDesc": "Test addLiquidity(token, 0, 0, 0)",
"testAddExceed": "Add Exceed Balance",
"testAddExceedDesc": "Add liquidity exceeding token balance",
"testRemoveZero": "Remove Liquidity Zero",
"testRemoveZeroDesc": "Test removeLiquidity(token, 0, 0, receiver)",
"testRemoveExceed": "Remove Exceed Balance",
"testRemoveExceedDesc": "Remove liquidity exceeding ytLP balance",
"testRemoveHighMinout": "Remove High MinOut",
"testRemoveHighMinoutDesc": "Set impossible minimum output amount",
"testSwapZero": "Swap Amount Zero",
"testSwapZeroDesc": "Test swapYT(tokenIn, tokenOut, 0, 0, receiver)",
"testSwapSame": "Swap Same Token",
"testSwapSameDesc": "Test swapYT(YT-A, YT-A, amount, 0, receiver)",
"testSwapExceed": "Swap Exceed Balance",
"testSwapExceedDesc": "Swap amount exceeding token balance"
} }
} }

View File

@@ -178,6 +178,23 @@
"swap": "交换", "swap": "交换",
"cooldownNotPassed": "冷却期未过,请稍后再试", "cooldownNotPassed": "冷却期未过,请稍后再试",
"insufficientOutput": "输出金额不足", "insufficientOutput": "输出金额不足",
"cooldownWarning": "冷却期剩余 {{time}},暂时无法移除流动性" "cooldownWarning": "冷却期剩余 {{time}},暂时无法移除流动性",
"boundaryHint": "这些测试旨在触发LP合约错误。每个测试都显示了预期的错误类型。",
"testAddZero": "添加流动性为0",
"testAddZeroDesc": "测试 addLiquidity(token, 0, 0, 0)",
"testAddExceed": "添加超过余额",
"testAddExceedDesc": "添加流动性金额超过代币余额",
"testRemoveZero": "移除流动性为0",
"testRemoveZeroDesc": "测试 removeLiquidity(token, 0, 0, receiver)",
"testRemoveExceed": "移除超过余额",
"testRemoveExceedDesc": "移除流动性金额超过ytLP余额",
"testRemoveHighMinout": "移除minOut过高",
"testRemoveHighMinoutDesc": "设置不可能达到的最小输出金额",
"testSwapZero": "互换金额为0",
"testSwapZeroDesc": "测试 swapYT(tokenIn, tokenOut, 0, 0, receiver)",
"testSwapSame": "相同代币互换",
"testSwapSameDesc": "测试 swapYT(YT-A, YT-A, amount, 0, receiver)",
"testSwapExceed": "互换超过余额",
"testSwapExceedDesc": "互换金额超过代币余额"
} }
} }