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
12 changes: 6 additions & 6 deletions snapshots/NativeTokenGateway.Operations.json
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": "229560",
"repayNative": "168311",
"supplyAsCollateralNative": "160307",
"supplyNative": "136441",
"withdrawNative: full": "125585",
"withdrawNative: partial": "136781"
}
16 changes: 8 additions & 8 deletions snapshots/SignatureGateway.Operations.json
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"
"borrowWithSig": "215849",
"repayWithSig": "189159",
"setSelfAsUserPositionManagerWithSig": "82643",
"setUsingAsCollateralWithSig": "85031",
"supplyWithSig": "153170",
"updateUserDynamicConfigWithSig": "62857",
"updateUserRiskPremiumWithSig": "61557",
"withdrawWithSig": "131678"
}
54 changes: 27 additions & 27 deletions snapshots/Spoke.Operations.ZeroRiskPremium.json
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
{
"borrow: first": "191325",
"borrow: second action, same reserve": "171297",
"borrow: first": "191303",
"borrow: second action, same reserve": "171275",
"liquidationCall (receiveShares): full": "300391",
"liquidationCall (receiveShares): partial": "300109",
"liquidationCall: full": "310756",
"liquidationCall: partial": "310474",
"permitReserve + repay (multicall)": "166317",
"permitReserve + supply (multicall)": "146862",
"permitReserve + supply + enable collateral (multicall)": "160573",
"repay: full": "126382",
"repay: partial": "131271",
"setUserPositionManagerWithSig: disable": "44846",
"setUserPositionManagerWithSig: enable": "68875",
"supply + enable collateral (multicall)": "140624",
"supply: 0 borrows, collateral disabled": "123679",
"supply: 0 borrows, collateral enabled": "106601",
"supply: second action, same reserve": "106579",
"updateUserDynamicConfig: 1 collateral": "73694",
"updateUserDynamicConfig: 2 collaterals": "88551",
"updateUserRiskPremium: 1 borrow": "94804",
"updateUserRiskPremium: 2 borrows": "104619",
"usingAsCollateral: 0 borrows, enable": "58915",
"usingAsCollateral: 1 borrow, disable": "105072",
"usingAsCollateral: 1 borrow, enable": "41803",
"usingAsCollateral: 2 borrows, disable": "126055",
"usingAsCollateral: 2 borrows, enable": "41815",
"withdraw: 0 borrows, full": "128910",
"withdraw: 0 borrows, partial": "133473",
"withdraw: 1 borrow, partial": "161036",
"withdraw: 2 borrows, partial": "174214",
"withdraw: non collateral": "106544"
"permitReserve + repay (multicall)": "166294",
"permitReserve + supply (multicall)": "146774",
"permitReserve + supply + enable collateral (multicall)": "160463",
"repay: full": "126425",
"repay: partial": "131314",
"setUserPositionManagerWithSig: disable": "52333",
"setUserPositionManagerWithSig: enable": "76362",
"supply + enable collateral (multicall)": "140536",
"supply: 0 borrows, collateral disabled": "123657",
"supply: 0 borrows, collateral enabled": "106579",
"supply: second action, same reserve": "106557",
"updateUserDynamicConfig: 1 collateral": "73782",
"updateUserDynamicConfig: 2 collaterals": "88639",
"updateUserRiskPremium: 1 borrow": "94782",
"updateUserRiskPremium: 2 borrows": "104597",
"usingAsCollateral: 0 borrows, enable": "58893",
"usingAsCollateral: 1 borrow, disable": "105050",
"usingAsCollateral: 1 borrow, enable": "41781",
"usingAsCollateral: 2 borrows, disable": "126033",
"usingAsCollateral: 2 borrows, enable": "41793",
"withdraw: 0 borrows, full": "128888",
"withdraw: 0 borrows, partial": "133451",
"withdraw: 1 borrow, partial": "161014",
"withdraw: 2 borrows, partial": "174192",
"withdraw: non collateral": "106522"
}
54 changes: 27 additions & 27 deletions snapshots/Spoke.Operations.json
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
{
"borrow: first": "262009",
"borrow: second action, same reserve": "204981",
"borrow: first": "261987",
"borrow: second action, same reserve": "204959",
"liquidationCall (receiveShares): full": "334242",
"liquidationCall (receiveShares): partial": "333960",
"liquidationCall: full": "344607",
"liquidationCall: partial": "344325",
"permitReserve + repay (multicall)": "163504",
"permitReserve + supply (multicall)": "146862",
"permitReserve + supply + enable collateral (multicall)": "160573",
"repay: full": "120544",
"repay: partial": "139833",
"setUserPositionManagerWithSig: disable": "44846",
"setUserPositionManagerWithSig: enable": "68875",
"supply + enable collateral (multicall)": "140624",
"supply: 0 borrows, collateral disabled": "123679",
"supply: 0 borrows, collateral enabled": "106601",
"supply: second action, same reserve": "106579",
"updateUserDynamicConfig: 1 collateral": "73694",
"updateUserDynamicConfig: 2 collaterals": "88551",
"updateUserRiskPremium: 1 borrow": "151368",
"updateUserRiskPremium: 2 borrows": "204852",
"usingAsCollateral: 0 borrows, enable": "58915",
"usingAsCollateral: 1 borrow, disable": "161636",
"usingAsCollateral: 1 borrow, enable": "41803",
"usingAsCollateral: 2 borrows, disable": "234288",
"usingAsCollateral: 2 borrows, enable": "41815",
"withdraw: 0 borrows, full": "128910",
"withdraw: 0 borrows, partial": "133473",
"withdraw: 1 borrow, partial": "215098",
"withdraw: 2 borrows, partial": "259848",
"withdraw: non collateral": "106544"
"permitReserve + repay (multicall)": "163485",
"permitReserve + supply (multicall)": "146774",
"permitReserve + supply + enable collateral (multicall)": "160463",
"repay: full": "120587",
"repay: partial": "139876",
"setUserPositionManagerWithSig: disable": "52333",
"setUserPositionManagerWithSig: enable": "76362",
"supply + enable collateral (multicall)": "140536",
"supply: 0 borrows, collateral disabled": "123657",
"supply: 0 borrows, collateral enabled": "106579",
"supply: second action, same reserve": "106557",
"updateUserDynamicConfig: 1 collateral": "73782",
"updateUserDynamicConfig: 2 collaterals": "88639",
"updateUserRiskPremium: 1 borrow": "151346",
"updateUserRiskPremium: 2 borrows": "204830",
"usingAsCollateral: 0 borrows, enable": "58893",
"usingAsCollateral: 1 borrow, disable": "161614",
"usingAsCollateral: 1 borrow, enable": "41781",
"usingAsCollateral: 2 borrows, disable": "234266",
"usingAsCollateral: 2 borrows, enable": "41793",
"withdraw: 0 borrows, full": "128888",
"withdraw: 0 borrows, partial": "133451",
"withdraw: 1 borrow, partial": "215076",
"withdraw: 2 borrows, partial": "259826",
"withdraw: non collateral": "106522"
}
23 changes: 23 additions & 0 deletions src/libraries/misc/SignatureChecker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2025 Aave Labs
pragma solidity ^0.8.0;

