YV
YieldVault Protocol — Smart Contract Test Suite
Hardhat 2.22 + Foundry · Solidity 0.8.24 · Ethereum Mainnet Fork (block 19,000,000)
All Tests Passing · 2026-06-18 09:41 UTC
Powered by CULTIVA IA · testing-contratos-inteligentes-web3
Tests Passing
47
/ 47 total · 0 failed
Coverage Global
97.2%
Statements · Objetivo ≥95%
Fuzz Runs
10K
por función · sin regresiones
Avg Gas · Deposit
61,420
Objetivo <80K ✓
CI Duration
4m 18s
GitHub Actions · ubuntu-22.04
Cobertura de código (solidity-coverage)
Informe de cobertura · contratos/ npx hardhat coverage
Archivo % Statements % Branches % Functions % Lines Uncovered Lines
YieldVault.sol 98.1%
96.4%
100%
97.8%
L182, L247
YVLToken.sol 100%
97.5%
100%
100%
Staking.sol 96.3%
91.2%
100%
95.8%
L88, L91, L315
TOTAL 97.2% 94.7% 100% 97.1% ✓ Objetivo cumplido
Resultados de tests (Hardhat · Chai)
🏦 YieldVault.sol 19 passing
✓ 19 ✗ 0 3.41s
Deployment · should set owner and initial state correctly
Owner asignado, paused=false, feeRecipient=treasury
42ms
Deposit · should accept USDC and mint vault shares
1000 USDC → 1000 yvUSDC al 1:1 en primer depósito
81ms
Deposit · should revert if amount is zero
revertedWith("YV: zero amount")
18ms
Deposit · should emit DepositEvent with correct args
emit Deposit(user, 1000e6, 1000e18)
55ms
Deposit · should apply 0.1% protocol fee on deposit
fee = 1 USDC; shares = 999 yvUSDC
67ms
Withdraw · should burn shares and return USDC
changeTokenBalances correcto en ambas direcciones
92ms
Withdraw · should revert if user has insufficient shares
revertedWith("YV: insufficient shares")
22ms
Harvest · should auto-compound yield into vault
pricePerShare incrementa tras harvest(); event emitido
184ms
Harvest · should revert if called by non-keeper
revertedWith("YV: not keeper")
19ms
Pause · should block deposits when paused
owner pausa → deposit() reverts "YV: paused"
31ms
Time-lock · funds locked for 7 days after deposit
time.increase(86400 * 6) → withdraw fails; 7 días → ok
38ms
Gas · deposit uses <80K gas
receipt.gasUsed = 61,420 ✓
77ms
Gas · withdraw uses <65K gas
receipt.gasUsed = 48,230 ✓
69ms
Snapshot · state reverts between tests (evm_snapshot)
balance post-revert = 0; limpieza correcta
12ms
Access · only owner can set fee
Ownable: caller is not the owner
16ms
Access · keeper role can be granted by owner
grantRole / revokeRole funcionan correctamente
28ms
Whale impersonation · large USDC holder can deposit
hardhat_impersonateAccount 0xBebc44 · balance pre 10M USDC
341ms
Mainnet fork · integrates with Aave v3 supply()
Fork block 19,000,000 · aToken balance post-deposit correcto
1,204ms
Mainnet fork · pricePerShare increases after Aave accrual
time.increase(365 días) → PPS 1.048 USDC (4.8% APY simulado)
898ms
🪙 YVLToken.sol 14 passing
✓ 14 ✗ 0 1.87s
ERC-20 · totalSupply = 100M YVLT tras deploy
38ms
Transfer · changeTokenBalances correcto
44ms
Transfer · reverts on zero address recipient
17ms
Allowance · approve + transferFrom flujo completo
62ms
Allowance · reverts on exceed allowance
21ms
Mint · only minter role can mint new tokens
29ms
Mint · reverts if exceeds maxSupply (200M)
24ms
Burn · user can burn own tokens
31ms
Snapshot · createSnapshot + balanceOfAt correcto
58ms
Snapshot · balanceOfAt devuelve saldo histórico
45ms
Gas · transfer uses <60K gas
gasUsed = 51,340 ✓
53ms
Emit · Transfer event args correctos
37ms
Emit · Approval event args correctos
33ms
Permit · EIP-2612 sign off-chain y gasless transfer
188ms
🔒 Staking.sol 14 passing
✓ 14 ✗ 0 2.19s
Stake · stake(100 YVLT) actualiza balances y totals
71ms
Stake · reverts si YVLT balance insuficiente
19ms
Unstake · reverts dentro del lock period (7 días)
time.increase(86400 * 6) → "Staking: still locked"
28ms
Unstake · permite unstake tras 7 días exactos
time.increase(86400 * 7 + 1) → ok
35ms
Rewards · claimRewards devuelve USDC acumulado
6 meses simulados → 12 USDC (4% APR sobre 1000 YVLT)
412ms
Rewards · rewardPerToken incrementa correctamente
88ms
Rewards · no rewards si pool vacío
22ms
Multi-user · dos stakers comparten rewards proporcionalmente
198ms
Emergency · emergencyWithdraw permite salida sin lock
64ms
Emergency · pierde rewards pendientes al hacer emergencyWithdraw
71ms
Snapshot/revert · beforeEach/afterEach limpia estado
14ms
Gas · stake() uses <75K gas
gasUsed = 68,140 ✓
73ms
Gas · claimRewards() uses <55K gas
gasUsed = 47,820 ✓
69ms
Emit · Staked / Unstaked / RewardClaimed events correctos
51ms
Gas Report (hardhat-gas-reporter)
Methods · ETH price $3,480 · Coinmarketcap REPORT
YieldVault.sol
deposit(uint256)
YieldVault
Min
58,210
Avg
61,420
Max
63,900
Target
<80K ✓
withdraw(uint256)
YieldVault
Min
45,110
Avg
48,230
Max
51,800
Target
<65K ✓
harvest()
YieldVault
Min
88,450
Avg
94,200
Max
97,100
Target
YVLToken.sol
transfer(address,uint256)
YVLToken
Min
49,800
Avg
51,340
Max
52,100
Target
<60K ✓
mint(address,uint256)
YVLToken
Min
44,200
Avg
46,810
Max
48,500
Target
ok
Staking.sol
stake(uint256)
Staking
Min
65,300
Avg
68,140
Max
70,800
Target
<75K ✓
claimRewards()
Staking
Min
44,100
Avg
47,820
Max
49,200
Target
<55K ✓
Foundry Fuzzing (Forge · 10K runs)
Funciones con fuzz testing activo forge test --fuzz-runs 10000
testFuzzDeposit(uint256 amount)
10,000 runs · ✓ no counterexample
assume: amount ∈ [1, 10M USDC] seed: 0x1a2b3c PASS
testFuzzWithdraw(uint256 shares)
10,000 runs · ✓ no counterexample
assume: shares ≤ userBalance seed: 0xdeadbeef PASS
testFuzzTransfer(uint256 amount)
10,000 runs · ✓ no counterexample
assume: amount ≤ totalSupply seed: 0xc0ffee PASS
testFuzzStake(uint256 amount)
10,000 runs · ✓ no counterexample
assume: amount ∈ [1, totalStaked/2] seed: 0xfacade PASS
testFuzzRewardCalc(uint256 duration)
10,000 runs · ✓ no counterexample
assume: duration ∈ [1 día, 2 años] seed: 0xabcdef PASS
Invariant Tests forge test --mt invariant
invariant_totalSharesEqTotalAssets()
5,000 calls · 50 sequences
totalShares × pricePerShare = totalAssets ± dust PASS ✓
invariant_noNegativeBalances()
5,000 calls · 50 sequences
∀ user: balanceOf(user) ≥ 0 PASS ✓
Mainnet Fork Tests (Hardhat + Foundry)
Network
Ethereum Mainnet
Block number
19,000,000
USDC (mainnet)
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
Aave v3 Pool
0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2
Whale impersonated
0x0A59649758aa4d66E25f08Dd01271e891fe52199
RPC Provider
Alchemy (Ethereum)
  • YieldVault.deposit() suministra USDC a Aave v3 correctamente
  • aUSDC balance del vault post-deposit = amount depositado
  • harvest() retira yield de Aave y re-deposita en vault
  • pricePerShare crece 4.8% tras simular 365 días (time.increase)
  • Whale (10M USDC) puede depositar sin overflow ni errors
  • Múltiples usuarios pueden depositar/retirar en el mismo bloque
  • emergencyWithdraw retira todos los fondos de Aave correctamente
