Skip to content

Commit

Permalink
Add BoostManager slippage protection and create Permit2Manager (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
haydenshively authored Oct 28, 2023
1 parent 29e9641 commit 8a608f0
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 3 deletions.
8 changes: 7 additions & 1 deletion periphery/src/managers/BoostManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ contract BoostManager is IManager, IUniswapV3SwapCallback {
uint128 liquidity;
// Leverage factor
uint24 boost;
(tokenId, lower, upper, liquidity, boost) = abi.decode(args, (uint256, int24, int24, uint128, uint24));
// Packed maxBorrow0 and maxBorrow1; slippage protection
uint224 maxBorrows;
(tokenId, lower, upper, liquidity, boost, maxBorrows) = abi.decode(
args,
(uint256, int24, int24, uint128, uint24, uint224)
);

require(owner == UNISWAP_NFT.ownerOf(tokenId), "Aloe: owners must match to import");

Expand All @@ -96,6 +101,7 @@ contract BoostManager is IManager, IUniswapV3SwapCallback {
amount1 = (needs1 + 1) > amount1 ? (needs1 + 1 - amount1) : 0;
}

require(amount0 < uint112(maxBorrows) && amount1 < (maxBorrows >> 112), "slippage");
borrower.borrow(amount0, amount1, msg.sender);
borrower.uniswapDeposit(lower, upper, liquidity);
}
Expand Down
73 changes: 73 additions & 0 deletions periphery/src/managers/Permit2Manager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {IManager} from "aloe-ii-core/Borrower.sol";
import {Factory} from "aloe-ii-core/Factory.sol";

import {IPermit2} from "../interfaces/IPermit2.sol";

/// @dev Permit2 signatures attest to {token, amount, spender, nonce, deadline}. Noticeable lacking is a `to`
/// field. So given a signature, this contract would have permission to send the user's tokens *anywhere*. In
/// this case the `to` address is part of the incoming calldata, so we must verify that the user is the creator
/// of that calldata (or has authorized it somehow).
/// Within `callback`, if `FACTORY.isBorrower(msg.sender) && owner == BORROWER_NFT`, then we can be sure
/// that either the user themselves OR someone who's been approved to manage their NFT(s) is the source
/// of the calldata.
contract Permit2Manager is IManager {
error Permit2CallFailed();

error BorrowerCallFailed();

IPermit2 public immutable PERMIT2;

Factory public immutable FACTORY;

address public immutable BORROWER_NFT;

constructor(IPermit2 permit2, Factory factory, address borrowerNft) {
PERMIT2 = permit2;
FACTORY = factory;
BORROWER_NFT = borrowerNft;
}

function callback(bytes calldata data, address owner, uint208) external override returns (uint208) {
// Need to check that `msg.sender` is really a borrower and that its owner is `BORROWER_NFT`
// in order to be sure that incoming `data` is in the expected format
require(FACTORY.isBorrower(msg.sender) && owner == BORROWER_NFT, "Aloe: bad caller");

// `data` layout...
// -------------------------------------------
// | value | start | end | length |
// | owner | 0 | 20 | 20 |
// | permit2 selector | 20 | 24 | 4 |
// | permit2 args | 24 | 248 | 224 |
// | permit2 sig | 248 | 313 | 65 |
// | borrower call | 313 | ? | ? |
// -------------------------------------------

// Get references to the calldata for the 2 calls we're going to make
bytes calldata dataPermit2 = data[20:313];
bytes calldata dataBorrower = data[313:];

// Before calling `PERMIT2`, verify
// (a) correct function selector
// (b) `to` field is the Borrower (`msg.sender`)
// (c) correct signer address, i.e. [claimed Permit2 signer] == [user who owns the Borrower]
// Note that data[:20] is the true owner prepended by the `BORROWER_NFT`
require(
bytes4(dataPermit2[:4]) == IPermit2.permitTransferFrom.selector &&
bytes20(dataPermit2[144:164]) == bytes20(msg.sender) &&
bytes20(dataPermit2[208:228]) == bytes20(data[:20])
);

// Make calls
bool success;
(success, ) = address(PERMIT2).call(dataPermit2); // solhint-disable-line avoid-low-level-calls
if (!success) revert Permit2CallFailed();

(success, ) = msg.sender.call(dataBorrower); // solhint-disable-line avoid-low-level-calls
if (!success) revert BorrowerCallFailed();

return 0;
}
}
11 changes: 9 additions & 2 deletions periphery/test/managers/BoostManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ contract BoostManagerTest is Test {
lender1.deposit(1e18, address(0));
}

function test_mint() public {
function test_mint() public {
address owner = 0xde8E7d3fFada10dE2A57E7bAc090dB06596F51Cd;

bytes memory mintCall;
Expand All @@ -67,7 +67,14 @@ contract BoostManagerTest is Test {
managers[0] = boostManager;
datas[0] = abi.encode(
uint8(0),
abi.encode(uint256(425835), int24(70020), int24(71700), uint128(344339104909795631), 10_000)
abi.encode(
uint256(425835),
int24(70020),
int24(71700),
uint128(344339104909795631),
10_000,
uint224(type(uint224).max)
)
);
antes[0] = 0.01 ether / 1e13;
modifyCall = abi.encodeCall(borrowerNft.modify, (owner, indices, managers, datas, antes));
Expand Down

0 comments on commit 8a608f0

Please sign in to comment.