import {SignatureChecker as OpenZeppelinSignatureChecker} from 'src/dependencies/openzeppelin/SignatureChecker.sol';

/// @title SignatureChecker
/// @author Aave Labs
library SignatureChecker {
/// @notice Checks if a signature is valid for a given signer and data hash.
/// @dev External wrapper around OpenZeppelin's SignatureChecker.isValidSignatureNow to reduce code size at the expense of an external delegatecall.
/// @param signer The address of the signer.
/// @param hash The hash of the data to be signed.
/// @param signature The signature bytes.
/// @return True if the signature is valid, false otherwise.
function isValidSignatureNow(
address signer,
bytes32 hash,
bytes memory signature
) external view returns (bool) {
return OpenZeppelinSignatureChecker.isValidSignatureNow(signer, hash, signature);
}
}
8 changes: 6 additions & 2 deletions src/libraries/types/EIP712Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ pragma solidity ^0.8.20;
/// @notice Defines type structs used in EIP712-typed signatures.
library EIP712Types {
struct SetUserPositionManager {
address positionManager;
address user;
bool approve;
PositionManagerUpdate[] updates;
uint256 nonce;
uint256 deadline;
}

struct PositionManagerUpdate {
address positionManager;
bool approve;
}

struct Permit {
address owner;
address spender;
Expand Down
22 changes: 14 additions & 8 deletions src/position-manager/SignatureGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,21 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic
uint256 deadline,
bytes calldata signature
) external onlyRegisteredSpoke(spoke) {
EIP712Types.PositionManagerUpdate[] memory updates = new EIP712Types.PositionManagerUpdate[](1);
updates[0] = EIP712Types.PositionManagerUpdate({
positionManager: address(this),
approve: approve
});
try
ISpoke(spoke).setUserPositionManagerWithSig({
positionManager: address(this),
user: user,
approve: approve,
nonce: nonce,
deadline: deadline,
signature: signature
})
ISpoke(spoke).setUserPositionManagerWithSig(
EIP712Types.SetUserPositionManager({
updates: updates,
user: user,
nonce: nonce,
deadline: deadline
}),
signature
)
{} catch {}
}

Expand Down
33 changes: 33 additions & 0 deletions src/position-manager/libraries/EIP712Hash.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {EIP712Types} from 'src/libraries/types/EIP712Types.sol';
/// @author Aave Labs
/// @notice Helper methods to hash EIP712 typed data structs.
library EIP712Hash {
using EIP712Hash for *;

bytes32 public constant SUPPLY_TYPEHASH =
// keccak256('Supply(address spoke,uint256 reserveId,uint256 amount,address onBehalfOf,uint256 nonce,uint256 deadline)')
0xe85497eb293c001e8483fe105efadd1d50aa0dadfc0570b27058031dfceab2e6;
Expand Down Expand Up @@ -36,6 +38,14 @@ library EIP712Hash {
// keccak256('UpdateUserDynamicConfig(address spoke,address user,uint256 nonce,uint256 deadline)')
0xba177b1f5b5e1e709f62c19f03c97988c57752ba561de58f383ebee4e8d0a71c;

bytes32 public constant SET_USER_POSITION_MANAGER_TYPEHASH =
// keccak256('SetUserPositionManager(address user,PositionManagerUpdate[] updates,uint256 nonce,uint256 deadline)PositionManagerUpdate(address positionManager,bool approve)')
0x585e1e37b666d270ee2f5249e16d075b3790ba51e019b5c949396d40af4cb092;

bytes32 public constant POSITION_MANAGER_UPDATE =
// keccak256('PositionManagerUpdate(address positionManager,bool approve)')
0x187dbd227227274b90655fb4011fc21dd749e8966fc040bd91e0b92609202565;

function hash(EIP712Types.Supply calldata params) internal pure returns (bytes32) {
return
keccak256(
Expand Down Expand Up @@ -138,4 +148,27 @@ library EIP712Hash {
)
);
}

function hash(
EIP712Types.SetUserPositionManager calldata params
) internal pure returns (bytes32) {
bytes32[] memory updatesHashes = new bytes32[](params.updates.length);
for (uint256 i = 0; i < updatesHashes.length; ++i) {
updatesHashes[i] = params.updates[i].hash();
}
return
keccak256(
abi.encode(
SET_USER_POSITION_MANAGER_TYPEHASH,
params.user,
keccak256(abi.encodePacked(updatesHashes)),
params.nonce,
params.deadline
)
);
}

function hash(EIP712Types.PositionManagerUpdate calldata params) internal pure returns (bytes32) {
return keccak256(abi.encode(POSITION_MANAGER_UPDATE, params.positionManager, params.approve));
}
}
45 changes: 18 additions & 27 deletions src/spoke/Spoke.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ pragma solidity 0.8.28;
import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
import {IERC20Permit} from 'src/dependencies/openzeppelin/IERC20Permit.sol';
import {SignatureChecker} from 'src/dependencies/openzeppelin/SignatureChecker.sol';
import {SignatureChecker} from 'src/libraries/misc/SignatureChecker.sol';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move with the other lib imports

import {AccessManagedUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/AccessManagedUpgradeable.sol';
import {EIP712} from 'src/dependencies/solady/EIP712.sol';
import {EIP712Hash, EIP712Types} from 'src/position-manager/libraries/EIP712Hash.sol';
Copy link
Contributor

@Kogaroshi Kogaroshi Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this should be a common lib for all contracts in the repo @miguelmtzinf

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% agree, didn't do it here bc it's done in another PR and wanted to only keep relevant changes

import {MathUtils} from 'src/libraries/math/MathUtils.sol';
import {PercentageMath} from 'src/libraries/math/PercentageMath.sol';
import {WadRayMath} from 'src/libraries/math/WadRayMath.sol';
Expand Down Expand Up @@ -37,11 +38,7 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
using PositionStatusMap for *;
using ReserveFlagsMap for ReserveFlags;
using UserPositionDebt for ISpoke.UserPosition;

/// @inheritdoc ISpoke
bytes32 public constant SET_USER_POSITION_MANAGER_TYPEHASH =
// keccak256('SetUserPositionManager(address positionManager,address user,bool approve,uint256 nonce,uint256 deadline)')
0x758d23a3c07218b7ea0b4f7f63903c4e9d5cbde72d3bcfe3e9896639025a0214;
using EIP712Hash for EIP712Types.SetUserPositionManager;

/// @inheritdoc ISpoke
address public immutable ORACLE;
Expand Down Expand Up @@ -448,29 +445,23 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea

/// @inheritdoc ISpoke
function setUserPositionManagerWithSig(
address positionManager,
address user,
bool approve,
uint256 nonce,
uint256 deadline,
EIP712Types.SetUserPositionManager calldata params,
bytes calldata signature
) external {
require(block.timestamp <= deadline, InvalidSignature());
bytes32 digest = _hashTypedData(
keccak256(
abi.encode(
SET_USER_POSITION_MANAGER_TYPEHASH,
positionManager,
user,
approve,
nonce,
deadline
)
)
bytes32 digest = _hashTypedData(params.hash());
require(
block.timestamp <= params.deadline &&
SignatureChecker.isValidSignatureNow(params.user, digest, signature),
InvalidSignature()
);
require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature());
_useCheckedNonce(user, nonce);
_setUserPositionManager({positionManager: positionManager, user: user, approve: approve});
_useCheckedNonce(params.user, params.nonce);
for (uint256 i = 0; i < params.updates.length; ++i) {
_setUserPositionManager({
positionManager: params.updates[i].positionManager,
user: params.user,
approve: params.updates[i].approve
});
}
}

/// @inheritdoc ISpoke
Expand Down Expand Up @@ -693,7 +684,7 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
function _setUserPositionManager(address positionManager, address user, bool approve) internal {
PositionManagerConfig storage config = _positionManager[positionManager];
// only allow approval when position manager is active for improved UX
require(!approve || config.active, InactivePositionManager());
require(!approve || config.active, InactivePositionManager()); // todo rm this ux check given sig batching?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be good to have, we don't want to approve an inactive one via the batch by mistake and be surprised if activated by Governance later

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my concern is that keeping this fails an entire batch bc of one such case

since it is only a UX check, i'm leaning towards removing it
unsure how likely a scenario it is for a user to pre approve a posm which is in line to be (re)approved by gov

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i d keep it, or no-op for ease of integraiton (e.g. we only approve if globally active)

Copy link
Member Author

@DhairyaSethi DhairyaSethi Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either is risky tbh
keeping it - one accidental update destroys batch
no-op - it's not really a no-op due to nonce consumption; user would notice a silent fail

config.approval[user] = approve;
emit SetUserPositionManager(user, positionManager, approve);
}
Expand Down
Loading
Loading