Detailed description of the impact of this finding.
There are eight functions that are vulnerable to overflow and underflow.
The functions are deposit, redeem, lockCollateral, unlockLiquidity, subtractLoss, addProceeds, addRdpx and convertToShares.
I can reduce the contract balance to zero by deploying an integer overflow attack.
The vulnerability is in the functions because they are not using safe math.
The vulnerable functions use unsafe add and unsafe subtract.
The deposit function calls a Deposit function on overflow.
And the redeem function calls a Withdraw function on underflow.
The others call similar functions like transfer and others use ERC/IERC.
Proof of Concept
Provide direct links to all referenced code in GitHub.
Lines of code
https://github.com/code-423n4/2023-08-dopex/blob/b174dcd7b68a5372d7b9a97c9dd50895e742689c/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L132 https://github.com/code-423n4/2023-08-dopex/blob/b174dcd7b68a5372d7b9a97c9dd50895e742689c/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L164 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L181 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L192-L195 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L201-L204 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L210-L213 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L280-L281
Vulnerability details
Impact
Detailed description of the impact of this finding. There are eight functions that are vulnerable to overflow and underflow. The functions are deposit, redeem, lockCollateral, unlockLiquidity, subtractLoss, addProceeds, addRdpx and convertToShares. I can reduce the contract balance to zero by deploying an integer overflow attack. The vulnerability is in the functions because they are not using safe math. The vulnerable functions use unsafe add and unsafe subtract. The deposit function calls a Deposit function on overflow. And the redeem function calls a Withdraw function on underflow. The others call similar functions like transfer and others use ERC/IERC.
Proof of Concept
Provide direct links to all referenced code in GitHub.
Add screenshots, logs, or any other relevant proof that illustrates the concept.
Exploit Foundry
Test Case Foundry
Log Foundry
Running 1 test for tests/perp-vault/Integration.t.sol:Integration [PASS] testAttack0() (gas: 617572) Logs: Balance Before 5000000000000000000
Traces: [617572] Integration::testAttack0() ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000001) │ └─ ← () ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000001, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000001, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000001, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000001, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [0] VM::stopPrank() │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000002) │ └─ ← () ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000002, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000002, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000002, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000002, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [0] VM::stopPrank() │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000003, 0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000003, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000003, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000003, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000003, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← true ├─ [0] VM::stopPrank() │ └─ ← () ├─ [34093] MockToken::mint(0x0000000000000000000000000000000000000001, 5000000000000000000 [5e18]) │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000001, amount: 5000000000000000000 [5e18]) │ └─ ← () ├─ [25293] MockToken::mint(0x0000000000000000000000000000000000000002, 20000000000000000000 [2e19]) │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000002, amount: 20000000000000000000 [2e19]) │ └─ ← () ├─ [25293] MockToken::mint(0x0000000000000000000000000000000000000003, 25000000000000000000 [2.5e19]) │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000003, amount: 25000000000000000000 [2.5e19]) │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000001) │ └─ ← () ├─ [143192] PerpetualAtlanticVaultLP::deposit(5000000000000000000 [5e18], 0x0000000000000000000000000000000000000001) │ ├─ [7644] PerpetualAtlanticVault::getUnderlyingPrice() [staticcall] │ │ ├─ [2324] MockRdpxEthPriceOracle::getRdpxPriceInEth() [staticcall] │ │ │ └─ ← 20000000 [2e7] │ │ └─ ← 20000000 [2e7] │ ├─ [51946] PerpetualAtlanticVault::updateFunding() │ │ ├─ [7315] MockToken::transfer(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 0) │ │ │ ├─ emit Transfer(from: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0) │ │ │ └─ ← true │ │ ├─ [4031] PerpetualAtlanticVaultLP::addProceeds(0) │ │ │ ├─ [583] MockToken::balanceOf(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c]) [staticcall] │ │ │ │ └─ ← 0 │ │ │ └─ ← () │ │ ├─ emit FundingPaid(sender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0, latestFundingPaymentPointer: 0) │ │ └─ ← () │ ├─ [18961] MockToken::transferFrom(0x0000000000000000000000000000000000000001, PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 5000000000000000000 [5e18]) │ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000001, to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 5000000000000000000 [5e18]) │ │ └─ ← true │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000001, amount: 5000000000000000000 [5e18]) │ ├─ emit Deposit(caller: 0x0000000000000000000000000000000000000001, owner: 0x0000000000000000000000000000000000000001, assets: 5000000000000000000 [5e18], shares: 5000000000000000000 [5e18]) │ └─ ← 0x0000000000000000000000000000000000000000000000004563918244f40000 ├─ [0] VM::stopPrank() │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000002) │ └─ ← () ├─ [36826] PerpetualAtlanticVaultLP::deposit(20000000000000000000 [2e19], 0x0000000000000000000000000000000000000002) │ ├─ [1144] PerpetualAtlanticVault::getUnderlyingPrice() [staticcall] │ │ ├─ [324] MockRdpxEthPriceOracle::getRdpxPriceInEth() [staticcall] │ │ │ └─ ← 20000000 [2e7] │ │ └─ ← 20000000 [2e7] │ ├─ [11472] PerpetualAtlanticVault::updateFunding() │ │ ├─ [3315] MockToken::transfer(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 0) │ │ │ ├─ emit Transfer(from: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0) │ │ │ └─ ← true │ │ ├─ [2031] PerpetualAtlanticVaultLP::addProceeds(0) │ │ │ ├─ [583] MockToken::balanceOf(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c]) [staticcall] │ │ │ │ └─ ← 5000000000000000000 [5e18] │ │ │ └─ ← () │ │ ├─ emit FundingPaid(sender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0, latestFundingPaymentPointer: 0) │ │ └─ ← () │ ├─ [3041] MockToken::transferFrom(0x0000000000000000000000000000000000000002, PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 20000000000000000000 [2e19]) │ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000002, to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 20000000000000000000 [2e19]) │ │ └─ ← true │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000002, amount: 20000000000000000000 [2e19]) │ ├─ emit Deposit(caller: 0x0000000000000000000000000000000000000002, owner: 0x0000000000000000000000000000000000000002, assets: 20000000000000000000 [2e19], shares: 20000000000000000000 [2e19]) │ └─ ← 0x000000000000000000000000000000000000000000000001158e460913d00000 ├─ [0] VM::stopPrank() │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000003, 0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [36826] PerpetualAtlanticVaultLP::deposit(25000000000000000000 [2.5e19], 0x0000000000000000000000000000000000000003) │ ├─ [1144] PerpetualAtlanticVault::getUnderlyingPrice() [staticcall] │ │ ├─ [324] MockRdpxEthPriceOracle::getRdpxPriceInEth() [staticcall] │ │ │ └─ ← 20000000 [2e7] │ │ └─ ← 20000000 [2e7] │ ├─ [11472] PerpetualAtlanticVault::updateFunding() │ │ ├─ [3315] MockToken::transfer(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 0) │ │ │ ├─ emit Transfer(from: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0) │ │ │ └─ ← true │ │ ├─ [2031] PerpetualAtlanticVaultLP::addProceeds(0) │ │ │ ├─ [583] MockToken::balanceOf(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c]) [staticcall] │ │ │ │ └─ ← 25000000000000000000 [2.5e19] │ │ │ └─ ← () │ │ ├─ emit FundingPaid(sender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0, latestFundingPaymentPointer: 0) │ │ └─ ← () │ ├─ [3041] MockToken::transferFrom(0x0000000000000000000000000000000000000003, PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 25000000000000000000 [2.5e19]) │ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000003, to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 25000000000000000000 [2.5e19]) │ │ └─ ← true │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000003, amount: 25000000000000000000 [2.5e19]) │ ├─ emit Deposit(caller: 0x0000000000000000000000000000000000000003, owner: 0x0000000000000000000000000000000000000003, assets: 25000000000000000000 [2.5e19], shares: 25000000000000000000 [2.5e19]) │ └─ ← 0x0000000000000000000000000000000000000000000000015af1d78b58c40000 ├─ [0] VM::stopPrank() │ └─ ← () ├─ [564] PerpetualAtlanticVaultLP::balanceOf(0x0000000000000000000000000000000000000001) [staticcall] │ └─ ← 5000000000000000000 [5e18] ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [564] PerpetualAtlanticVaultLP::balanceOf(0x0000000000000000000000000000000000000001) [staticcall] │ └─ ← 5000000000000000000 [5e18] ├─ [0] console::9710a9d0(00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000004563918244f40000000000000000000000000000000000000000000000000000000000000000000f42616c616e6365204265666f7265200000000000000000000000000000000000) [staticcall] │ └─ ← () ├─ [0] VM::expectRevert(Arithmetic over/underflow) │ └─ ← () └─ ← "Arithmetic over/underflow"
Test result: ok. 1 passed; 0 failed; finished in 831.75ms