Skip to content

Commit

Permalink
馃悰 debt-manager: verify flashloan call origin
Browse files Browse the repository at this point in the history
  • Loading branch information
santichez committed Jun 6, 2023
1 parent 1d33235 commit 9803f19
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/shy-pumpkins-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/protocol": patch
---

馃悰 debt-manager: verify flashloan call origin
79 changes: 40 additions & 39 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,46 @@ AuditorTest:testEnableMarketShouldRevertWithInvalidPriceFeed() (gas: 149281)
AuditorTest:testEnterExitMarket() (gas: 178761)
AuditorTest:testExitMarketOwning() (gas: 177445)
DebtManagerTest:testApproveMaliciousMarket() (gas: 29119)
DebtManagerTest:testApproveMarket() (gas: 61565)
DebtManagerTest:testAvailableLiquidity() (gas: 100388)
DebtManagerTest:testCallReceiveFlashLoanFromAnyAddress() (gas: 30801)
DebtManagerTest:testDeleverage() (gas: 458464)
DebtManagerTest:testDeleverageHalfBorrowPosition() (gas: 497565)
DebtManagerTest:testFixedDeleverage() (gas: 436705)
DebtManagerTest:testFixedRoll() (gas: 522930)
DebtManagerTest:testFixedRollSameMaturityWithThreeLoops() (gas: 400425)
DebtManagerTest:testFixedRollWithAccurateBorrowSlippage() (gas: 770617)
DebtManagerTest:testFixedRollWithAccurateBorrowSlippageWithThreeLoops() (gas: 1104009)
DebtManagerTest:testFixedRollWithAccurateRepaySlippage() (gas: 770577)
DebtManagerTest:testFixedRollWithAccurateRepaySlippageWithThreeLoops() (gas: 1098769)
DebtManagerTest:testFixedToFloatingRoll() (gas: 472215)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidity() (gas: 539926)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippage() (gas: 807412)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippageWithThreeLoops() (gas: 978700)
DebtManagerTest:testFixedToFloatingRollWithAccurateSlippage() (gas: 655321)
DebtManagerTest:testFlashloanFeeGreaterThanZero() (gas: 407080)
DebtManagerTest:testFloatingToFixedRoll() (gas: 513386)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidity() (gas: 598860)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippage() (gas: 973985)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippageWithThreePools() (gas: 1182089)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippage() (gas: 781194)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippageWithPreviousPosition() (gas: 732092)
DebtManagerTest:testLateFixedDeleverage() (gas: 478073)
DebtManagerTest:testLateFixedRoll() (gas: 533609)
DebtManagerTest:testLateFixedRollWithThreeLoops() (gas: 716843)
DebtManagerTest:testLateFixedToFloatingRoll() (gas: 481452)
DebtManagerTest:testLateFixedToFloatingRollWithThreeLoops() (gas: 646045)
DebtManagerTest:testLeverage() (gas: 372783)
DebtManagerTest:testLeverageShouldFailWhenHealthFactorNearOne() (gas: 726587)
DebtManagerTest:testLeverageWithAlreadyDepositedAmount() (gas: 401179)
DebtManagerTest:testLeverageWithInvalidBalancerVault() (gas: 2749364)
DebtManagerTest:testMockBalancerVault() (gas: 4099919)
DebtManagerTest:testPartialFixedDeleverage() (gas: 523032)
DebtManagerTest:testPartialFixedRoll() (gas: 586251)
DebtManagerTest:testPartialFixedToFloatingRoll() (gas: 550330)
DebtManagerTest:testPartialLateFixedRoll() (gas: 578630)
DebtManagerTest:testPartialLateFixedToFloatingRoll() (gas: 549072)
DebtManagerTest:testApproveMarket() (gas: 61610)
DebtManagerTest:testAvailableLiquidity() (gas: 100410)
DebtManagerTest:testBalancerFlashloanCallFromDifferentOrigin() (gas: 65654)
DebtManagerTest:testCallReceiveFlashLoanFromAnyAddress() (gas: 33485)
DebtManagerTest:testDeleverage() (gas: 461852)
DebtManagerTest:testDeleverageHalfBorrowPosition() (gas: 500975)
DebtManagerTest:testFixedDeleverage() (gas: 451803)
DebtManagerTest:testFixedRoll() (gas: 525733)
DebtManagerTest:testFixedRollSameMaturityWithThreeLoops() (gas: 400447)
DebtManagerTest:testFixedRollWithAccurateBorrowSlippage() (gas: 796123)
DebtManagerTest:testFixedRollWithAccurateBorrowSlippageWithThreeLoops() (gas: 1130235)
DebtManagerTest:testFixedRollWithAccurateRepaySlippage() (gas: 796083)
DebtManagerTest:testFixedRollWithAccurateRepaySlippageWithThreeLoops() (gas: 1124995)
DebtManagerTest:testFixedToFloatingRoll() (gas: 480252)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidity() (gas: 542869)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippage() (gas: 833198)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippageWithThreeLoops() (gas: 1004798)
DebtManagerTest:testFixedToFloatingRollWithAccurateSlippage() (gas: 680795)
DebtManagerTest:testFlashloanFeeGreaterThanZero() (gas: 429735)
DebtManagerTest:testFloatingToFixedRoll() (gas: 516165)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidity() (gas: 601795)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippage() (gas: 999755)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippageWithThreePools() (gas: 1208171)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippage() (gas: 806652)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippageWithPreviousPosition() (gas: 757550)
DebtManagerTest:testLateFixedDeleverage() (gas: 484949)
DebtManagerTest:testLateFixedRoll() (gas: 536412)
DebtManagerTest:testLateFixedRollWithThreeLoops() (gas: 720006)
DebtManagerTest:testLateFixedToFloatingRoll() (gas: 487641)
DebtManagerTest:testLateFixedToFloatingRollWithThreeLoops() (gas: 649144)
DebtManagerTest:testLeverage() (gas: 375538)
DebtManagerTest:testLeverageShouldFailWhenHealthFactorNearOne() (gas: 751995)
DebtManagerTest:testLeverageWithAlreadyDepositedAmount() (gas: 403934)
DebtManagerTest:testLeverageWithInvalidBalancerVault() (gas: 2751564)
DebtManagerTest:testMockBalancerVault() (gas: 4104898)
DebtManagerTest:testPartialFixedDeleverage() (gas: 525811)
DebtManagerTest:testPartialFixedRoll() (gas: 589054)
DebtManagerTest:testPartialFixedToFloatingRoll() (gas: 553117)
DebtManagerTest:testPartialLateFixedRoll() (gas: 581433)
DebtManagerTest:testPartialLateFixedToFloatingRoll() (gas: 551859)
InterestRateModelTest:testFixedBorrowRate() (gas: 8089)
InterestRateModelTest:testFloatingBorrowRate() (gas: 6236)
InterestRateModelTest:testMinFixedRate() (gas: 7610)
Expand Down
37 changes: 27 additions & 10 deletions contracts/periphery/DebtManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ contract DebtManager is Initializable {
calls[0] = abi.encodeCall(ERC4626.deposit, (amounts[0] + (deposit ? principal : 0), msg.sender));
calls[1] = abi.encodeCall(Market.borrow, (amounts[0], address(balancerVault), msg.sender));

balancerVault.flashLoan(address(this), tokens, amounts, abi.encode(market, calls));
balancerVault.flashLoan(address(this), tokens, amounts, call(abi.encode(market, calls)));
}

