-
Notifications
You must be signed in to change notification settings - Fork 35
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 all 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
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,11 @@ | ||
| { | ||
| "approveWithdraw": "49895", | ||
| "approveWithdrawWithSig": "66560", | ||
| "borrowOnBehalfOf": "309723", | ||
| "delegateCredit": "49864", | ||
| "delegateCreditWithSig": "66505", | ||
| "renounceCreditDelegation": "28020", | ||
| "renounceWithdrawAllowance": "28007", | ||
| "withdrawOnBehalfOf: full": "121329", | ||
| "withdrawOnBehalfOf: partial": "131461" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| { | ||
| "borrowNative": "229604", | ||
| "repayNative": "168312", | ||
| "supplyAsCollateralNative": "160373", | ||
| "supplyNative": "136476", | ||
| "withdrawNative: full": "125620", | ||
| "withdrawNative: partial": "136825" | ||
| "borrowNative": "229684", | ||
| "repayNative": "168370", | ||
| "supplyAsCollateralNative": "160453", | ||
| "supplyNative": "136523", | ||
| "withdrawNative: full": "125667", | ||
| "withdrawNative: partial": "136883" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "renounceGlobalPermission": "27699", | ||
| "renounceUserDynamicConfigPermission": "27763", | ||
| "renounceUserRiskPremiumPermission": "27720", | ||
| "renounceUsingAsCollateralPermission": "27698", | ||
| "setGlobalPermission": "49868", | ||
| "setUserDynamicConfigPermission": "49845", | ||
| "setUserRiskPremiumPermission": "49891", | ||
| "setUsingAsCollateralOnBehalfOf": "71916", | ||
| "setUsingAsCollateralPermission": "49847", | ||
| "updateUserDynamicConfigOnBehalfOf": "49577", | ||
| "updateUserRiskPremiumOnBehalfOf": "130940" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "setSelfAsUserPositionManagerWithSig": "74809" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
| { | ||
| "borrowWithSig": "215893", | ||
| "repayWithSig": "189160", | ||
| "setSelfAsUserPositionManagerWithSig": "74858", | ||
| "setUsingAsCollateralWithSig": "85053", | ||
| "supplyWithSig": "153205", | ||
| "updateUserDynamicConfigWithSig": "62769", | ||
| "updateUserRiskPremiumWithSig": "61579", | ||
| "withdrawWithSig": "131713" | ||
| "setSelfAsUserPositionManagerWithSig": "74880", | ||
| "setUsingAsCollateralWithSig": "85075", | ||
| "supplyWithSig": "153223", | ||
| "updateUserDynamicConfigWithSig": "62791", | ||
| "updateUserRiskPremiumWithSig": "61513", | ||
| "withdrawWithSig": "131731" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "repayOnBehalfOf": "170151", | ||
| "supplyOnBehalfOf": "138004" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,290 @@ | ||
| // 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'; | ||
Kogaroshi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import {ISpokeBase} from 'src/spoke/interfaces/ISpokeBase.sol'; | ||
| import {IAllowancePositionManager} from 'src/position-manager/interfaces/IAllowancePositionManager.sol'; | ||
|
|
||
| /// @title AllowancePositionManager | ||
| /// @author Aave Labs | ||
| /// @notice Position manager to handle withdraw permit, credit delegation and borrow actions on behalf of users. | ||
| contract AllowancePositionManager is | ||
| IAllowancePositionManager, | ||
| PositionManagerBase, | ||
| NoncesKeyed, | ||
| EIP712 | ||
| { | ||
| using SafeERC20 for IERC20; | ||
| using MathUtils for uint256; | ||
| using EIP712Hash for *; | ||
|
|
||
| mapping(address spoke => mapping(uint256 reserveId => mapping(address owner => mapping(address spender => uint256 amount)))) | ||
| private _withdrawAllowances; | ||
|
|
||
| mapping(address spoke => mapping(uint256 reserveId => mapping(address owner => mapping(address spender => uint256 amount)))) | ||
| private _creditDelegations; | ||
yan-man marked this conversation as resolved.
Show resolved
Hide resolved
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. i dn't be opposed to use similar terms (e.g.
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. Yeah, kept |
||
|
|
||
| /// @dev Constructor. | ||
| /// @param initialOwner_ The address of the initial owner. | ||
| constructor(address initialOwner_) PositionManagerBase(initialOwner_) {} | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function approveWithdraw( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address spender, | ||
| uint256 amount | ||
| ) external onlyRegisteredSpoke(spoke) { | ||
| _updateWithdrawAllowance({ | ||
| spoke: spoke, | ||
| reserveId: reserveId, | ||
| owner: msg.sender, | ||
| spender: spender, | ||
| newAllowance: amount | ||
| }); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function approveWithdrawWithSig( | ||
| EIP712Types.WithdrawPermit calldata params, | ||
| bytes calldata signature | ||
| ) external onlyRegisteredSpoke(params.spoke) { | ||
| require(block.timestamp <= params.deadline, InvalidSignature()); | ||
| bytes32 digest = _hashTypedData(params.hash()); | ||
| require( | ||
| SignatureChecker.isValidSignatureNow(params.owner, digest, signature), | ||
| InvalidSignature() | ||
| ); | ||
| _useCheckedNonce(params.owner, params.nonce); | ||
|
|
||
| _updateWithdrawAllowance({ | ||
| spoke: params.spoke, | ||
| reserveId: params.reserveId, | ||
| owner: params.owner, | ||
| spender: params.spender, | ||
| newAllowance: params.amount | ||
| }); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function delegateCredit( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address spender, | ||
| uint256 amount | ||
| ) external onlyRegisteredSpoke(spoke) { | ||
| _updateCreditDelegation({ | ||
| spoke: spoke, | ||
| reserveId: reserveId, | ||
| owner: msg.sender, | ||
| spender: spender, | ||
| newCreditDelegation: amount | ||
| }); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function delegateCreditWithSig( | ||
| EIP712Types.CreditDelegation calldata params, | ||
| bytes calldata signature | ||
| ) external onlyRegisteredSpoke(params.spoke) { | ||
| require(block.timestamp <= params.deadline, InvalidSignature()); | ||
| bytes32 digest = _hashTypedData(params.hash()); | ||
| require( | ||
| SignatureChecker.isValidSignatureNow(params.owner, digest, signature), | ||
| InvalidSignature() | ||
| ); | ||
| _useCheckedNonce(params.owner, params.nonce); | ||
|
|
||
| _updateCreditDelegation({ | ||
| spoke: params.spoke, | ||
| reserveId: params.reserveId, | ||
| owner: params.owner, | ||
| spender: params.spender, | ||
| newCreditDelegation: params.amount | ||
| }); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function renounceWithdrawAllowance( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address owner | ||
| ) external onlyRegisteredSpoke(spoke) { | ||
| if (_withdrawAllowances[spoke][reserveId][owner][msg.sender] == 0) { | ||
| return; | ||
| } | ||
| _updateWithdrawAllowance({ | ||
| spoke: spoke, | ||
| reserveId: reserveId, | ||
| owner: owner, | ||
| spender: msg.sender, | ||
| newAllowance: 0 | ||
| }); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function renounceCreditDelegation( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address owner | ||
| ) external onlyRegisteredSpoke(spoke) { | ||
| if (_creditDelegations[spoke][reserveId][owner][msg.sender] == 0) { | ||
| return; | ||
| } | ||
| _updateCreditDelegation({ | ||
| spoke: spoke, | ||
| reserveId: reserveId, | ||
| owner: owner, | ||
| spender: msg.sender, | ||
| newCreditDelegation: 0 | ||
| }); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function withdrawOnBehalfOf( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| uint256 amount, | ||
| address onBehalfOf | ||
| ) external onlyRegisteredSpoke(spoke) returns (uint256, uint256) { | ||
| IERC20 asset = IERC20(_getReserveUnderlying(spoke, reserveId)); | ||
| _spendWithdrawAllowance({ | ||
| spoke: spoke, | ||
| reserveId: reserveId, | ||
| owner: onBehalfOf, | ||
| spender: msg.sender, | ||
| amount: amount | ||
|
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. Thinking about max withdraw, which could be a bit confusing / hard to achieve...
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. max withdraw can be executed by passing an absurdly high
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. 21bce1d for non-decreasing infinite allowance. |
||
| }); | ||
|
|
||
| (uint256 withdrawnShares, uint256 withdrawnAmount) = ISpokeBase(spoke).withdraw( | ||
| reserveId, | ||
| amount, | ||
| onBehalfOf | ||
| ); | ||
| asset.safeTransfer(msg.sender, withdrawnAmount); | ||
|
|
||
| return (withdrawnShares, withdrawnAmount); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function borrowOnBehalfOf( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| uint256 amount, | ||
| address onBehalfOf | ||
| ) external onlyRegisteredSpoke(spoke) returns (uint256, uint256) { | ||
| IERC20 asset = IERC20(_getReserveUnderlying(spoke, reserveId)); | ||
| _spendCreditDelegation({ | ||
| spoke: spoke, | ||
| reserveId: reserveId, | ||
| owner: onBehalfOf, | ||
| spender: msg.sender, | ||
| amount: amount | ||
| }); | ||
|
|
||
| (uint256 borrowedShares, uint256 borrowedAmount) = ISpokeBase(spoke).borrow( | ||
| reserveId, | ||
| amount, | ||
| onBehalfOf | ||
| ); | ||
| asset.safeTransfer(msg.sender, borrowedAmount); | ||
|
|
||
| return (borrowedShares, borrowedAmount); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function withdrawAllowance( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address owner, | ||
| address spender | ||
| ) external view returns (uint256) { | ||
| return _withdrawAllowances[spoke][reserveId][owner][spender]; | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function creditDelegation( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address owner, | ||
| address spender | ||
| ) external view returns (uint256) { | ||
| return _creditDelegations[spoke][reserveId][owner][spender]; | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function DOMAIN_SEPARATOR() external view returns (bytes32) { | ||
| return _domainSeparator(); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function WITHDRAW_PERMIT_TYPEHASH() external pure returns (bytes32) { | ||
| return EIP712Hash.WITHDRAW_PERMIT_TYPEHASH; | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function CREDIT_DELEGATION_TYPEHASH() external pure returns (bytes32) { | ||
| return EIP712Hash.CREDIT_DELEGATION_TYPEHASH; | ||
| } | ||
|
|
||
| function _updateWithdrawAllowance( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address owner, | ||
| address spender, | ||
| uint256 newAllowance | ||
| ) internal { | ||
| _withdrawAllowances[spoke][reserveId][owner][spender] = newAllowance; | ||
| emit WithdrawApproval(spoke, reserveId, owner, spender, newAllowance); | ||
| } | ||
|
|
||
| function _updateCreditDelegation( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address owner, | ||
| address spender, | ||
| uint256 newCreditDelegation | ||
| ) internal { | ||
| _creditDelegations[spoke][reserveId][owner][spender] = newCreditDelegation; | ||
| emit CreditDelegation(spoke, reserveId, owner, spender, newCreditDelegation); | ||
| } | ||
|
|
||
| function _spendWithdrawAllowance( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address owner, | ||
| address spender, | ||
| uint256 amount | ||
| ) internal { | ||
| uint256 currentAllowance = _withdrawAllowances[spoke][reserveId][owner][spender]; | ||
| require(currentAllowance >= amount, InsufficientWithdrawAllowance(currentAllowance, amount)); | ||
| if (currentAllowance != type(uint256).max) { | ||
| _withdrawAllowances[spoke][reserveId][owner][spender] = currentAllowance.uncheckedSub(amount); | ||
| } | ||
| } | ||
|
|
||
| function _spendCreditDelegation( | ||
| address spoke, | ||
| uint256 reserveId, | ||
| address owner, | ||
| address spender, | ||
| uint256 amount | ||
| ) internal { | ||
| uint256 currentAllowance = _creditDelegations[spoke][reserveId][owner][spender]; | ||
| require(currentAllowance >= amount, InsufficientCreditDelegation(currentAllowance, amount)); | ||
| if (currentAllowance != type(uint256).max) { | ||
| _creditDelegations[spoke][reserveId][owner][spender] = currentAllowance.uncheckedSub(amount); | ||
| } | ||
| } | ||
|
|
||
| function _domainNameAndVersion() internal pure override returns (string memory, string memory) { | ||
| return ('AllowancePositionManager', '1'); | ||
| } | ||
| } | ||
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