Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ additional_compiler_profiles = [
compilation_restrictions = [
{ paths = "src/hub/instances/HubInstance.sol", via_ir = true, optimizer_runs = 22_300 },
{ paths = "src/spoke/instances/SpokeInstance.sol", via_ir = true, optimizer_runs = 750 },
{ paths = "src/spoke/instances/PermissionedSpokeInstance.sol", via_ir = true, optimizer_runs = 750 },
]

[bind_json]
Expand Down
17 changes: 17 additions & 0 deletions snapshots/PermissionedSpoke.Operations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"borrow: borrow-allowlist policy": "303440",
"borrow: global-manager policy": "297903",
"borrow: position-manager policy": "297779",
"repay: partial, borrow-allowlist policy": "150693",
"repay: partial, global-manager policy": "150550",
"repay: partial, position-manager policy": "150426",
"supply: borrow-allowlist policy": "132302",
"supply: global-manager policy": "132159",
"supply: position-manager policy": "132035",
"usingAsCollateral: enable, borrow-allowlist policy": "64575",
"usingAsCollateral: enable, global-manager policy": "64432",
"usingAsCollateral: enable, position-manager policy": "64308",
"withdraw: partial, borrow-allowlist policy": "185434",
"withdraw: partial, global-manager policy": "185291",
"withdraw: partial, position-manager policy": "185167"
}
43 changes: 43 additions & 0 deletions src/spoke/PermissionedSpoke.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: LicenseRef-BUSL
pragma solidity 0.8.28;

import {Spoke} from 'src/spoke/Spoke.sol';
import {ISpokeGate} from 'src/spoke/interfaces/ISpokeGate.sol';

/// @title PermissionedSpoke
/// @author Aave Labs
/// @notice Spoke where a gate replaces the default position manager authorization on position
/// actions.
abstract contract PermissionedSpoke is Spoke {
/// @notice The gate deciding whether position actions are allowed.
address public immutable GATE;

/// @dev Constructor.
/// @param gate_ The address of the gate.
constructor(address gate_) {
require(gate_ != address(0), InvalidAddress());
GATE = gate_;
}

/// @dev The gate fully decides whether the current call is allowed. The external
/// `isPositionManager` function preserves the underlying position manager semantics.
function _isPositionManager(
address user,
address manager
) internal view virtual override returns (bool) {
return
ISpokeGate(GATE).isCallAllowed({
caller: manager,
onBehalfOf: user,
data: msg.data
});
}

/// @dev Returns the underlying position manager relationship without consulting the gate.
function isPositionManager(
address user,
address positionManager
) external view virtual override returns (bool) {
return Spoke._isPositionManager(user, positionManager);
}
}
10 changes: 8 additions & 2 deletions src/spoke/Spoke.sol
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,10 @@ abstract contract Spoke is
}

/// @inheritdoc ISpoke
function isPositionManager(address user, address positionManager) external view returns (bool) {
function isPositionManager(
address user,
address positionManager
) external view virtual returns (bool) {
return _isPositionManager(user, positionManager);
}

Expand Down Expand Up @@ -906,7 +909,10 @@ abstract contract Spoke is
}

/// @notice Returns whether `manager` is active and approved positionManager for `user`.
function _isPositionManager(address user, address manager) internal view returns (bool) {
function _isPositionManager(
address user,
address manager
) internal view virtual returns (bool) {
if (user == manager) return true;
PositionManagerConfig storage config = _positionManager[manager];
return config.active && config.approval[user];
Expand Down
37 changes: 37 additions & 0 deletions src/spoke/instances/PermissionedSpokeInstance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: LicenseRef-BUSL
pragma solidity 0.8.28;

import {Spoke} from 'src/spoke/Spoke.sol';
import {PermissionedSpoke} from 'src/spoke/PermissionedSpoke.sol';
import {SpokeInstance} from 'src/spoke/instances/SpokeInstance.sol';

/// @title PermissionedSpokeInstance
/// @author Aave Labs
/// @notice Implementation contract for the PermissionedSpoke.
contract PermissionedSpokeInstance is SpokeInstance, PermissionedSpoke {
/// @dev Constructor.
/// @param oracle_ The address of the oracle.
/// @param maxUserReservesLimit_ The maximum number of collateral and borrow reserves a user can have.
/// @param gate_ The address of the gate.
constructor(
address oracle_,
uint16 maxUserReservesLimit_,
address gate_
) SpokeInstance(oracle_, maxUserReservesLimit_) PermissionedSpoke(gate_) {}

/// @dev Resolves the diamond inheritance to the gate authorization of the PermissionedSpoke.
function _isPositionManager(
address user,
address manager
) internal view override(Spoke, PermissionedSpoke) returns (bool) {
return PermissionedSpoke._isPositionManager(user, manager);
}

/// @dev Resolves the diamond inheritance while preserving position manager query semantics.
function isPositionManager(
address user,
address positionManager
) external view override(Spoke, PermissionedSpoke) returns (bool) {
return Spoke._isPositionManager(user, positionManager);
}
}
21 changes: 21 additions & 0 deletions src/spoke/interfaces/ISpokeGate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: LicenseRef-BUSL
pragma solidity ^0.8.0;

/// @title ISpokeGate
/// @author Aave Labs
/// @notice Interface for a gate, which replaces the default position manager authorization on
/// position actions of a permissioned Spoke.
interface ISpokeGate {
/// @notice Returns whether a position action on the Spoke is allowed.
/// @dev Called by the Spoke, so it can preserve the default authorization by calling back
/// `ISpoke(msg.sender).isPositionManager`.
/// @param caller The transaction initiator on the Spoke.
/// @param onBehalfOf The owner of the position being modified.
/// @param data The full calldata of the Spoke call, allowing per-action decoding.
/// @return True if the call is allowed.
function isCallAllowed(
address caller,
address onBehalfOf,
bytes calldata data
) external view returns (bool);
}
Loading
Loading