Closed code423n4 closed 10 months ago
bytes032 marked the issue as primary issue
bytes032 marked the issue as sufficient quality report
Related to #1921
psytama (sponsor) disputed
This is a design choice and the code works as intended.
POC from the Warden:
The following POC provides the details to shows the validity of the issue and the code is not working correctly as mentioned above.
First, to show premium paid, add the following console.log to RdpxV2Core.sol#L922
console.log("premium paid = %d", premium);
For the POC, add and run the following code in tests\rdpxV2-core\Integration.t.sol
function testPeakboltIncorrectOptionPurchase() public {
console.log("------------- bonding with decaying bond (1 dpxETH)-------------");
// test with rdpx decaying bonds
rdpxDecayingBonds.grantRole(rdpxDecayingBonds.MINTER_ROLE(), address(this));
rdpxDecayingBonds.mint(address(this), block.timestamp + 10, 125 * 1e16);
assertEq(rdpxDecayingBonds.ownerOf(1), address(this));
rdpxDecayingBonds.approve(address(rdpxV2Core), 1);
(uint256 rdpxRequiredToBond, ) = rdpxV2Core.calculateBondCost(1 * 1e18, 1);
console.log("rdpxRequiredToBond: %d", rdpxRequiredToBond);
// bond using decaying rdpxBondId 1
uint256 wethBalanceBefore = weth.balanceOf(address(this));
(, uint256 rdpxReserveBefore, ) = rdpxV2Core.getReserveTokenInfo("RDPX");
rdpxV2Core.bond(1 * 1e18, 1, address(this));
uint256 wethBalanceAfter = weth.balanceOf(address(this));
uint256 wethPaidForDecayingBonding = wethBalanceBefore - wethBalanceAfter;
(, uint256 rdpxReserveAfter, ) = rdpxV2Core.getReserveTokenInfo("RDPX");
uint256 rdpxReserveChange = rdpxReserveAfter - rdpxReserveBefore;
console.log("rDPX reserve increase: %d", rdpxReserveChange);
uint256 optionsIndex = vault.balanceOf(address(rdpxV2Core)) - 1;
(uint256 strike, uint256 optionsAmount, ) = vault.optionPositions(optionsIndex);
console.log("option purchased for rdpxRequired: %d", optionsAmount);
// this will pass as amount of option purchase is equal to rdpx amount contributed to reserve
assertEq(optionsAmount, rdpxReserveChange);
console.log("------------- regular bonding (1 dpxETH) -------------");
(rdpxRequiredToBond, ) = rdpxV2Core.calculateBondCost(1 * 1e18, 0);
console.log("rdpxRequiredToBond: %d ", rdpxRequiredToBond);
// regular bonding
wethBalanceBefore = weth.balanceOf(address(this));
(, rdpxReserveBefore, ) = rdpxV2Core.getReserveTokenInfo("RDPX");
rdpxV2Core.bond(1 * 1e18, 0, address(this));
wethBalanceAfter = weth.balanceOf(address(this));
uint256 wethPaidForRegularBonding = wethBalanceBefore - wethBalanceAfter;
(, rdpxReserveAfter, ) = rdpxV2Core.getReserveTokenInfo("RDPX");
rdpxReserveChange = rdpxReserveAfter - rdpxReserveBefore;
console.log("rDPX reserve increase: %d", rdpxReserveChange);
optionsIndex = vault.balanceOf(address(rdpxV2Core)) - 1;
(strike, optionsAmount, ) = vault.optionPositions(optionsIndex);
console.log("option purchased for rdpxRequired: %d", optionsAmount);
// this will fail as amount of option purchased < rdpx amount contributed to reserve (incorrect)
// that means rdpxV2Core has a smaller than required hedge against rDPX price drop, which is wrong
assertEq(optionsAmount, rdpxReserveChange);
// this will fail as users performing regular bonding is paying less weth than users bonding with decaying bond
// this is due to a lower premium (incorrect) paid for regular bonding, because of incorrect option purchased
assertEq(wethPaidForDecayingBonding, wethPaidForRegularBonding);
}
GalloDaSballo changed the severity to 2 (Med Risk)
I've adjusted the POC to revert after the first operations as to not change the balance of reserves which has been demonstrated to change pricing
The output
------------- bonding with decaying bond (1 dpxETH)-------------
rdpxRequiredToBond: 1250000000000000000
rDPX reserve increase: 1250000000000000000
option purchased for rdpxRequired: 1250000000000000000
------------- regular bonding (1 dpxETH) -------------
rdpxRequiredToBond: 1225000000000000000
rDPX reserve increase: 1275000000000000000
option purchased for rdpxRequired: 1225000000000000000
Error: a == b not satisfied [uint]
Left: 1225000000000000000
Right: 1275000000000000000
Error: a == b not satisfied [uint]
Left: 812500000000000000
Right: 806250000000000000
Unless I'm missing something, the Warden has shown how, by purchasing effectively the same thing, we can get two difference prices
GalloDaSballo marked the issue as satisfactory
GalloDaSballo marked the issue as selected for report
GalloDaSballo marked issue #780 as primary and marked this issue as a duplicate of 780
Lines of code
https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L904-L924
Vulnerability details
During rDPX bonding,
rdpxV2Core
will purchase put options for the rDPX that are used for minting DPXETH. This is for hedging against rDPX price drop to protect the DPXETH peg. The amount of put options to purchase is equivalent to the amount of rDPX that are used for minting DPXETH, which isrdxRequired
as given bycalculateBondCost()
.The issue is that for regular bonding,
rdxRequired
is reduced by the discount provided by Treasury Reserve and the amount of rDPX used for minting is actuallyrdxRequired
+ rDPX discount given by Treasury Reserve. We can see the_transfer()
code below actually increment the rDPX Backing Reserve by_rdpxAmount + extraRdpxToWithdraw
, which is the actual amount of rDPX use for minting DPXETH.So users that perform regular/delegate bonding will pay less premium due to the reduced amount of put options. With a reduced amount of put options, the hedging in place to protect DPXETH peg will be less effective.
Impact
Users who performs bonding via rDPX Decaying Bond will be shortchanged as they are paying the full premium while users who perform regular/delegate bonding will pay less premium than required.
Also, with a reduced amount of put options, it will reduce effectiveness of the hedging against rDPX price drop, thereby leading to higher likelihood of DPX ETH depeg.
Proof of Concept
https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L904-L924
https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L1162-L1178
Recommended Mitigation Steps
Update
_purchaseOptions(rdpxRequired)
to include the discount provided by Treasury Reserve.Assessed type
Other