/// @notice Deleverages the position of `msg.sender` a certain `percentage` by taking a flash loan from
Expand All @@ -88,7 +88,7 @@ contract DebtManager is Initializable {
}
calls[1] = abi.encodeCall(Market.withdraw, (amounts[0], address(balancerVault), msg.sender));

balancerVault.flashLoan(address(this), tokens, amounts, abi.encode(market, calls));
balancerVault.flashLoan(address(this), tokens, amounts, call(abi.encode(market, calls)));
}

/// @notice Rolls a percentage of the floating position of `msg.sender` to a fixed position.
Expand Down Expand Up @@ -137,7 +137,7 @@ contract DebtManager is Initializable {
}
}

balancerVault.flashLoan(address(this), tokens, amounts, abi.encode(market, calls));
balancerVault.flashLoan(address(this), tokens, amounts, call(abi.encode(market, calls)));
(uint256 newPrincipal, uint256 newFee) = market.fixedBorrowPositions(borrowMaturity, msg.sender);
if (maxBorrowAssets < newPrincipal + newFee - r.principal - r.fee) revert Disagreement();
}
Expand All @@ -159,8 +159,10 @@ contract DebtManager is Initializable {
RollVars memory r;
tokens[0] = market.asset();

(, , uint256 floatingBorrowShares) = market.accounts(msg.sender);
r.principal = market.previewRefund(floatingBorrowShares);
{
(, , uint256 floatingBorrowShares) = market.accounts(msg.sender);
r.principal = market.previewRefund(floatingBorrowShares);
}
(uint256 repayAssets, uint256 positionAssets) = repayAtMaturityAssets(market, repayMaturity, percentage);
r.loopCount = repayAssets.mulDivUp(1, tokens[0].balanceOf(address(balancerVault)));
positionAssets = positionAssets / r.loopCount;
Expand All @@ -180,9 +182,11 @@ contract DebtManager is Initializable {
++r.i;
}
}
balancerVault.flashLoan(address(this), tokens, amounts, abi.encode(market, calls));
(, , floatingBorrowShares) = market.accounts(msg.sender);
if (maxRepayAssets < market.previewRefund(floatingBorrowShares) - r.principal) revert Disagreement();
balancerVault.flashLoan(address(this), tokens, amounts, call(abi.encode(market, calls)));
{
(, , uint256 floatingBorrowShares) = market.accounts(msg.sender);
if (maxRepayAssets < market.previewRefund(floatingBorrowShares) - r.principal) revert Disagreement();
}
}

