-
Notifications
You must be signed in to change notification settings - Fork 36
feat : new position managers #1054
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 1 commit
a00ec35
ec2170c
7ae1fdb
0ffdb57
abcf269
a42b632
a1e33c0
aee822d
f33c26d
ffa90f0
480e518
8dbe9c0
68cb0d4
6f718ba
bc4a75c
bf6476f
883990b
19443f7
1063e3f
2e28199
f0477f1
c922404
21bce1d
85105ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // Copyright (c) 2025 Aave Labs | ||
| pragma solidity 0.8.28; | ||
|
|
||
| import {SignatureChecker} from 'src/dependencies/openzeppelin/SignatureChecker.sol'; | ||
| import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol'; | ||
| import {EIP712} from 'src/dependencies/solady/EIP712.sol'; | ||
| import {NoncesKeyed} from 'src/utils/NoncesKeyed.sol'; | ||
| import {EIP712Hash, EIP712Types} from 'src/position-manager/libraries/EIP712Hash.sol'; | ||
| import {PositionManagerBase} from 'src/position-manager/PositionManagerBase.sol'; | ||
| import {ICreditDelegationPositionManager} from 'src/position-manager/interfaces/ICreditDelegationPositionManager.sol'; | ||
|
|
||
| /// @title CreditDelegationPositionManager | ||
| /// @author Aave Labs | ||
| /// @notice Position manager to handle credit delegation and borrow actions on behalf of users. | ||
| contract CreditDelegationPositionManager is | ||
| ICreditDelegationPositionManager, | ||
| PositionManagerBase, | ||
| NoncesKeyed, | ||
| EIP712 | ||
| { | ||
| using SafeERC20 for IERC20; | ||
| using EIP712Hash for *; | ||
|
|
||
| /// @notice Mapping of credit delegations: owner => spender => reserveId => amount. | ||
| mapping(address => mapping(address => mapping(uint256 => uint256))) private _creditDelegations; | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// @dev Constructor. | ||
| /// @param spoke_ The address of the spoke contract. | ||
| constructor(address spoke_) PositionManagerBase(spoke_) {} | ||
|
|
||
| /// @inheritdoc ICreditDelegationPositionManager | ||
| function approveCreditDelegation(address spender, uint256 reserveId, uint256 amount) external { | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| _creditDelegations[msg.sender][spender][reserveId] = amount; | ||
| emit CreditDelegation(msg.sender, spender, reserveId, amount); | ||
| } | ||
|
|
||
| /// @inheritdoc ICreditDelegationPositionManager | ||
| function approveCreditDelegationWithSig( | ||
|
||
| EIP712Types.CreditDelegation calldata params, | ||
| bytes calldata signature | ||
| ) external { | ||
| require(block.timestamp <= params.deadline, InvalidSignature()); | ||
| address user = params.owner; | ||
| bytes32 digest = _hashTypedData(params.hash()); | ||
| require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature()); | ||
| _useCheckedNonce(user, params.nonce); | ||
|
|
||
| _creditDelegations[user][params.spender][params.reserveId] = params.amount; | ||
| emit CreditDelegation(user, params.spender, params.reserveId, params.amount); | ||
| } | ||
|
|
||
| /// @inheritdoc ICreditDelegationPositionManager | ||
| function borrowOnBehalfOf( | ||
| uint256 reserveId, | ||
| uint256 amount, | ||
| address onBehalfOf | ||
| ) external returns (uint256, uint256) { | ||
| uint256 currentAllowance = _creditDelegations[onBehalfOf][msg.sender][reserveId]; | ||
| if (currentAllowance < amount) { | ||
| revert InsufficientCreditDelegation(); | ||
| } | ||
| _creditDelegations[onBehalfOf][msg.sender][reserveId] -= amount; | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| IERC20 asset = _getReserveUnderlying(reserveId); | ||
| (uint256 borrowedShares, uint256 borrowedAmount) = SPOKE.borrow(reserveId, amount, onBehalfOf); | ||
| asset.safeTransfer(msg.sender, borrowedAmount); | ||
|
|
||
| return (borrowedShares, borrowedAmount); | ||
| } | ||
|
|
||
| /// @inheritdoc ICreditDelegationPositionManager | ||
| function creditDelegationAllowance( | ||
| address owner, | ||
| address spender, | ||
| uint256 reserveId | ||
| ) external view returns (uint256) { | ||
| return _creditDelegations[owner][spender][reserveId]; | ||
| } | ||
|
|
||
| /// @inheritdoc ICreditDelegationPositionManager | ||
| function CREDIT_DELEGATION_TYPEHASH() external pure returns (bytes32) { | ||
| return EIP712Hash.CREDIT_DELEGATION_TYPEHASH; | ||
| } | ||
|
|
||
| function _domainNameAndVersion() internal pure override returns (string memory, string memory) { | ||
| return ('CreditDelegationPositionManager', '1'); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // Copyright (c) 2025 Aave Labs | ||
| pragma solidity 0.8.28; | ||
|
|
||
| import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol'; | ||
| import {IERC20Permit} from 'src/dependencies/openzeppelin/IERC20Permit.sol'; | ||
| import {Multicall} from 'src/utils/Multicall.sol'; | ||
| import {EIP712Types} from 'src/libraries/types/EIP712Types.sol'; | ||
| import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol'; | ||
| import {IPositionManagerBase} from 'src/position-manager/interfaces/IPositionManagerBase.sol'; | ||
|
|
||
| /// @title PositionManagerBase | ||
| /// @author Aave Labs | ||
| /// @notice Base implementation for position manager common functionalities. | ||
| abstract contract PositionManagerBase is IPositionManagerBase, Multicall { | ||
| using SafeERC20 for IERC20; | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// @inheritdoc IPositionManagerBase | ||
| ISpoke public immutable override SPOKE; | ||
|
|
||
| /// @dev Constructor. | ||
| /// @param spoke_ The address of the spoke contract. | ||
| constructor(address spoke_) { | ||
| require(spoke_ != address(0), InvalidAddress()); | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| SPOKE = ISpoke(spoke_); | ||
| } | ||
|
|
||
| /// @inheritdoc IPositionManagerBase | ||
| function setSelfAsUserPositionManagerWithSig( | ||
Kogaroshi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| EIP712Types.SetUserPositionManager calldata params, | ||
| bytes calldata signature | ||
| ) external { | ||
| try | ||
| SPOKE.setUserPositionManagerWithSig( | ||
| address(this), | ||
| params.user, | ||
| params.approve, | ||
| params.nonce, | ||
| params.deadline, | ||
| signature | ||
| ) | ||
| {} catch {} | ||
| } | ||
|
|
||
| /// @inheritdoc IPositionManagerBase | ||
| function permitReserve( | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| uint256 reserveId, | ||
| address onBehalfOf, | ||
| uint256 value, | ||
| uint256 deadline, | ||
| uint8 permitV, | ||
| bytes32 permitR, | ||
| bytes32 permitS | ||
| ) external { | ||
| address underlying = address(_getReserveUnderlying(reserveId)); | ||
| try | ||
| IERC20Permit(underlying).permit({ | ||
| owner: onBehalfOf, | ||
| spender: address(this), | ||
| value: value, | ||
| deadline: deadline, | ||
| v: permitV, | ||
| r: permitR, | ||
| s: permitS | ||
| }) | ||
| {} catch {} | ||
| } | ||
|
|
||
| /// @return The underlying asset for `reserveId` on the Spoke. | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| function _getReserveUnderlying(uint256 reserveId) internal view returns (IERC20) { | ||
| return IERC20(SPOKE.getReserve(reserveId).underlying); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // Copyright (c) 2025 Aave Labs | ||
| pragma solidity 0.8.28; | ||
|
|
||
| import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol'; | ||
| import {PositionManagerBase} from 'src/position-manager/PositionManagerBase.sol'; | ||
| import {ISupplyRepayPositionManager} from 'src/position-manager/interfaces/ISupplyRepayPositionManager.sol'; | ||
|
|
||
| /// @title SupplyRepayPositionManager | ||
| /// @author Aave Labs | ||
| /// @notice Position manager to handle supply and repay actions on behalf of users. | ||
| contract SupplyRepayPositionManager is ISupplyRepayPositionManager, PositionManagerBase { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wondering if we should find another name for this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| using SafeERC20 for IERC20; | ||
|
|
||
| /// @dev Constructor. | ||
| /// @param spoke_ The address of the spoke contract. | ||
| constructor(address spoke_) PositionManagerBase(spoke_) {} | ||
|
|
||
| /// @inheritdoc ISupplyRepayPositionManager | ||
| function supplyOnBehalfOf( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't we want to have the intents-based version of these actions? |
||
| uint256 reserveId, | ||
| uint256 amount, | ||
| address onBehalfOf | ||
| ) external returns (uint256, uint256) { | ||
| IERC20 asset = _getReserveUnderlying(reserveId); | ||
| asset.safeTransferFrom(msg.sender, address(this), amount); | ||
Kogaroshi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| asset.forceApprove(address(SPOKE), amount); | ||
| return SPOKE.supply(reserveId, amount, onBehalfOf); | ||
| } | ||
|
|
||
| /// @inheritdoc ISupplyRepayPositionManager | ||
| function repayOnBehalfOf( | ||
| uint256 reserveId, | ||
| uint256 amount, | ||
| address onBehalfOf | ||
| ) external returns (uint256, uint256) { | ||
| // TODO : see for limitations on repay amount ? | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| IERC20 asset = _getReserveUnderlying(reserveId); | ||
| asset.safeTransferFrom(msg.sender, address(this), amount); | ||
| asset.forceApprove(address(SPOKE), amount); | ||
| return SPOKE.repay(reserveId, amount, onBehalfOf); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // Copyright (c) 2025 Aave Labs | ||
| pragma solidity 0.8.28; | ||
|
|
||
| import {SignatureChecker} from 'src/dependencies/openzeppelin/SignatureChecker.sol'; | ||
| import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol'; | ||
| import {EIP712} from 'src/dependencies/solady/EIP712.sol'; | ||
| import {NoncesKeyed} from 'src/utils/NoncesKeyed.sol'; | ||
| import {EIP712Hash, EIP712Types} from 'src/position-manager/libraries/EIP712Hash.sol'; | ||
| import {PositionManagerBase} from 'src/position-manager/PositionManagerBase.sol'; | ||
| import {IWithdrawPermitPositionManager} from 'src/position-manager/interfaces/IWithdrawPermitPositionManager.sol'; | ||
|
|
||
| /// @title WithdrawPermitPositionManager | ||
| /// @author Aave Labs | ||
| /// @notice Position manager to handle withdraw permit actions on behalf of users. | ||
| contract WithdrawPermitPositionManager is | ||
| IWithdrawPermitPositionManager, | ||
| PositionManagerBase, | ||
| NoncesKeyed, | ||
| EIP712 | ||
| { | ||
| using SafeERC20 for IERC20; | ||
| using EIP712Hash for *; | ||
|
|
||
| /// @notice Mapping of withdraw allowances: owner => spender => reserveId => amount. | ||
| mapping(address => mapping(address => mapping(uint256 => uint256))) private _withdrawAllowances; | ||
|
|
||
| /// @dev Constructor. | ||
| /// @param spoke_ The address of the spoke contract. | ||
| constructor(address spoke_) PositionManagerBase(spoke_) {} | ||
|
|
||
| /// @inheritdoc IWithdrawPermitPositionManager | ||
| function approveWithdraw(address spender, uint256 reserveId, uint256 amount) external { | ||
| _withdrawAllowances[msg.sender][spender][reserveId] = amount; | ||
| emit WithdrawApproval(msg.sender, spender, reserveId, amount); | ||
| } | ||
|
|
||
| /// @inheritdoc IWithdrawPermitPositionManager | ||
| function approveWithdrawWithSig( | ||
| EIP712Types.WithdrawPermit calldata params, | ||
| bytes calldata signature | ||
| ) external { | ||
| require(block.timestamp <= params.deadline, InvalidSignature()); | ||
| address user = params.owner; | ||
| bytes32 digest = _hashTypedData(params.hash()); | ||
| require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature()); | ||
| _useCheckedNonce(user, params.nonce); | ||
|
|
||
| _withdrawAllowances[user][params.spender][params.reserveId] = params.amount; | ||
| emit WithdrawApproval(user, params.spender, params.reserveId, params.amount); | ||
| } | ||
|
|
||
| /// @inheritdoc IWithdrawPermitPositionManager | ||
| function withdrawOnBehalfOf( | ||
| uint256 reserveId, | ||
| uint256 amount, | ||
| address onBehalfOf | ||
| ) external returns (uint256, uint256) { | ||
| uint256 currentAllowance = _withdrawAllowances[onBehalfOf][msg.sender][reserveId]; | ||
| if (currentAllowance < amount) { | ||
| revert InsufficientWithdrawAllowance(); | ||
| } | ||
| _withdrawAllowances[onBehalfOf][msg.sender][reserveId] -= amount; | ||
DhairyaSethi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| IERC20 asset = _getReserveUnderlying(reserveId); | ||
| (uint256 withdrawnShares, uint256 withdrawnAmount) = SPOKE.withdraw( | ||
| reserveId, | ||
| amount, | ||
| onBehalfOf | ||
| ); | ||
| asset.safeTransfer(msg.sender, withdrawnAmount); | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return (withdrawnShares, withdrawnAmount); | ||
| } | ||
|
|
||
| /// @inheritdoc IWithdrawPermitPositionManager | ||
| function withdrawAllowance( | ||
| address owner, | ||
| address spender, | ||
| uint256 reserveId | ||
| ) external view returns (uint256) { | ||
| return _withdrawAllowances[owner][spender][reserveId]; | ||
| } | ||
|
|
||
| /// @inheritdoc IWithdrawPermitPositionManager | ||
| function WITHDRAW_PERMIT_TYPEHASH() external pure returns (bytes32) { | ||
| return EIP712Hash.WITHDRAW_TYPEHASH; | ||
| } | ||
|
|
||
| function _domainNameAndVersion() internal pure override returns (string memory, string memory) { | ||
| return ('WithdrawPermitPositionManager', '1'); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // Copyright (c) 2025 Aave Labs | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {EIP712Types} from 'src/libraries/types/EIP712Types.sol'; | ||
| import {IPositionManagerBase} from 'src/position-manager/interfaces/IPositionManagerBase.sol'; | ||
|
|
||
| interface ICreditDelegationPositionManager is IPositionManagerBase { | ||
| error InsufficientCreditDelegation(); | ||
|
|
||
| event CreditDelegation( | ||
| address indexed owner, | ||
| address indexed spender, | ||
| uint256 indexed reserveId, | ||
| uint256 amount | ||
| ); | ||
|
|
||
| function approveCreditDelegation(address spender, uint256 reserveId, uint256 amount) external; | ||
|
|
||
| function approveCreditDelegationWithSig( | ||
| EIP712Types.CreditDelegation calldata params, | ||
| bytes calldata signature | ||
| ) external; | ||
|
|
||
| function borrowOnBehalfOf( | ||
| uint256 reserveId, | ||
| uint256 amount, | ||
| address onBehalfOf | ||
| ) external returns (uint256, uint256); | ||
|
|
||
| function creditDelegationAllowance( | ||
| address owner, | ||
| address spender, | ||
| uint256 reserveId | ||
| ) external view returns (uint256); | ||
|
|
||
| /// @notice Returns the type hash for the CreditDelegation intent. | ||
| function CREDIT_DELEGATION_TYPEHASH() external view returns (bytes32); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we still need
SetUserPositionManagerstruct here? seems like theyre only used in the testsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no preference, could keep for future usage, or just move to tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think if not used in src prefer to move it to test, perhaps TestTypes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it will be used in src, on the spoke
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm where is this struct used? not seeing it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setUserPositionManagerWithSig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
method isn't consistent w the rest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually spoke field is annoying, we'll see. but this struct doesn't belong in tests dir for sure