Position Manager Flow¶
End-to-end execution flow for managing position manager approvals and performing actions on behalf of users in Aave V3.
Quick Reference¶
Aspect |
Details |
|---|---|
Entry Points |
|
Key Transformations |
None - direct storage updates |
State Changes |
|
Events Emitted |
|
Flow Diagram¶
flowchart TD
%% Styling definitions
classDef validation fill:#ffcccc,stroke:#ff0000,stroke-width:2px
classDef transformation fill:#ccffcc,stroke:#00aa00,stroke-width:2px
classDef storage fill:#ccccff,stroke:#0000ff,stroke-width:2px
classDef event fill:#ffffcc,stroke:#aaaa00,stroke-width:2px
classDef error fill:#ff0000,stroke:#000000,color:#fff
subgraph ApprovalFlow ["1. Approve Position Manager Flow"]
direction TB
Entry1["Pool.approvePositionManager<br/>positionManager, approve"] --> CheckState1["VALIDATION<br/>Check current approval state"]
class CheckState1 validation
CheckState1 -->|Already same state| Skip1[Return early]
CheckState1 -->|State change needed| Store1["STORAGE UPDATE<br/>_positionManager<br/>[msg.sender][positionManager]<br/>= approve"]
class Store1 storage
Store1 --> Branch1{approve?}
Branch1 -->|Yes| EventApproved["EMIT<br/>PositionManagerApproved"]
class EventApproved event
Branch1 -->|No| EventRevoked["EMIT<br/>PositionManagerRevoked"]
class EventRevoked event
end
subgraph RenounceFlow ["2. Renounce Position Manager Role Flow"]
direction TB
Entry2["Pool.renouncePositionManagerRole<br/>user"] --> CheckState2["VALIDATION<br/>Check if caller is approved<br/>as position manager"]
class CheckState2 validation
CheckState2 -->|Not approved| Skip2[Return early]
CheckState2 -->|Is approved| Store2["STORAGE UPDATE<br/>_positionManager<br/>[user][msg.sender]<br/>= false"]
class Store2 storage
Store2 --> EventRevoked2["EMIT<br/>PositionManagerRevoked"]
class EventRevoked2 event
end
subgraph CollateralFlow ["3. Set Collateral On Behalf Flow"]
direction TB
Entry3["Pool.setUserUseReserveAsCollateral<br/>OnBehalfOf<br/>asset, useAsCollateral,<br/>onBehalfOf"] --> ValidateManager["VALIDATION<br/>onlyPositionManager<br/>modifier check"]
class ValidateManager validation
ValidateManager -->|Revert| Error1["ERROR<br/>CallerNotPositionManager"]
class Error1 error
ValidateManager -->|Pass| ExecuteCollateral["SupplyLogic<br/>executeUseReserveAsCollateral"]
ExecuteCollateral --> ValidateCollateral["ValidationLogic<br/>validateUseAsCollateral"]
class ValidateCollateral validation
ValidateCollateral --> UpdateConfig["UserConfig<br/>setUsingAsCollateral"]
class UpdateConfig storage
UpdateConfig --> CollateralEvent["EMIT<br/>ReserveUsedAsCollateral<br/>Enabled/Disabled"]
class CollateralEvent event
end
subgraph EModeFlow ["4. Set E-Mode On Behalf Flow"]
direction TB
Entry4["Pool.setUserEModeOnBehalfOf<br/>categoryId, onBehalfOf"] --> ValidateManager2["VALIDATION<br/>onlyPositionManager<br/>modifier check"]
class ValidateManager2 validation
ValidateManager2 -->|Revert| Error2["ERROR<br/>CallerNotPositionManager"]
class Error2 error
ValidateManager2 -->|Pass| ExecuteEMode["SupplyLogic<br/>executeSetUserEMode"]
ExecuteEMode --> ValidateEMode["ValidationLogic<br/>validateEModeCategory"]
class ValidateEMode validation
ValidateEMode --> UpdateEMode["STORAGE UPDATE<br/>_usersEModeCategory<br/>[onBehalfOf] = categoryId"]
class UpdateEMode storage
UpdateEMode --> EModeEvent["EMIT<br/>UserEModeSet"]
class EModeEvent event
end
Step-by-Step Execution¶
1. Approve Position Manager¶
File: contracts/protocol/pool/Pool.sol
function approvePositionManager(address positionManager, bool approve) external override {
// Skip if already in desired state (gas optimization)
if (_positionManager[_msgSender()][positionManager] == approve) return;
// Update storage
_positionManager[_msgSender()][positionManager] = approve;
// Emit appropriate event
if (approve) {
emit PositionManagerApproved({user: _msgSender(), positionManager: positionManager});
} else {
emit PositionManagerRevoked({user: _msgSender(), positionManager: positionManager});
}
}
Key Points:
User (msg.sender) approves a position manager to act on their behalf
State check prevents redundant storage writes
Emits different events based on approval/revocation
2. Renounce Position Manager Role¶
File: contracts/protocol/pool/Pool.sol
function renouncePositionManagerRole(address user) external override {
// Only proceed if caller is actually an approved position manager
if (_positionManager[user][_msgSender()] == false) return;
// Revoke approval
_positionManager[user][_msgSender()] = false;
emit PositionManagerRevoked({user: user, positionManager: _msgSender()});
}
Key Points:
Position manager can voluntarily renounce their role
Returns early if caller is not actually approved
Emits PositionManagerRevoked event
3. Access Control Modifier¶
File: contracts/protocol/pool/Pool.sol
/**
* @dev Only an approved position manager can call functions marked by this modifier.
*/
modifier onlyPositionManager(address onBehalfOf) {
_onlyPositionManager(onBehalfOf);
_;
}
function _onlyPositionManager(address onBehalfOf) internal view virtual {
require(_positionManager[onBehalfOf][_msgSender()], Errors.CallerNotPositionManager());
}
Key Points:
Checks if msg.sender is approved as position manager for
onBehalfOfuserReverts with
CallerNotPositionManagerif not authorizedUsed by both
setUserUseReserveAsCollateralOnBehalfOfandsetUserEModeOnBehalfOf
4. Set Collateral On Behalf Of User¶
File: contracts/protocol/pool/Pool.sol
function setUserUseReserveAsCollateralOnBehalfOf(
address asset,
bool useAsCollateral,
address onBehalfOf
) external override onlyPositionManager(onBehalfOf) {
SupplyLogic.executeUseReserveAsCollateral(
_reserves,
_reservesList,
_eModeCategories,
_usersConfig[onBehalfOf],
onBehalfOf,
asset,
useAsCollateral,
ADDRESSES_PROVIDER.getPriceOracle(),
_usersEModeCategory[onBehalfOf]
);
}
File: contracts/protocol/libraries/logic/SupplyLogic.sol
function executeUseReserveAsCollateral(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.UserConfigurationMap storage userConfig,
address user,
address asset,
bool useAsCollateral,
address priceOracle,
uint8 userEModeCategory
) external {
DataTypes.ReserveData storage reserve = reserves[asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
// Update state
reserve.updateState(reserveCache);
// Validate
ValidationLogic.validateUseAsCollateral(
reserves,
reservesList,
reserveCache,
userConfig,
asset,
user,
useAsCollateral,
priceOracle,
userEModeCategory
);
// Update user configuration
userConfig.setUsingAsCollateral(reserve.id, useAsCollateral);
// Emit appropriate event
if (useAsCollateral) {
emit ReserveUsedAsCollateralEnabled(asset, user);
} else {
emit ReserveUsedAsCollateralDisabled(asset, user);
}
}
Key Points:
Position manager can enable/disable collateral usage for user
Requires
onlyPositionManagermodifier - validates access controlPerforms same validations as direct user call
Emits collateral events
5. Set E-Mode On Behalf Of User¶
File: contracts/protocol/pool/Pool.sol
function setUserEModeOnBehalfOf(
uint8 categoryId,
address onBehalfOf
) external override onlyPositionManager(onBehalfOf) {
SupplyLogic.executeSetUserEMode(
_reserves,
_reservesList,
_eModeCategories,
_usersEModeCategory,
_usersConfig[onBehalfOf],
onBehalfOf,
ADDRESSES_PROVIDER.getPriceOracle(),
categoryId
);
}
File: contracts/protocol/libraries/logic/SupplyLogic.sol
function executeSetUserEMode(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
mapping(address => uint8) storage usersEModeCategory,
DataTypes.UserConfigurationMap storage userConfig,
address user,
address priceOracle,
uint8 categoryId
) external {
// Validation
ValidationLogic.validateEModeCategory(
reserves,
reservesList,
userConfig,
eModeCategories,
categoryId,
user,
priceOracle
);
// Update user's eMode category
usersEModeCategory[user] = categoryId;
emit UserEModeSet(user, categoryId);
}
Key Points:
Position manager can set user’s eMode category
Validates that user can enter specified eMode category
Updates storage and emits UserEModeSet event
6. Check Approval Status¶
File: contracts/protocol/pool/Pool.sol
function isApprovedPositionManager(
address user,
address positionManager
) external view override returns (bool) {
return _positionManager[user][positionManager];
}
Storage Layout¶
// PoolStorage.sol
// Allowlisted permissionManagers can enable collaterals & switch eModes on behalf of a user
mapping(address user => mapping(address permittedPositionManager => bool))
internal _positionManager;
Key Points:
Double mapping: user → position manager → bool
Position manager must be explicitly approved by user
Approval is required before any “onBehalfOf” actions
Event Details¶
PositionManagerApproved Event¶
Emitted when a user approves a position manager.
event PositionManagerApproved(
address indexed user, // User granting approval
address indexed positionManager // Address being approved
);
PositionManagerRevoked Event¶
Emitted when position manager approval is revoked (either by user or by renouncement).
event PositionManagerRevoked(
address indexed user, // User whose approval is revoked
address indexed positionManager // Address being revoked
);
ReserveUsedAsCollateralEnabled Event¶
Emitted when position manager enables collateral for user.
event ReserveUsedAsCollateralEnabled(
address indexed reserve,
address indexed user
);
ReserveUsedAsCollateralDisabled Event¶
Emitted when position manager disables collateral for user.
event ReserveUsedAsCollateralDisabled(
address indexed reserve,
address indexed user
);
UserEModeSet Event¶
Emitted when position manager sets eMode category for user.
event UserEModeSet(
address indexed user,
uint8 categoryId
);
Error Conditions¶
Error |
Condition |
File |
|---|---|---|
|
|
Pool.sol |
|
Disabling collateral would put user’s health factor below liquidation threshold |
ValidationLogic.sol |
|
Attempting to use category 0 (reserved) |
Pool.sol |
|
User has collateral that cannot be used in the requested eMode category |
ValidationLogic.sol |
Source File Locations¶
contracts/protocol/pool/Pool.sol
contracts/protocol/pool/PoolStorage.sol
contracts/protocol/libraries/logic/SupplyLogic.sol
contracts/protocol/libraries/logic/ValidationLogic.sol
contracts/protocol/libraries/helpers/Errors.sol
Security Considerations¶
Approval Required: Position managers must be explicitly approved before they can act on behalf of a user
Revocation: Users can revoke approval at any time via
approvePositionManager(manager, false)Self-Renouncement: Position managers can voluntarily renounce their role via
renouncePositionManagerRole(user)Limited Actions: Position managers can ONLY:
Enable/disable user’s collateral (
setUserUseReserveAsCollateralOnBehalfOf)Set user’s eMode category (
setUserEModeOnBehalfOf)
They CANNOT:
Supply on behalf of user
Withdraw on behalf of user
Borrow on behalf of user
Repay on behalf of user
Liquidate positions
Validation Still Applied: All actions performed by position managers go through the same validation logic as direct user actions
Gas Optimization:
approvePositionManagerreturns early if state is already correct