-
Notifications
You must be signed in to change notification settings - Fork 36
feat: Temporary approvals in AllowancePositionManager #1059
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: feat/new-position-managers
Are you sure you want to change the base?
Changes from 6 commits
a443ac9
6ee2b26
198c2b7
f7a36b2
2752b73
26743fa
4cb32ed
53340e3
6e766f6
01225ac
c7330ed
a96826c
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 |
|---|---|---|
|
|
@@ -4,6 +4,8 @@ pragma solidity 0.8.28; | |
|
|
||
| import {SignatureChecker} from 'src/dependencies/openzeppelin/SignatureChecker.sol'; | ||
| import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol'; | ||
| import {SlotDerivation} from 'src/dependencies/openzeppelin/SlotDerivation.sol'; | ||
| import {TransientSlot} from 'src/dependencies/openzeppelin/TransientSlot.sol'; | ||
| import {EIP712} from 'src/dependencies/solady/EIP712.sol'; | ||
| import {MathUtils} from 'src/libraries/math/MathUtils.sol'; | ||
| import {NoncesKeyed} from 'src/utils/NoncesKeyed.sol'; | ||
|
|
@@ -24,15 +26,27 @@ contract AllowancePositionManager is | |
| using SafeERC20 for IERC20; | ||
| using EIP712Hash for *; | ||
| using MathUtils for uint256; | ||
| using SlotDerivation for bytes32; | ||
| using TransientSlot for *; | ||
|
|
||
| /// @notice Mapping of withdraw allowances. | ||
| mapping(address owner => mapping(address spender => mapping(uint256 reserveId => uint256 amount))) | ||
| private _withdrawAllowances; | ||
|
|
||
| /// @notice Slot for the temporary withdraw allowances. | ||
| /// @dev keccak256('temporary.withdrawAllowances') | ||
| bytes32 private constant _TEMPORARY_WITHDRAW_ALLOWANCES_SLOT = | ||
| 0x1c6a61279a13a86a789311ddf30aee38e2f4a9f6c4aad1ff4a2e75a4018e68c3; | ||
|
||
|
|
||
| /// @notice Mapping of credit delegations. | ||
| mapping(address owner => mapping(address spender => mapping(uint256 reserveId => uint256 amount))) | ||
| private _creditDelegations; | ||
|
|
||
| /// @notice Slot for the temporary credit delegations. | ||
| /// @dev keccak256('temporary.creditDelegations') | ||
| bytes32 private constant _TEMPORARY_CREDIT_DELEGATIONS_SLOT = | ||
| 0xcd470af8670f5baa744a0341af8a2e3f5d7ca086178908432a5cfaf39cb9299d; | ||
|
|
||
| /// @dev Constructor. | ||
| /// @param spoke_ The address of the spoke contract. | ||
| constructor(address spoke_) PositionManagerBase(spoke_) {} | ||
|
|
@@ -58,6 +72,11 @@ contract AllowancePositionManager is | |
| _updateWithdrawAllowance(params.owner, params.spender, params.reserveId, params.amount, true); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function temporaryApproveWithdraw(address spender, uint256 reserveId, uint256 amount) external { | ||
| _temporaryWithdrawAllowancesSlot(msg.sender, spender, reserveId).tstore(amount); | ||
DhairyaSethi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function creditDelegation(address spender, uint256 reserveId, uint256 amount) external { | ||
| _updateCreditDelegation(msg.sender, spender, reserveId, amount, true); | ||
avniculae marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
@@ -79,6 +98,11 @@ contract AllowancePositionManager is | |
| _updateCreditDelegation(params.owner, params.spender, params.reserveId, params.amount, true); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function temporaryCreditDelegation(address spender, uint256 reserveId, uint256 amount) external { | ||
| _temporaryCreditDelegationsSlot(msg.sender, spender, reserveId).tstore(amount); | ||
| } | ||
|
|
||
| /// @inheritdoc IAllowancePositionManager | ||
| function renounceWithdrawAllowance(address owner, uint256 reserveId) external { | ||
| _updateWithdrawAllowance( | ||
|
|
@@ -108,15 +132,7 @@ contract AllowancePositionManager is | |
| address onBehalfOf | ||
| ) external returns (uint256, uint256) { | ||
| require(amount > 0, InvalidAmount()); | ||
| uint256 currentAllowance = _withdrawAllowances[onBehalfOf][msg.sender][reserveId]; | ||
| require(currentAllowance >= amount, InsufficientWithdrawAllowance(currentAllowance, amount)); | ||
| _updateWithdrawAllowance( | ||
| onBehalfOf, | ||
| msg.sender, | ||
| reserveId, | ||
| currentAllowance.uncheckedSub(amount), | ||
| true | ||
| ); | ||
| _spendWithdrawAllowance(onBehalfOf, msg.sender, reserveId, amount); | ||
|
|
||
| IERC20 asset = _getReserveUnderlying(reserveId); | ||
| (uint256 withdrawnShares, uint256 withdrawnAmount) = ISpokeBase(SPOKE).withdraw( | ||
|
|
@@ -136,15 +152,7 @@ contract AllowancePositionManager is | |
| address onBehalfOf | ||
| ) external returns (uint256, uint256) { | ||
| require(amount > 0, InvalidAmount()); | ||
| uint256 currentAllowance = _creditDelegations[onBehalfOf][msg.sender][reserveId]; | ||
| require(currentAllowance >= amount, InsufficientCreditDelegation(currentAllowance, amount)); | ||
| _updateCreditDelegation( | ||
| onBehalfOf, | ||
| msg.sender, | ||
| reserveId, | ||
| currentAllowance.uncheckedSub(amount), | ||
| true | ||
| ); | ||
| _spendCreditDelegation(onBehalfOf, msg.sender, reserveId, amount); | ||
|
|
||
| IERC20 asset = _getReserveUnderlying(reserveId); | ||
| (uint256 borrowedShares, uint256 borrowedAmount) = ISpokeBase(SPOKE).borrow( | ||
|
|
@@ -219,4 +227,90 @@ contract AllowancePositionManager is | |
| function _domainNameAndVersion() internal pure override returns (string memory, string memory) { | ||
| return ('AllowancePositionManager', '1'); | ||
| } | ||
|
|
||
| /// @dev Temporary allowance takes precedence over stored allowance, and does not cumulate. | ||
| function _spendWithdrawAllowance( | ||
| address onBehalfOf, | ||
| address spender, | ||
| uint256 reserveId, | ||
| uint256 amount | ||
| ) internal { | ||
| uint256 temporaryAllowance = _temporaryWithdrawAllowancesSlot(onBehalfOf, spender, reserveId) | ||
| .tload(); | ||
| if (temporaryAllowance > 0) { | ||
| require( | ||
| temporaryAllowance >= amount, | ||
| InsufficientWithdrawAllowance(temporaryAllowance, amount) | ||
DhairyaSethi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ); | ||
DhairyaSethi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| _temporaryWithdrawAllowancesSlot(onBehalfOf, spender, reserveId).tstore( | ||
| temporaryAllowance.uncheckedSub(amount) | ||
| ); | ||
| } else { | ||
| uint256 allowance = _withdrawAllowances[onBehalfOf][spender][reserveId]; | ||
| require(allowance >= amount, InsufficientWithdrawAllowance(allowance, amount)); | ||
| _updateWithdrawAllowance({ | ||
| owner: onBehalfOf, | ||
| spender: spender, | ||
| reserveId: reserveId, | ||
| newAllowance: allowance.uncheckedSub(amount), | ||
| emitEvent: true | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /// @dev Temporary allowance takes precedence over stored allowance, and does not cumulate. | ||
| function _spendCreditDelegation( | ||
| address onBehalfOf, | ||
| address spender, | ||
| uint256 reserveId, | ||
| uint256 amount | ||
| ) internal { | ||
| uint256 temporaryAllowance = _temporaryCreditDelegationsSlot(onBehalfOf, spender, reserveId) | ||
| .tload(); | ||
| if (temporaryAllowance > 0) { | ||
| require( | ||
| temporaryAllowance >= amount, | ||
| InsufficientCreditDelegation(temporaryAllowance, amount) | ||
| ); | ||
| _temporaryCreditDelegationsSlot(onBehalfOf, spender, reserveId).tstore( | ||
| temporaryAllowance.uncheckedSub(amount) | ||
| ); | ||
| } else { | ||
| uint256 allowance = _creditDelegations[onBehalfOf][spender][reserveId]; | ||
| require(allowance >= amount, InsufficientCreditDelegation(allowance, amount)); | ||
| _updateCreditDelegation({ | ||
| owner: onBehalfOf, | ||
| spender: spender, | ||
| reserveId: reserveId, | ||
| newCreditDelegation: allowance.uncheckedSub(amount), | ||
| emitEvent: true | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| function _temporaryWithdrawAllowancesSlot( | ||
| address owner, | ||
| address spender, | ||
| uint256 reserveId | ||
| ) internal pure returns (TransientSlot.Uint256Slot) { | ||
| return | ||
| _TEMPORARY_WITHDRAW_ALLOWANCES_SLOT | ||
| .deriveMapping(owner) | ||
| .deriveMapping(spender) | ||
| .deriveMapping(reserveId) | ||
| .asUint256(); | ||
| } | ||
|
|
||
| function _temporaryCreditDelegationsSlot( | ||
| address owner, | ||
| address spender, | ||
| uint256 reserveId | ||
| ) internal pure returns (TransientSlot.Uint256Slot) { | ||
| return | ||
| _TEMPORARY_CREDIT_DELEGATIONS_SLOT | ||
| .deriveMapping(owner) | ||
| .deriveMapping(spender) | ||
| .deriveMapping(reserveId) | ||
| .asUint256(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // Copyright (c) 2025 Aave Labs | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {TransientSlot} from 'src/dependencies/openzeppelin/TransientSlot.sol'; | ||
| import {AllowancePositionManager} from 'src/position-manager/AllowancePositionManager.sol'; | ||
|
|
||
| contract AllowancePositionManagerWrapper is AllowancePositionManager { | ||
| using TransientSlot for *; | ||
|
|
||
| constructor(address spoke_) AllowancePositionManager(spoke_) {} | ||
|
|
||
| function temporaryWithdrawAllowance( | ||
| address owner, | ||
| address spender, | ||
| uint256 reserveId | ||
| ) external view returns (uint256) { | ||
| return _temporaryWithdrawAllowancesSlot(owner, spender, reserveId).tload(); | ||
| } | ||
|
|
||
| function temporaryCreditDelegation( | ||
| address owner, | ||
| address spender, | ||
| uint256 reserveId | ||
| ) external view returns (uint256) { | ||
| return _temporaryCreditDelegationsSlot(owner, spender, reserveId).tload(); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.