/// @notice Rolls a percentage of the fixed position of `msg.sender` to another fixed pool.
Expand Down Expand Up @@ -235,7 +239,7 @@ contract DebtManager is Initializable {
}
}

balancerVault.flashLoan(address(this), tokens, amounts, abi.encode(market, calls));
balancerVault.flashLoan(address(this), tokens, amounts, call(abi.encode(market, calls)));
(uint256 newPrincipal, uint256 newFee) = market.fixedBorrowPositions(borrowMaturity, msg.sender);
if (
newPrincipal + newFee >
Expand Down Expand Up @@ -284,11 +288,24 @@ contract DebtManager is Initializable {
}
}

/// @notice Hash of the call data that will be used to verify that the flash loan is originated from `this`.
bytes32 private callHash;

/// @notice Hashes the data and stores its value in `callHash`.
/// @param data The calldata to be hashed.
/// @return Same calldata that was passed as an argument.
function call(bytes memory data) internal returns (bytes memory) {
callHash = keccak256(data);
return data;
}

/// @notice Callback function called by the Balancer Vault contract when a flash loan is initiated.
/// @dev Only the Balancer Vault contract is allowed to call this function.
/// @param userData Additional data provided by the borrower for the flash loan.
function receiveFlashLoan(ERC20[] memory, uint256[] memory, uint256[] memory, bytes memory userData) external {
assert(msg.sender == address(balancerVault));
bytes32 memCallHash = callHash;
assert(msg.sender == address(balancerVault) && memCallHash != bytes32(0) && memCallHash == keccak256(userData));
callHash = bytes32(0);

(Market market, bytes[] memory calls) = abi.decode(userData, (Market, bytes[]));
for (uint256 i = 0; i < calls.length; ) {
Expand Down
21 changes: 20 additions & 1 deletion test/solidity/DebtManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ contract DebtManagerTest is Test {
uint256[] memory feeAmounts = new uint256[](1);
ERC20[] memory assets = new ERC20[](1);

vm.expectRevert();
vm.expectRevert(stdError.assertionError);
debtManager.receiveFlashLoan(assets, amounts, feeAmounts, "");
}

Expand Down Expand Up @@ -611,6 +611,25 @@ contract DebtManagerTest is Test {
assertEq(availableAssets[1].liquidity, usdc.balanceOf(address(debtManager.balancerVault())));
}

function testBalancerFlashloanCallFromDifferentOrigin() external {
ERC20[] memory tokens = new ERC20[](1);
tokens[0] = usdc;
uint256[] memory amounts = new uint256[](1);
amounts[0] = 0;
bytes memory maliciousCall = abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
address(this),
address(1),
usdc.balanceOf(address(this))
);
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodePacked(maliciousCall);
IBalancerVault balancerVault = debtManager.balancerVault();

vm.expectRevert(stdError.assertionError);
balancerVault.flashLoan(address(debtManager), tokens, amounts, abi.encode(usdc, calls));
}

function testMockBalancerVault() external {
MockBalancerVault mockBalancerVault = new MockBalancerVault();
debtManager = DebtManager(
Expand Down

0 comments on commit 9803f19

Please sign in to comment.