CI/CD Pipeline (GitHub Actions)
Última ejecución · main · commit a3f9d21 ✓ Passed · 4m 18s
1
Checkout + Setup Node 20
actions/checkout@v4 · setup-node@v4
12s
2
Instalar dependencias
npm ci
38s
3
Compilar contratos
npx hardhat compile
24s
4
Unit tests (Hardhat · Chai)
npx hardhat test
1m 32s
5
Cobertura de código
npx hardhat coverage
58s
6
Foundry fuzzing (10K runs)
forge test --fuzz-runs 10000
47s
7
Upload coverage a Codecov
codecov/codecov-action@v4 · badge actualizado
8s
8
Gas report artefact
actions/upload-artifact@v4 · gas-report.txt
5s
Snippet de referencia — test critico YieldVault
test/YieldVault.test.js — Mainnet fork + whale impersonation Hardhat · ethers v6
const { expect } = require("chai"); const { ethers, network } = require("hardhat"); const { loadFixture, time } = require("@nomicfoundation/hardhat-network-helpers"); describe("YieldVault — Mainnet Fork", function () { const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; const AAVE_V3 = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"; const WHALE = "0x0A59649758aa4d66E25f08Dd01271e891fe52199"; // 10M USDC async function forkFixture() { // Reset fork al bloque 19_000_000 await network.provider.request({ method: "hardhat_reset", params: [{ forking: { jsonRpcUrl: process.env.MAINNET_RPC_URL, blockNumber: 19_000_000 } }], }); // Impersonar whale con fondos reales await network.provider.request({ method: "hardhat_impersonateAccount", params: [WHALE] }); const whale = await ethers.getSigner(WHALE); await network.provider.send("hardhat_setBalance", [WHALE, "0x56BC75E2D63100000"]); const usdc = await ethers.getContractAt("IERC20", USDC); const aave = await ethers.getContractAt("IPool", AAVE_V3); const Vault = await ethers.getContractFactory("YieldVault"); const vault = await Vault.deploy(USDC, AAVE_V3); return { vault, usdc, aave, whale }; } it("pricePerShare grows after 1 year of Aave accrual", async function () { const { vault, usdc, whale } = await loadFixture(forkFixture); const amount = ethers.parseUnits("100000", 6); // 100K USDC await usdc.connect(whale).approve(vault.target, amount); await vault.connect(whale).deposit(amount); const ppsBefore = await vault.pricePerShare(); await time.increase(365 * 24 * 3600); // +1 año await vault.harvest(); // auto-compound Aave yield const ppsAfter = await vault.pricePerShare(); expect(ppsAfter).to.be.greaterThan(ppsBefore); // ~4.8% APY sobre depósito inicial expect(ppsAfter).to.be.closeTo(ethers.parseUnits("1.048", 18), ethers.parseUnits("0.005", 18)); }); });