-
Notifications
You must be signed in to change notification settings - Fork 61
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
Changes from 2 commits
a00ec35
ec2170c
7ae1fdb
0ffdb57
abcf269
a42b632
a1e33c0
aee822d
f33c26d
ffa90f0
480e518
8dbe9c0
68cb0d4
6f718ba
bc4a75c
bf6476f
883990b
19443f7
1063e3f
2e28199
f0477f1
c922404
21bce1d
85105ec
d6a31a3
d26b6f3
87a0da0
38a39b0
728fc5c
c69e8ca
1922ab3
87cefba
7375b26
bd27684
950ddd8
0bc1956
c381ae6
74e9556
7754e70
9aae00d
7f976b2
2f81479
d783743
dc2b55d
5c98657
2b4e475
db9220a
6922f0f
ea0d782
bb87690
a7bb6e4
8030c43
a5bc542
c054b75
c1d7dd0
2b18a65
2b747a2
ceaf94f
2a52053
62d4edd
2f291a4
f06ebf0
3285a8d
96368d1
56ac15f
a977805
ad40ee5
f557b01
b48de13
ffbbffd
03391a1
64dd44c
0549c68
9b6cc7d
5e31b1d
c5881ef
2bf96b3
96ebca5
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,91 @@ | ||
| // 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 {MathUtils} from 'src/libraries/math/MathUtils.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 *; | ||
| using MathUtils for uint256; | ||
|
|
||
| /// @notice Mapping of credit delegations. | ||
| mapping(address owner => mapping(address spender => mapping(uint256 reserveId => uint256 amount))) | ||
| private _creditDelegations; | ||
|
|
||
| /// @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 { | ||
| _creditDelegations[msg.sender][spender][reserveId] = amount; | ||
| emit CreditDelegation(msg.sender, spender, reserveId, amount); | ||
| } | ||
|
|
||
| /// @inheritdoc ICreditDelegationPositionManager | ||
| function approveCreditDelegationWithSig( | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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) { | ||
| require(amount > 0, InvalidAmount()); | ||
| uint256 currentAllowance = _creditDelegations[onBehalfOf][msg.sender][reserveId]; | ||
| require(currentAllowance >= amount, InsufficientCreditDelegation(currentAllowance, amount)); | ||
| _creditDelegations[onBehalfOf][msg.sender][reserveId] = currentAllowance.uncheckedSub(amount); | ||
|
|
||
| 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_); | ||
| } | ||
|
Comment on lines
+41
to
+45
|
||
|
|
||
| /// @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 | ||
|
Comment on lines
+81
to
+83
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. ok so we ensure |
||
| 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,48 @@ | ||
| // 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 { | ||
Kogaroshi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| using SafeERC20 for IERC20; | ||
|
|
||
| /// @dev Constructor. | ||
| /// @param spoke_ The address of the spoke contract. | ||
| constructor(address spoke_) PositionManagerBase(spoke_) {} | ||
|
|
||
| /// @inheritdoc ISupplyRepayPositionManager | ||
| function supplyOnBehalfOf( | ||
|
||
| uint256 reserveId, | ||
| uint256 amount, | ||
| address onBehalfOf | ||
| ) external returns (uint256, uint256) { | ||
| require(amount > 0, InvalidAmount()); | ||
| IERC20 asset = _getReserveUnderlying(reserveId); | ||
| asset.safeTransferFrom(msg.sender, address(this), amount); | ||
Kogaroshi marked this conversation as resolved.
Outdated
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) { | ||
| require(amount > 0, InvalidAmount()); | ||
| IERC20 asset = _getReserveUnderlying(reserveId); | ||
|
|
||
| uint256 userTotalDebt = SPOKE.getUserTotalDebt(reserveId, onBehalfOf); | ||
| uint256 repayAmount = amount > userTotalDebt ? userTotalDebt : amount; | ||
miguelmtzinf marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| asset.safeTransferFrom(msg.sender, address(this), repayAmount); | ||
| asset.forceApprove(address(SPOKE), repayAmount); | ||
| return SPOKE.repay(reserveId, repayAmount, onBehalfOf); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| // 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 {MathUtils} from 'src/libraries/math/MathUtils.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 *; | ||
| using MathUtils for uint256; | ||
|
|
||
| /// @notice Mapping of withdraw allowances. | ||
| mapping(address owner => mapping(address spender => mapping(uint256 reserveId => uint256 amount))) | ||
| 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) { | ||
| require(amount > 0, InvalidAmount()); | ||
| uint256 currentAllowance = _withdrawAllowances[onBehalfOf][msg.sender][reserveId]; | ||
| require(currentAllowance >= amount, InsufficientWithdrawAllowance(currentAllowance, amount)); | ||
| _withdrawAllowances[onBehalfOf][msg.sender][reserveId] = currentAllowance.uncheckedSub(amount); | ||
|
|
||
| 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,64 @@ | ||
| // 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'; | ||
|
|
||
| /// @title ICreditDelegationPositionManager | ||
| /// @author Aave Labs | ||
| /// @notice Interface for position manager handling credit delegation actions. | ||
| interface ICreditDelegationPositionManager is IPositionManagerBase { | ||
| /// @notice Thrown when the credit delegation allowance is insufficient. | ||
| error InsufficientCreditDelegation(uint256 allowance, uint256 required); | ||
|
|
||
| /// @notice Emitted when a credit delegation is given. | ||
| event CreditDelegation( | ||
| address indexed owner, | ||
| address indexed spender, | ||
| uint256 indexed reserveId, | ||
| uint256 amount | ||
| ); | ||
|
|
||
| /// @notice Approves a credit delegation allowance for a spender. | ||
| /// @param spender The address of the spender to receive the allowance. | ||
| /// @param reserveId The identifier of the reserve. | ||
| /// @param amount The amount of allowance. | ||
| function approveCreditDelegation(address spender, uint256 reserveId, uint256 amount) external; | ||
|
|
||
| /// @notice Approves a credit delegation allowance for a spender via signature. | ||
| /// @param params The structured CreditDelegation parameters. | ||
| /// @param signature The signed bytes for the intent. | ||
| function approveCreditDelegationWithSig( | ||
| EIP712Types.CreditDelegation calldata params, | ||
| bytes calldata signature | ||
| ) external; | ||
|
|
||
| /// @notice Executes a borrow on behalf of a user. | ||
| /// @dev The caller must have sufficient credit delegation allowance from `onBehalfOf`. | ||
| /// @dev The caller receives the borrowed assets. | ||
| /// @param reserveId The identifier of the reserve. | ||
| /// @param amount The amount to borrow. | ||
| /// @param onBehalfOf The address of the user to borrow on behalf of. | ||
| /// @return The amount of shares borrowed. | ||
| /// @return The amount of assets borrowed. | ||
| function borrowOnBehalfOf( | ||
| uint256 reserveId, | ||
| uint256 amount, | ||
| address onBehalfOf | ||
| ) external returns (uint256, uint256); | ||
|
|
||
| /// @notice Returns the credit delegation allowance for a spender on behalf of an owner. | ||
| /// @param owner The address of the owner. | ||
| /// @param spender The address of the spender. | ||
| /// @param reserveId The identifier of the reserve. | ||
| /// @return The amount of credit delegation allowance. | ||
| 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.