Skip to content

Commit 2e47a93

Browse files
authored
fix: makes the position managers spoke-agnostic (#872)
1 parent 7f949d1 commit 2e47a93

23 files changed

+1329
-640
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"borrowNative": "226558",
3+
"repayNative": "241518",
4+
"supplyAsCollateralNative": "155867",
5+
"supplyNative": "132174",
6+
"withdrawNative: full": "130004",
7+
"withdrawNative: partial": "142305"
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"borrowWithSig": "214862",
3+
"repayWithSig": "242717",
4+
"setSelfAsUserPositionManagerWithSig": "92890",
5+
"setUsingAsCollateralWithSig": "58839",
6+
"supplyWithSig": "148895",
7+
"updateUserDynamicConfigWithSig": "63202",
8+
"updateUserRiskPremiumWithSig": "61971",
9+
"withdrawWithSig": "136110"
10+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
// Copyright (c) 2025 Aave Labs
3+
pragma solidity 0.8.28;
4+
5+
import {Ownable2Step, Ownable} from 'src/dependencies/openzeppelin/Ownable2Step.sol';
6+
import {Rescuable} from 'src/utils/Rescuable.sol';
7+
import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
8+
import {IGatewayBase} from 'src/position-manager/interfaces/IGatewayBase.sol';
9+
10+
/// @title GatewayBase
11+
/// @author Aave Labs
12+
/// @notice Base implementation for gateway common functionalities.
13+
abstract contract GatewayBase is IGatewayBase, Rescuable, Ownable2Step {
14+
mapping(address => bool) internal _registeredSpokes;
15+
16+
/// @notice Modifier that checks if the specified spoke is registered.
17+
modifier onlyRegisteredSpoke(address spoke) {
18+
_isSpokeValid(spoke);
19+
_;
20+
}
21+
22+
/// @dev Constructor.
23+
/// @param initialOwner_ The address of the initial owner.
24+
constructor(address initialOwner_) Ownable(initialOwner_) {}
25+
26+
/// @inheritdoc IGatewayBase
27+
function registerSpoke(address spoke, bool active) external onlyOwner {
28+
require(spoke != address(0), InvalidAddress());
29+
_registeredSpokes[spoke] = active;
30+
emit SpokeRegistered(spoke, active);
31+
}
32+
33+
/// @inheritdoc IGatewayBase
34+
function renouncePositionManagerRole(address spoke, address user) external onlyOwner {
35+
require(user != address(0), InvalidAddress());
36+
ISpoke(spoke).renouncePositionManagerRole(user);
37+
}
38+
39+
/// @inheritdoc IGatewayBase
40+
function isSpokeRegistered(address spoke) external view returns (bool) {
41+
return _registeredSpokes[spoke];
42+
}
43+
44+
/// @dev Verifies the specified spoke is registered.
45+
function _isSpokeValid(address spoke) internal view {
46+
require(_registeredSpokes[spoke], SpokeNotRegistered());
47+
}
48+
49+
/// @return The underlying asset for `reserveId` on the specified spoke.
50+
/// @return The corresponding hub address.
51+
function _getReserveData(
52+
address spoke,
53+
uint256 reserveId
54+
) internal view returns (address, address) {
55+
ISpoke.Reserve memory reserveData = ISpoke(spoke).getReserve(reserveId);
56+
return (reserveData.underlying, address(reserveData.hub));
57+
}
58+
59+
/// @dev The `owner()` is the allowed caller for Rescuable methods.
60+
function _rescueGuardian() internal view override returns (address) {
61+
return owner();
62+
}
63+
}

src/position-manager/NativeTokenGateway.sol

Lines changed: 58 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,29 @@
33
pragma solidity 0.8.28;
44

55
import {ReentrancyGuardTransient} from 'src/dependencies/openzeppelin/ReentrancyGuardTransient.sol';
6-
import {Ownable2Step, Ownable} from 'src/dependencies/openzeppelin/Ownable2Step.sol';
76
import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
87
import {Address} from 'src/dependencies/openzeppelin/Address.sol';
98
import {MathUtils} from 'src/libraries/math/MathUtils.sol';
10-
import {Rescuable} from 'src/utils/Rescuable.sol';
119
import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
1210
import {INativeWrapper} from 'src/position-manager/interfaces/INativeWrapper.sol';
11+
import {GatewayBase} from 'src/position-manager/GatewayBase.sol';
1312
import {INativeTokenGateway} from 'src/position-manager/interfaces/INativeTokenGateway.sol';
1413

1514
/// @title NativeTokenGateway
1615
/// @author Aave Labs
1716
/// @notice Gateway to interact with a spoke using the native coin of a chain.
1817
/// @dev Contract must be an active & approved user position manager in order to execute spoke actions on a user's behalf.
19-
contract NativeTokenGateway is
20-
INativeTokenGateway,
21-
ReentrancyGuardTransient,
22-
Rescuable,
23-
Ownable2Step
24-
{
18+
contract NativeTokenGateway is INativeTokenGateway, GatewayBase, ReentrancyGuardTransient {
2519
using SafeERC20 for *;
2620

2721
INativeWrapper internal immutable _nativeWrapper;
28-
ISpoke internal immutable _spoke;
2922

3023
/// @dev Constructor.
3124
/// @param nativeWrapper_ The address of the native wrapper contract.
32-
/// @param spoke_ The address of the connected spoke.
3325
/// @param initialOwner_ The address of the initial owner.
34-
constructor(
35-
address nativeWrapper_,
36-
address spoke_,
37-
address initialOwner_
38-
) Ownable(initialOwner_) {
39-
require(nativeWrapper_ != address(0) && spoke_ != address(0), InvalidAddress());
26+
constructor(address nativeWrapper_, address initialOwner_) GatewayBase(initialOwner_) {
27+
require(nativeWrapper_ != address(0), InvalidAddress());
4028
_nativeWrapper = INativeWrapper(payable(nativeWrapper_));
41-
_spoke = ISpoke(spoke_);
4229
}
4330

4431
/// @dev Checks only 'nativeWrapper' can transfer native tokens.
@@ -52,95 +39,103 @@ contract NativeTokenGateway is
5239
}
5340

5441
/// @inheritdoc INativeTokenGateway
55-
function supplyNative(uint256 reserveId, uint256 amount) external payable nonReentrant {
56-
(IERC20 underlying, address hub) = _getReserveData(reserveId);
57-
_validateParams(underlying, amount);
42+
function supplyNative(
43+
address spoke,
44+
uint256 reserveId,
45+
uint256 amount
46+
) external payable nonReentrant onlyRegisteredSpoke(spoke) {
5847
require(msg.value == amount, NativeAmountMismatch());
48+
_supplyNative(spoke, reserveId, msg.sender, amount);
49+
}
5950

60-
_nativeWrapper.deposit{value: amount}();
61-
_nativeWrapper.forceApprove(hub, amount);
62-
_spoke.supply(reserveId, amount, msg.sender);
51+
/// @inheritdoc INativeTokenGateway
52+
function supplyAsCollateralNative(
53+
address spoke,
54+
uint256 reserveId,
55+
uint256 amount
56+
) external payable nonReentrant onlyRegisteredSpoke(spoke) {
57+
require(msg.value == amount, NativeAmountMismatch());
58+
_supplyNative(spoke, reserveId, msg.sender, amount);
59+
ISpoke(spoke).setUsingAsCollateral(reserveId, true, msg.sender);
6360
}
6461

6562
/// @inheritdoc INativeTokenGateway
66-
function withdrawNative(uint256 reserveId, uint256 amount, address receiver) external {
67-
(IERC20 underlying, ) = _getReserveData(reserveId);
63+
function withdrawNative(
64+
address spoke,
65+
uint256 reserveId,
66+
uint256 amount
67+
) external onlyRegisteredSpoke(spoke) {
68+
(address underlying, ) = _getReserveData(spoke, reserveId);
6869
_validateParams(underlying, amount);
69-
require(receiver != address(0), InvalidAddress());
7070

7171
uint256 withdrawAmount = MathUtils.min(
7272
amount,
73-
_spoke.getUserSuppliedAssets(reserveId, msg.sender)
73+
ISpoke(spoke).getUserSuppliedAssets(reserveId, msg.sender)
7474
);
7575

76-
_spoke.withdraw(reserveId, withdrawAmount, msg.sender);
76+
ISpoke(spoke).withdraw(reserveId, withdrawAmount, msg.sender);
7777
_nativeWrapper.withdraw(withdrawAmount);
78-
Address.sendValue(payable(receiver), withdrawAmount);
78+
Address.sendValue(payable(msg.sender), withdrawAmount);
7979
}
8080

8181
/// @inheritdoc INativeTokenGateway
82-
function borrowNative(uint256 reserveId, uint256 amount, address receiver) external {
83-
(IERC20 underlying, ) = _getReserveData(reserveId);
82+
function borrowNative(
83+
address spoke,
84+
uint256 reserveId,
85+
uint256 amount
86+
) external onlyRegisteredSpoke(spoke) {
87+
(address underlying, ) = _getReserveData(spoke, reserveId);
8488
_validateParams(underlying, amount);
85-
require(receiver != address(0), InvalidAddress());
8689

87-
_spoke.borrow(reserveId, amount, msg.sender);
90+
ISpoke(spoke).borrow(reserveId, amount, msg.sender);
8891
_nativeWrapper.withdraw(amount);
89-
Address.sendValue(payable(receiver), amount);
92+
Address.sendValue(payable(msg.sender), amount);
9093
}
9194

9295
/// @inheritdoc INativeTokenGateway
93-
function repayNative(uint256 reserveId, uint256 amount) external payable nonReentrant {
94-
(IERC20 underlying, address hub) = _getReserveData(reserveId);
95-
_validateParams(underlying, amount);
96+
function repayNative(
97+
address spoke,
98+
uint256 reserveId,
99+
uint256 amount
100+
) external payable nonReentrant onlyRegisteredSpoke(spoke) {
96101
require(msg.value == amount, NativeAmountMismatch());
102+
(address underlying, address hub) = _getReserveData(spoke, reserveId);
103+
_validateParams(underlying, amount);
97104

98-
uint256 userDebtAmount = _spoke.getUserTotalDebt(reserveId, msg.sender);
105+
uint256 userTotalDebt = ISpoke(spoke).getUserTotalDebt(reserveId, msg.sender);
99106
uint256 repayAmount = amount;
100107
uint256 leftovers;
101-
if (amount > userDebtAmount) {
102-
leftovers = amount - userDebtAmount;
103-
repayAmount = userDebtAmount;
108+
if (amount > userTotalDebt) {
109+
leftovers = amount - userTotalDebt;
110+
repayAmount = userTotalDebt;
104111
}
105112

106113
_nativeWrapper.deposit{value: repayAmount}();
107114
_nativeWrapper.forceApprove(hub, repayAmount);
108-
_spoke.repay(reserveId, repayAmount, msg.sender);
115+
ISpoke(spoke).repay(reserveId, repayAmount, msg.sender);
109116

110117
if (leftovers > 0) {
111118
Address.sendValue(payable(msg.sender), leftovers);
112119
}
113120
}
114121

115-
/// @inheritdoc INativeTokenGateway
116-
function renouncePositionManagerRole(address user) external onlyOwner {
117-
_spoke.renouncePositionManagerRole(user);
118-
}
119-
120122
/// @inheritdoc INativeTokenGateway
121123
function NATIVE_WRAPPER() external view returns (address) {
122124
return address(_nativeWrapper);
123125
}
124126

125-
/// @inheritdoc INativeTokenGateway
126-
function SPOKE() external view returns (address) {
127-
return address(_spoke);
128-
}
127+
/// @dev `msg.value` verification must be done before calling this.
128+
function _supplyNative(address spoke, uint256 reserveId, address user, uint256 amount) internal {
129+
(address underlying, address hub) = _getReserveData(spoke, reserveId);
130+
_validateParams(underlying, amount);
129131

130-
/// @dev RescueGuardian is the owner of the contract.
131-
function _rescueGuardian() internal view override returns (address) {
132-
return owner();
132+
_nativeWrapper.deposit{value: amount}();
133+
_nativeWrapper.forceApprove(hub, amount);
134+
ISpoke(spoke).supply(reserveId, amount, user);
133135
}
134136

135-
function _validateParams(IERC20 underlying, uint256 amount) internal view {
136-
require(address(underlying) == address(_nativeWrapper), NotNativeWrappedAsset());
137+
function _validateParams(address underlying, uint256 amount) internal view {
138+
require(address(_nativeWrapper) == underlying, NotNativeWrappedAsset());
137139
require(amount > 0, InvalidAmount());
138140
}
139-
140-
/// @return The underlying asset for `reserveId` on connected spoke.
141-
/// @return The corresponding hub address.
142-
function _getReserveData(uint256 reserveId) internal view returns (IERC20, address) {
143-
ISpoke.Reserve memory reserveData = _spoke.getReserve(reserveId);
144-
return (IERC20(reserveData.underlying), address(reserveData.hub));
145-
}
146141
}

0 commit comments

Comments
 (0)