diff --git a/snapshots/NativeTokenGateway.Operations.json b/snapshots/NativeTokenGateway.Operations.json index 9beadd031..5c31554b6 100644 --- a/snapshots/NativeTokenGateway.Operations.json +++ b/snapshots/NativeTokenGateway.Operations.json @@ -1,6 +1,6 @@ { - "borrowNative": "229604", - "repayNative": "168312", + "borrowNative": "229582", + "repayNative": "168377", "supplyAsCollateralNative": "160373", "supplyNative": "136476", "withdrawNative: full": "125620", diff --git a/snapshots/SignatureGateway.Operations.json b/snapshots/SignatureGateway.Operations.json index 54c084a4d..eeb9bb186 100644 --- a/snapshots/SignatureGateway.Operations.json +++ b/snapshots/SignatureGateway.Operations.json @@ -1,10 +1,10 @@ { - "borrowWithSig": "215893", - "repayWithSig": "189160", - "setSelfAsUserPositionManagerWithSig": "74858", - "setUsingAsCollateralWithSig": "85053", - "supplyWithSig": "153205", - "updateUserDynamicConfigWithSig": "62769", - "updateUserRiskPremiumWithSig": "61579", - "withdrawWithSig": "131713" + "borrowWithSig": "215959", + "repayWithSig": "189313", + "setSelfAsUserPositionManagerWithSig": "76453", + "setUsingAsCollateralWithSig": "85107", + "supplyWithSig": "153248", + "updateUserDynamicConfigWithSig": "62662", + "updateUserRiskPremiumWithSig": "61472", + "withdrawWithSig": "131766" } \ No newline at end of file diff --git a/snapshots/Spoke.Getters.json b/snapshots/Spoke.Getters.json index 000034236..aead49eba 100644 --- a/snapshots/Spoke.Getters.json +++ b/snapshots/Spoke.Getters.json @@ -1,7 +1,7 @@ { - "getUserAccountData: supplies: 0, borrows: 0": "11937", - "getUserAccountData: supplies: 1, borrows: 0": "48600", - "getUserAccountData: supplies: 2, borrows: 0": "80378", - "getUserAccountData: supplies: 2, borrows: 1": "100166", - "getUserAccountData: supplies: 2, borrows: 2": "118596" + "getUserAccountData: supplies: 0, borrows: 0": "11915", + "getUserAccountData: supplies: 1, borrows: 0": "48578", + "getUserAccountData: supplies: 2, borrows: 0": "80356", + "getUserAccountData: supplies: 2, borrows: 1": "100144", + "getUserAccountData: supplies: 2, borrows: 2": "118574" } \ No newline at end of file diff --git a/snapshots/Spoke.Operations.ZeroRiskPremium.json b/snapshots/Spoke.Operations.ZeroRiskPremium.json index dd334eaec..2d1a410ee 100644 --- a/snapshots/Spoke.Operations.ZeroRiskPremium.json +++ b/snapshots/Spoke.Operations.ZeroRiskPremium.json @@ -1,18 +1,18 @@ { - "borrow: first": "191325", - "borrow: second action, same reserve": "171297", - "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", + "borrow: first": "191303", + "borrow: second action, same reserve": "171275", + "liquidationCall (receiveShares): full": "300369", + "liquidationCall (receiveShares): partial": "300087", + "liquidationCall: full": "310734", + "liquidationCall: partial": "310452", + "permitReserve + repay (multicall)": "166360", + "permitReserve + supply (multicall)": "146840", + "permitReserve + supply + enable collateral (multicall)": "160551", + "repay: full": "126447", + "repay: partial": "131336", + "setUserPositionManagerWithSig: disable": "45813", + "setUserPositionManagerWithSig: enable": "69842", + "supply + enable collateral (multicall)": "140602", "supply: 0 borrows, collateral disabled": "123679", "supply: 0 borrows, collateral enabled": "106601", "supply: second action, same reserve": "106579", diff --git a/snapshots/Spoke.Operations.json b/snapshots/Spoke.Operations.json index 28821aab8..6a2a310e6 100644 --- a/snapshots/Spoke.Operations.json +++ b/snapshots/Spoke.Operations.json @@ -1,18 +1,18 @@ { - "borrow: first": "262009", - "borrow: second action, same reserve": "204981", - "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", + "borrow: first": "261987", + "borrow: second action, same reserve": "204959", + "liquidationCall (receiveShares): full": "334220", + "liquidationCall (receiveShares): partial": "333938", + "liquidationCall: full": "344585", + "liquidationCall: partial": "344303", + "permitReserve + repay (multicall)": "163538", + "permitReserve + supply (multicall)": "146840", + "permitReserve + supply + enable collateral (multicall)": "160551", + "repay: full": "120609", + "repay: partial": "139898", + "setUserPositionManagerWithSig: disable": "45813", + "setUserPositionManagerWithSig: enable": "69842", + "supply + enable collateral (multicall)": "140602", "supply: 0 borrows, collateral disabled": "123679", "supply: 0 borrows, collateral enabled": "106601", "supply: second action, same reserve": "106579", diff --git a/src/interfaces/IIntentConsumer.sol b/src/interfaces/IIntentConsumer.sol new file mode 100644 index 000000000..a7cf83cf9 --- /dev/null +++ b/src/interfaces/IIntentConsumer.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +// Copyright (c) 2025 Aave Labs +pragma solidity ^0.8.0; + +import {INoncesKeyed} from 'src/interfaces/INoncesKeyed.sol'; + +/// @title IIntentConsumer +/// @author Aave Labs +/// @notice Minimal interface for IntentConsumer. +interface IIntentConsumer is INoncesKeyed { + /// @notice Thrown when signature deadline has passed or signer is not the expected one. + error InvalidSignature(); + + /// @notice Returns the EIP-712 domain separator. + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/src/position-manager/libraries/EIP712Hash.sol b/src/libraries/cryptography/EIP712Hash.sol similarity index 87% rename from src/position-manager/libraries/EIP712Hash.sol rename to src/libraries/cryptography/EIP712Hash.sol index 060bd4575..2da917140 100644 --- a/src/position-manager/libraries/EIP712Hash.sol +++ b/src/libraries/cryptography/EIP712Hash.sol @@ -8,6 +8,10 @@ import {EIP712Types} from 'src/libraries/types/EIP712Types.sol'; /// @author Aave Labs /// @notice Helper methods to hash EIP712 typed data structs. library EIP712Hash { + bytes32 public constant SET_USER_POSITION_MANAGER_TYPEHASH = + // keccak256('SetUserPositionManager(address positionManager,address user,bool approve,uint256 nonce,uint256 deadline)') + 0x758d23a3c07218b7ea0b4f7f63903c4e9d5cbde72d3bcfe3e9896639025a0214; + bytes32 public constant SUPPLY_TYPEHASH = // keccak256('Supply(address spoke,uint256 reserveId,uint256 amount,address onBehalfOf,uint256 nonce,uint256 deadline)') 0xe85497eb293c001e8483fe105efadd1d50aa0dadfc0570b27058031dfceab2e6; @@ -138,4 +142,20 @@ library EIP712Hash { ) ); } + + function hash( + EIP712Types.SetUserPositionManager calldata params + ) internal pure returns (bytes32) { + return + keccak256( + abi.encode( + SET_USER_POSITION_MANAGER_TYPEHASH, + params.positionManager, + params.user, + params.approve, + params.nonce, + params.deadline + ) + ); + } } diff --git a/src/misc/IntentConsumer.sol b/src/misc/IntentConsumer.sol new file mode 100644 index 000000000..a987adfa6 --- /dev/null +++ b/src/misc/IntentConsumer.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +// Copyright (c) 2025 Aave Labs +pragma solidity 0.8.28; + +import {EIP712} from 'src/dependencies/solady/EIP712.sol'; +import {NoncesKeyed} from 'src/utils/NoncesKeyed.sol'; +import {SignatureChecker} from 'src/dependencies/openzeppelin/SignatureChecker.sol'; +import {IIntentConsumer} from 'src/interfaces/IIntentConsumer.sol'; + +abstract contract IntentConsumer is IIntentConsumer, NoncesKeyed, EIP712 { + /// @dev Verifies the signature for given signer & intent hash, and consumes the keyed-nonce. + /// @param signer The address of the user. + /// @param intentHash The hash of the intent struct. + /// @param nonce The keyed-nonce for the intent. + /// @param deadline The deadline timestamp for the intent. + /// @param signature The signature bytes. + function _verifyAndConsumeIntent( + address signer, + bytes32 intentHash, + uint256 nonce, + uint256 deadline, + bytes calldata signature + ) internal { + require(block.timestamp <= deadline, InvalidSignature()); + bytes32 digest = _hashTypedData(intentHash); + require(SignatureChecker.isValidSignatureNow(signer, digest, signature), InvalidSignature()); + _useCheckedNonce(signer, nonce); + } + + /// @inheritdoc IIntentConsumer + function DOMAIN_SEPARATOR() external view returns (bytes32) { + return _domainSeparator(); + } +} diff --git a/src/position-manager/SignatureGateway.sol b/src/position-manager/SignatureGateway.sol index c4ab78a29..cd01f0fd1 100644 --- a/src/position-manager/SignatureGateway.sol +++ b/src/position-manager/SignatureGateway.sol @@ -2,14 +2,12 @@ // Copyright (c) 2025 Aave Labs pragma solidity 0.8.28; -import {SignatureChecker} from 'src/dependencies/openzeppelin/SignatureChecker.sol'; +import {IntentConsumer} from 'src/misc/IntentConsumer.sol'; import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol'; import {IERC20Permit} from 'src/dependencies/openzeppelin/IERC20Permit.sol'; -import {EIP712} from 'src/dependencies/solady/EIP712.sol'; import {MathUtils} from 'src/libraries/math/MathUtils.sol'; -import {NoncesKeyed} from 'src/utils/NoncesKeyed.sol'; import {Multicall} from 'src/utils/Multicall.sol'; -import {EIP712Hash, EIP712Types} from 'src/position-manager/libraries/EIP712Hash.sol'; +import {EIP712Hash, EIP712Types} from 'src/libraries/cryptography/EIP712Hash.sol'; import {GatewayBase} from 'src/position-manager/GatewayBase.sol'; import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol'; import {ISignatureGateway} from 'src/position-manager/interfaces/ISignatureGateway.sol'; @@ -20,7 +18,7 @@ import {ISignatureGateway} from 'src/position-manager/interfaces/ISignatureGatew /// @dev Contract must be an active & approved user position manager to execute spoke actions on user's behalf. /// @dev Uses keyed-nonces where each key's namespace nonce is consumed sequentially. Intents bundled through /// multicall can be executed independently in order of signed nonce & deadline; does not guarantee batch atomicity. -contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multicall, EIP712 { +contract SignatureGateway is ISignatureGateway, GatewayBase, Multicall, IntentConsumer { using SafeERC20 for IERC20; using EIP712Hash for *; @@ -33,13 +31,16 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic EIP712Types.Supply calldata params, bytes calldata signature ) external onlyRegisteredSpoke(params.spoke) returns (uint256, uint256) { - require(block.timestamp <= params.deadline, InvalidSignature()); address spoke = params.spoke; uint256 reserveId = params.reserveId; address user = params.onBehalfOf; - bytes32 digest = _hashTypedData(params.hash()); - require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature()); - _useCheckedNonce(user, params.nonce); + _verifyAndConsumeIntent({ + signer: user, + intentHash: params.hash(), + nonce: params.nonce, + deadline: params.deadline, + signature: signature + }); IERC20 underlying = IERC20(_getReserveUnderlying(spoke, reserveId)); underlying.safeTransferFrom(user, address(this), params.amount); @@ -57,9 +58,13 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic address spoke = params.spoke; uint256 reserveId = params.reserveId; address user = params.onBehalfOf; - bytes32 digest = _hashTypedData(params.hash()); - require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature()); - _useCheckedNonce(user, params.nonce); + _verifyAndConsumeIntent({ + signer: user, + intentHash: params.hash(), + nonce: params.nonce, + deadline: params.deadline, + signature: signature + }); IERC20 underlying = IERC20(_getReserveUnderlying(spoke, reserveId)); (uint256 withdrawnShares, uint256 withdrawnAmount) = ISpoke(spoke).withdraw( @@ -81,9 +86,13 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic address spoke = params.spoke; uint256 reserveId = params.reserveId; address user = params.onBehalfOf; - bytes32 digest = _hashTypedData(params.hash()); - require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature()); - _useCheckedNonce(user, params.nonce); + _verifyAndConsumeIntent({ + signer: user, + intentHash: params.hash(), + nonce: params.nonce, + deadline: params.deadline, + signature: signature + }); IERC20 underlying = IERC20(_getReserveUnderlying(spoke, reserveId)); (uint256 borrowedShares, uint256 borrowedAmount) = ISpoke(spoke).borrow( @@ -105,9 +114,13 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic address spoke = params.spoke; uint256 reserveId = params.reserveId; address user = params.onBehalfOf; - bytes32 digest = _hashTypedData(params.hash()); - require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature()); - _useCheckedNonce(user, params.nonce); + _verifyAndConsumeIntent({ + signer: user, + intentHash: params.hash(), + nonce: params.nonce, + deadline: params.deadline, + signature: signature + }); IERC20 underlying = IERC20(_getReserveUnderlying(spoke, reserveId)); uint256 repayAmount = MathUtils.min( @@ -126,11 +139,14 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic EIP712Types.SetUsingAsCollateral calldata params, bytes calldata signature ) external onlyRegisteredSpoke(params.spoke) { - require(block.timestamp <= params.deadline, InvalidSignature()); address user = params.onBehalfOf; - bytes32 digest = _hashTypedData(params.hash()); - require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature()); - _useCheckedNonce(user, params.nonce); + _verifyAndConsumeIntent({ + signer: user, + intentHash: params.hash(), + nonce: params.nonce, + deadline: params.deadline, + signature: signature + }); ISpoke(params.spoke).setUsingAsCollateral(params.reserveId, params.useAsCollateral, user); } @@ -140,13 +156,13 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic EIP712Types.UpdateUserRiskPremium calldata params, bytes calldata signature ) external onlyRegisteredSpoke(params.spoke) { - require(block.timestamp <= params.deadline, InvalidSignature()); - bytes32 digest = _hashTypedData(params.hash()); - require( - SignatureChecker.isValidSignatureNow(params.user, digest, signature), - InvalidSignature() - ); - _useCheckedNonce(params.user, params.nonce); + _verifyAndConsumeIntent({ + signer: params.user, + intentHash: params.hash(), + nonce: params.nonce, + deadline: params.deadline, + signature: signature + }); ISpoke(params.spoke).updateUserRiskPremium(params.user); } @@ -156,13 +172,13 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic EIP712Types.UpdateUserDynamicConfig calldata params, bytes calldata signature ) external onlyRegisteredSpoke(params.spoke) { - require(block.timestamp <= params.deadline, InvalidSignature()); - bytes32 digest = _hashTypedData(params.hash()); - require( - SignatureChecker.isValidSignatureNow(params.user, digest, signature), - InvalidSignature() - ); - _useCheckedNonce(params.user, params.nonce); + _verifyAndConsumeIntent({ + signer: params.user, + intentHash: params.hash(), + nonce: params.nonce, + deadline: params.deadline, + signature: signature + }); ISpoke(params.spoke).updateUserDynamicConfig(params.user); } @@ -170,22 +186,11 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic /// @inheritdoc ISignatureGateway function setSelfAsUserPositionManagerWithSig( address spoke, - address user, - bool approve, - uint256 nonce, - uint256 deadline, + EIP712Types.SetUserPositionManager calldata params, bytes calldata signature ) external onlyRegisteredSpoke(spoke) { - try - ISpoke(spoke).setUserPositionManagerWithSig({ - positionManager: address(this), - user: user, - approve: approve, - nonce: nonce, - deadline: deadline, - signature: signature - }) - {} catch {} + require(params.positionManager == address(this), PositionManagerNotSelf()); + try ISpoke(spoke).setUserPositionManagerWithSig(params, signature) {} catch {} } /// @inheritdoc ISignatureGateway @@ -213,11 +218,6 @@ contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multic {} catch {} } - /// @inheritdoc ISignatureGateway - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparator(); - } - /// @inheritdoc ISignatureGateway function SUPPLY_TYPEHASH() external pure returns (bytes32) { return EIP712Hash.SUPPLY_TYPEHASH; diff --git a/src/position-manager/interfaces/ISignatureGateway.sol b/src/position-manager/interfaces/ISignatureGateway.sol index 8f67436b8..bfff8d0e8 100644 --- a/src/position-manager/interfaces/ISignatureGateway.sol +++ b/src/position-manager/interfaces/ISignatureGateway.sol @@ -3,16 +3,16 @@ pragma solidity ^0.8.0; import {IMulticall} from 'src/interfaces/IMulticall.sol'; -import {INoncesKeyed} from 'src/interfaces/INoncesKeyed.sol'; +import {IIntentConsumer} from 'src/interfaces/IIntentConsumer.sol'; import {EIP712Types} from 'src/libraries/types/EIP712Types.sol'; import {IGatewayBase} from 'src/position-manager/interfaces/IGatewayBase.sol'; /// @title ISignatureGateway /// @author Aave Labs /// @notice Minimal interface for protocol actions involving signed intents. -interface ISignatureGateway is IMulticall, INoncesKeyed, IGatewayBase { - /// @notice Thrown when signature deadline has passed or signer is not `onBehalfOf`. - error InvalidSignature(); +interface ISignatureGateway is IMulticall, IIntentConsumer, IGatewayBase { + /// @notice Emitted when the positionManager parameter of an intent is not this contract. + error PositionManagerNotSelf(); /// @notice Facilitates `supply` action on the specified registered `spoke` with a typed signature from `onBehalfOf`. /// @dev Supplied assets are pulled from `onBehalfOf`, prior approval to this gateway is required. @@ -96,17 +96,11 @@ interface ISignatureGateway is IMulticall, INoncesKeyed, IGatewayBase { /// @dev The signature is consumed on the the specified registered `spoke`. /// @dev The given data is passed to the `spoke` for the signature to be verified. /// @param spoke The address of the registered spoke. - /// @param user The address of the user on whose behalf this gateway can act. - /// @param approve True to approve the gateway, false to revoke approval. - /// @param nonce The key-prefixed nonce for the signature. - /// @param deadline The deadline for the signature. + /// @param params The structured setUserPositionManager parameters. /// @param signature The signed bytes for the intent. function setSelfAsUserPositionManagerWithSig( address spoke, - address user, - bool approve, - uint256 nonce, - uint256 deadline, + EIP712Types.SetUserPositionManager calldata params, bytes calldata signature ) external; @@ -129,9 +123,6 @@ interface ISignatureGateway is IMulticall, INoncesKeyed, IGatewayBase { bytes32 permitS ) external; - /// @notice Returns the EIP712 domain separator. - function DOMAIN_SEPARATOR() external view returns (bytes32); - /// @notice Returns the type hash for the Supply intent. function SUPPLY_TYPEHASH() external view returns (bytes32); diff --git a/src/spoke/Spoke.sol b/src/spoke/Spoke.sol index d71faaa03..eb4bc86d2 100644 --- a/src/spoke/Spoke.sol +++ b/src/spoke/Spoke.sol @@ -5,10 +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 {IntentConsumer} from 'src/misc/IntentConsumer.sol'; import {AccessManagedUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/AccessManagedUpgradeable.sol'; -import {EIP712} from 'src/dependencies/solady/EIP712.sol'; import {MathUtils} from 'src/libraries/math/MathUtils.sol'; +import {EIP712Hash, EIP712Types} from 'src/libraries/cryptography/EIP712Hash.sol'; import {PercentageMath} from 'src/libraries/math/PercentageMath.sol'; import {WadRayMath} from 'src/libraries/math/WadRayMath.sol'; import {KeyValueList} from 'src/spoke/libraries/KeyValueList.sol'; @@ -16,7 +16,6 @@ import {LiquidationLogic} from 'src/spoke/libraries/LiquidationLogic.sol'; import {PositionStatusMap} from 'src/spoke/libraries/PositionStatusMap.sol'; import {ReserveFlags, ReserveFlagsMap} from 'src/spoke/libraries/ReserveFlagsMap.sol'; import {UserPositionDebt} from 'src/spoke/libraries/UserPositionDebt.sol'; -import {NoncesKeyed} from 'src/utils/NoncesKeyed.sol'; import {Multicall} from 'src/utils/Multicall.sol'; import {IAaveOracle} from 'src/spoke/interfaces/IAaveOracle.sol'; import {IHubBase} from 'src/hub/interfaces/IHubBase.sol'; @@ -26,7 +25,7 @@ import {ISpokeBase, ISpoke} from 'src/spoke/interfaces/ISpoke.sol'; /// @author Aave Labs /// @notice Handles risk configuration & borrowing strategy for reserves and user positions. /// @dev Each reserve can be associated with a separate Hub. -abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradeable, EIP712 { +abstract contract Spoke is ISpoke, Multicall, AccessManagedUpgradeable, IntentConsumer { using SafeCast for *; using SafeERC20 for IERC20; using MathUtils for *; @@ -37,13 +36,8 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea using PositionStatusMap for *; using ReserveFlagsMap for ReserveFlags; using UserPositionDebt for ISpoke.UserPosition; + using EIP712Hash for EIP712Types.SetUserPositionManager; - /// @inheritdoc ISpoke - bytes32 public constant SET_USER_POSITION_MANAGER_TYPEHASH = - // keccak256('SetUserPositionManager(address positionManager,address user,bool approve,uint256 nonce,uint256 deadline)') - 0x758d23a3c07218b7ea0b4f7f63903c4e9d5cbde72d3bcfe3e9896639025a0214; - - /// @inheritdoc ISpoke address public immutable ORACLE; /// @dev The maximum allowed value for an asset identifier (inclusive). @@ -448,29 +442,21 @@ 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 - ) - ) - ); - require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature()); - _useCheckedNonce(user, nonce); - _setUserPositionManager({positionManager: positionManager, user: user, approve: approve}); + _verifyAndConsumeIntent({ + signer: params.user, + intentHash: params.hash(), + nonce: params.nonce, + deadline: params.deadline, + signature: signature + }); + _setUserPositionManager({ + positionManager: params.positionManager, + user: params.user, + approve: params.approve + }); } /// @inheritdoc ISpoke @@ -675,8 +661,8 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea } /// @inheritdoc ISpoke - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparator(); + function SET_USER_POSITION_MANAGER_TYPEHASH() external pure returns (bytes32) { + return EIP712Hash.SET_USER_POSITION_MANAGER_TYPEHASH; } /// @inheritdoc ISpoke diff --git a/src/spoke/interfaces/ISpoke.sol b/src/spoke/interfaces/ISpoke.sol index 29a0a5819..e39319337 100644 --- a/src/spoke/interfaces/ISpoke.sol +++ b/src/spoke/interfaces/ISpoke.sol @@ -3,17 +3,18 @@ pragma solidity ^0.8.0; import {IAccessManaged} from 'src/dependencies/openzeppelin/IAccessManaged.sol'; -import {INoncesKeyed} from 'src/interfaces/INoncesKeyed.sol'; +import {IIntentConsumer} from 'src/interfaces/IIntentConsumer.sol'; import {IMulticall} from 'src/interfaces/IMulticall.sol'; import {IHubBase} from 'src/hub/interfaces/IHubBase.sol'; import {ISpokeBase} from 'src/spoke/interfaces/ISpokeBase.sol'; +import {EIP712Types} from 'src/libraries/types/EIP712Types.sol'; type ReserveFlags is uint8; /// @title ISpoke /// @author Aave Labs /// @notice Full interface for Spoke. -interface ISpoke is ISpokeBase, IMulticall, INoncesKeyed, IAccessManaged { +interface ISpoke is ISpokeBase, IMulticall, IIntentConsumer, IAccessManaged { /// @notice Reserve level data. /// @dev underlying The address of the underlying asset. /// @dev hub The address of the associated Hub. @@ -270,9 +271,6 @@ interface ISpoke is ISpokeBase, IMulticall, INoncesKeyed, IAccessManaged { /// @notice Thrown if an inactive position manager is set as a user's position manager. error InactivePositionManager(); - /// @notice Thrown when a signature is invalid. - error InvalidSignature(); - /// @notice Thrown for an invalid zero address. error InvalidAddress(); @@ -403,18 +401,10 @@ interface ISpoke is ISpokeBase, IMulticall, INoncesKeyed, IAccessManaged { /// @notice Enables a user to grant or revoke approval for a position manager using an EIP712-typed intent. /// @dev Uses keyed-nonces where for each key's namespace nonce is consumed sequentially. - /// @param positionManager The address of the position manager. - /// @param user The address of the user on whose behalf position manager can act. - /// @param approve True to approve the position manager, false to revoke approval. - /// @param nonce The key-prefixed nonce for the signature. - /// @param deadline The deadline for the signature. + /// @param params The structured setUserPositionManager parameters. /// @param signature The EIP712-compliant signature bytes. function setUserPositionManagerWithSig( - address positionManager, - address user, - bool approve, - uint256 nonce, - uint256 deadline, + EIP712Types.SetUserPositionManager calldata params, bytes calldata signature ) external; @@ -521,17 +511,13 @@ interface ISpoke is ISpokeBase, IMulticall, INoncesKeyed, IAccessManaged { /// @return True if positionManager is active and approved by user. function isPositionManager(address user, address positionManager) external view returns (bool); - /// @notice Returns the EIP-712 domain separator. - function DOMAIN_SEPARATOR() external view returns (bytes32); - /// @notice Returns the address of the external `LiquidationLogic` library. /// @return The address of the library. function getLiquidationLogic() external pure returns (address); - /// @notice Returns the type hash for the SetUserPositionManager intent. - /// @return The bytes-encoded EIP-712 struct hash representing the intent. - function SET_USER_POSITION_MANAGER_TYPEHASH() external view returns (bytes32); - /// @notice Returns the address of the AaveOracle contract. function ORACLE() external view returns (address); + + /// @notice Returns the type hash for the SetUserPositionManager intent. + function SET_USER_POSITION_MANAGER_TYPEHASH() external view returns (bytes32); } diff --git a/tests/Base.t.sol b/tests/Base.t.sol index 424bc3683..ffee1b789 100644 --- a/tests/Base.t.sol +++ b/tests/Base.t.sol @@ -38,6 +38,7 @@ import {Roles} from 'src/libraries/types/Roles.sol'; import {Rescuable, IRescuable} from 'src/utils/Rescuable.sol'; import {NoncesKeyed, INoncesKeyed} from 'src/utils/NoncesKeyed.sol'; import {UnitPriceFeed} from 'src/misc/UnitPriceFeed.sol'; +import {IntentConsumer, IIntentConsumer} from 'src/misc/IntentConsumer.sol'; import {AccessManagerEnumerable} from 'src/access/AccessManagerEnumerable.sol'; // hub diff --git a/tests/gas/Gateways.Operations.gas.t.sol b/tests/gas/Gateways.Operations.gas.t.sol index 148ba56c4..2758eed0b 100644 --- a/tests/gas/Gateways.Operations.gas.t.sol +++ b/tests/gas/Gateways.Operations.gas.t.sol @@ -250,10 +250,7 @@ contract SignatureGateway_Gas_Tests is SignatureGatewayBaseTest { gateway.setSelfAsUserPositionManagerWithSig({ spoke: address(spoke1), - user: p.user, - approve: p.approve, - nonce: p.nonce, - deadline: p.deadline, + params: p, signature: signature }); vm.snapshotGasLastCall(NAMESPACE, 'setSelfAsUserPositionManagerWithSig'); diff --git a/tests/gas/Spoke.Operations.gas.t.sol b/tests/gas/Spoke.Operations.gas.t.sol index aa6d2b8d9..de05ac476 100644 --- a/tests/gas/Spoke.Operations.gas.t.sol +++ b/tests/gas/Spoke.Operations.gas.t.sol @@ -309,14 +309,7 @@ contract SpokeOperations_Gas_Tests is SpokeBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPk, _getTypedDataHash(spoke, params)); bytes memory signature = abi.encodePacked(r, s, v); - spoke.setUserPositionManagerWithSig( - params.positionManager, - params.user, - params.approve, - params.nonce, - params.deadline, - signature - ); + spoke.setUserPositionManagerWithSig(params, signature); vm.snapshotGasLastCall(NAMESPACE, 'setUserPositionManagerWithSig: enable'); params.approve = false; @@ -324,14 +317,7 @@ contract SpokeOperations_Gas_Tests is SpokeBase { (v, r, s) = vm.sign(userPk, _getTypedDataHash(spoke, params)); signature = abi.encodePacked(r, s, v); - spoke.setUserPositionManagerWithSig( - params.positionManager, - params.user, - params.approve, - params.nonce, - params.deadline, - signature - ); + spoke.setUserPositionManagerWithSig(params, signature); vm.snapshotGasLastCall(NAMESPACE, 'setUserPositionManagerWithSig: disable'); } diff --git a/tests/unit/Spoke/Spoke.SetUserPositionManagerWithSig.t.sol b/tests/unit/Spoke/Spoke.SetUserPositionManagerWithSig.t.sol index 3ff896f34..bf32e1773 100644 --- a/tests/unit/Spoke/Spoke.SetUserPositionManagerWithSig.t.sol +++ b/tests/unit/Spoke/Spoke.SetUserPositionManagerWithSig.t.sol @@ -66,7 +66,11 @@ contract SpokeSetUserPositionManagerWithSigTest is SpokeBase { assertEq(spoke.DOMAIN_SEPARATOR(), expectedDomainSeparator); } - function test_setUserPositionManager_typeHash() public pure { + function test_setUserPositionManager_typeHash() public view { + assertEq( + Constants.SET_USER_POSITION_MANAGER_TYPEHASH, + spoke1.SET_USER_POSITION_MANAGER_TYPEHASH() + ); assertEq( Constants.SET_USER_POSITION_MANAGER_TYPEHASH, vm.eip712HashType('SetUserPositionManager') @@ -91,16 +95,9 @@ contract SpokeSetUserPositionManagerWithSigTest is SpokeBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, digest); bytes memory signature = abi.encodePacked(r, s, v); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); - spoke1.setUserPositionManagerWithSig( - params.positionManager, - params.user, - params.approve, - params.nonce, - params.deadline, - signature - ); + spoke1.setUserPositionManagerWithSig(params, signature); } function test_setUserPositionManagerWithSig_revertsWith_InvalidSignature_dueTo_InvalidSigner() @@ -115,16 +112,9 @@ contract SpokeSetUserPositionManagerWithSigTest is SpokeBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(randomUserPk, digest); bytes memory signature = abi.encodePacked(r, s, v); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); - spoke1.setUserPositionManagerWithSig( - params.positionManager, - params.user, - params.approve, - params.nonce, - params.deadline, - signature - ); + spoke1.setUserPositionManagerWithSig(params, signature); } function test_setUserPositionManagerWithSig_revertsWith_InvalidAccountNonce(bytes32) public { @@ -148,14 +138,7 @@ contract SpokeSetUserPositionManagerWithSigTest is SpokeBase { abi.encodeWithSelector(INoncesKeyed.InvalidAccountNonce.selector, params.user, currentNonce) ); vm.prank(vm.randomAddress()); - spoke1.setUserPositionManagerWithSig( - params.positionManager, - params.user, - params.approve, - params.nonce, - params.deadline, - signature - ); + spoke1.setUserPositionManagerWithSig(params, signature); } function test_setUserPositionManagerWithSig() public { @@ -173,14 +156,7 @@ contract SpokeSetUserPositionManagerWithSigTest is SpokeBase { emit ISpoke.SetUserPositionManager(params.user, params.positionManager, params.approve); vm.prank(vm.randomAddress()); - spoke1.setUserPositionManagerWithSig( - params.positionManager, - params.user, - params.approve, - params.nonce, - params.deadline, - signature - ); + spoke1.setUserPositionManagerWithSig(params, signature); _assertNonceIncrement(spoke1, params.user, params.nonce); assertEq(spoke1.isPositionManager(params.user, params.positionManager), params.approve); @@ -203,16 +179,9 @@ contract SpokeSetUserPositionManagerWithSigTest is SpokeBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, digest); bytes memory signature = abi.encodePacked(r, s, v); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); - spoke1.setUserPositionManagerWithSig( - params.positionManager, - params.user, - params.approve, - params.nonce, - params.deadline, - signature - ); + spoke1.setUserPositionManagerWithSig(params, signature); } function test_setUserPositionManagerWithSig_ERC1271_revertsWith_InvalidSignature_dueTo_InvalidHash() @@ -243,16 +212,11 @@ contract SpokeSetUserPositionManagerWithSigTest is SpokeBase { vm.prank(alice); smartWallet.approveHash(digest); - vm.expectRevert(ISpoke.InvalidSignature.selector); + invalidParams.nonce = params.nonce; + + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); - spoke1.setUserPositionManagerWithSig( - invalidParams.positionManager, - invalidParams.user, - invalidParams.approve, - params.nonce, - invalidParams.deadline, - signature - ); + spoke1.setUserPositionManagerWithSig(invalidParams, signature); } function test_setUserPositionManagerWithSig_ERC1271_revertsWith_InvalidAccountNonce( @@ -286,14 +250,7 @@ contract SpokeSetUserPositionManagerWithSigTest is SpokeBase { ) ); vm.prank(vm.randomAddress()); - spoke1.setUserPositionManagerWithSig( - params.positionManager, - params.user, - params.approve, - params.nonce, - params.deadline, - signature - ); + spoke1.setUserPositionManagerWithSig(params, signature); } function test_setUserPositionManagerWithSig_ERC1271() public { @@ -322,14 +279,7 @@ contract SpokeSetUserPositionManagerWithSigTest is SpokeBase { emit ISpoke.SetUserPositionManager(params.user, params.positionManager, params.approve); vm.prank(vm.randomAddress()); - spoke1.setUserPositionManagerWithSig( - params.positionManager, - params.user, - params.approve, - params.nonce, - params.deadline, - signature - ); + spoke1.setUserPositionManagerWithSig(params, signature); _assertNonceIncrement(spoke1, params.user, params.nonce); assertEq(spoke1.isPositionManager(params.user, params.positionManager), params.approve); diff --git a/tests/unit/misc/EIP712Hash.t.sol b/tests/unit/libraries/EIP712Hash.t.sol similarity index 98% rename from tests/unit/misc/EIP712Hash.t.sol rename to tests/unit/libraries/EIP712Hash.t.sol index 2a5b29148..7796a1f67 100644 --- a/tests/unit/misc/EIP712Hash.t.sol +++ b/tests/unit/libraries/EIP712Hash.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {Test} from 'forge-std/Test.sol'; -import {EIP712Hash, EIP712Types} from 'src/position-manager/libraries/EIP712Hash.sol'; +import {EIP712Hash, EIP712Types} from 'src/libraries/cryptography/EIP712Hash.sol'; contract EIP712HashTest is Test { using EIP712Hash for *; diff --git a/tests/unit/misc/SignatureGateway/SignatureGateway.Reverts.InvalidSignature.t.sol b/tests/unit/misc/SignatureGateway/SignatureGateway.Reverts.InvalidSignature.t.sol index 2bf1c37a2..17e087621 100644 --- a/tests/unit/misc/SignatureGateway/SignatureGateway.Reverts.InvalidSignature.t.sol +++ b/tests/unit/misc/SignatureGateway/SignatureGateway.Reverts.InvalidSignature.t.sol @@ -9,7 +9,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.Supply memory p = _supplyData(spoke1, alice, _warpAfterRandomDeadline()); bytes memory signature = _sign(alicePk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.supplyWithSig(p, signature); } @@ -18,7 +18,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.Withdraw memory p = _withdrawData(spoke1, alice, _warpAfterRandomDeadline()); bytes memory signature = _sign(alicePk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.withdrawWithSig(p, signature); } @@ -27,7 +27,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.Borrow memory p = _borrowData(spoke1, alice, _warpAfterRandomDeadline()); bytes memory signature = _sign(alicePk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.borrowWithSig(p, signature); } @@ -36,7 +36,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.Repay memory p = _repayData(spoke1, alice, _warpAfterRandomDeadline()); bytes memory signature = _sign(alicePk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.repayWithSig(p, signature); } @@ -48,7 +48,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.SetUsingAsCollateral memory p = _setAsCollateralData(spoke1, alice, deadline); bytes memory signature = _sign(alicePk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.setUsingAsCollateralWithSig(p, signature); } @@ -60,7 +60,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.UpdateUserRiskPremium memory p = _updateRiskPremiumData(spoke1, alice, deadline); bytes memory signature = _sign(alicePk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.updateUserRiskPremiumWithSig(p, signature); } @@ -75,7 +75,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { ); bytes memory signature = _sign(alicePk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.updateUserDynamicConfigWithSig(p, signature); } @@ -88,7 +88,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.Supply memory p = _supplyData(spoke1, onBehalfOf, _warpAfterRandomDeadline()); bytes memory signature = _sign(randomUserPk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.supplyWithSig(p, signature); } @@ -101,7 +101,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.Withdraw memory p = _withdrawData(spoke1, onBehalfOf, _warpAfterRandomDeadline()); bytes memory signature = _sign(randomUserPk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.withdrawWithSig(p, signature); } @@ -114,7 +114,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.Borrow memory p = _borrowData(spoke1, onBehalfOf, _warpAfterRandomDeadline()); bytes memory signature = _sign(randomUserPk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.borrowWithSig(p, signature); } @@ -127,7 +127,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.Repay memory p = _repayData(spoke1, onBehalfOf, _warpAfterRandomDeadline()); bytes memory signature = _sign(randomUserPk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.repayWithSig(p, signature); } @@ -143,7 +143,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.SetUsingAsCollateral memory p = _setAsCollateralData(spoke1, onBehalfOf, deadline); bytes memory signature = _sign(randomUserPk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.setUsingAsCollateralWithSig(p, signature); } @@ -159,7 +159,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.UpdateUserRiskPremium memory p = _updateRiskPremiumData(spoke1, user, deadline); bytes memory signature = _sign(randomUserPk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.updateUserRiskPremiumWithSig(p, signature); } @@ -175,7 +175,7 @@ contract SignatureGatewayInvalidSignatureTest is SignatureGatewayBaseTest { EIP712Types.UpdateUserDynamicConfig memory p = _updateDynamicConfigData(spoke1, user, deadline); bytes memory signature = _sign(randomUserPk, _getTypedDataHash(gateway, p)); - vm.expectRevert(ISpoke.InvalidSignature.selector); + vm.expectRevert(IIntentConsumer.InvalidSignature.selector); vm.prank(vm.randomAddress()); gateway.updateUserDynamicConfigWithSig(p, signature); } diff --git a/tests/unit/misc/SignatureGateway/SignatureGateway.SetSelfAsUserPositionManagerWithSig.t.sol b/tests/unit/misc/SignatureGateway/SignatureGateway.SetSelfAsUserPositionManagerWithSig.t.sol index a524035ae..ed051b5a7 100644 --- a/tests/unit/misc/SignatureGateway/SignatureGateway.SetSelfAsUserPositionManagerWithSig.t.sol +++ b/tests/unit/misc/SignatureGateway/SignatureGateway.SetSelfAsUserPositionManagerWithSig.t.sol @@ -6,15 +6,51 @@ import 'tests/unit/misc/SignatureGateway/SignatureGateway.Base.t.sol'; contract SignatureGatewaySetSelfAsUserPositionManagerTest is SignatureGatewayBaseTest { function test_setSelfAsUserPositionManagerWithSig_revertsWith_SpokeNotRegistered() public { + address user = vm.randomAddress(); + bool approve = vm.randomBool(); + uint256 nonce = vm.randomUint(); + uint256 deadline = vm.randomUint(); + bytes memory signature = vm.randomBytes(72); + vm.expectRevert(IGatewayBase.SpokeNotRegistered.selector); vm.prank(vm.randomAddress()); gateway.setSelfAsUserPositionManagerWithSig({ spoke: address(spoke2), - user: vm.randomAddress(), - approve: vm.randomBool(), - nonce: vm.randomUint(), - deadline: vm.randomUint(), - signature: vm.randomBytes(72) + params: EIP712Types.SetUserPositionManager({ + positionManager: address(gateway), + user: user, + approve: approve, + nonce: nonce, + deadline: deadline + }), + signature: signature + }); + } + + function test_setSelfAsUserPositionManagerWithSig_revertsWith_PositionManagerNotSelf() public { + address user = vm.randomAddress(); + bool approve = vm.randomBool(); + uint256 nonce = vm.randomUint(); + uint256 deadline = vm.randomUint(); + bytes memory signature = vm.randomBytes(72); + + address invalidPositionManager = vm.randomAddress(); + while (invalidPositionManager == address(gateway)) { + invalidPositionManager = vm.randomAddress(); + } + + vm.expectRevert(ISignatureGateway.PositionManagerNotSelf.selector); + vm.prank(vm.randomAddress()); + gateway.setSelfAsUserPositionManagerWithSig({ + spoke: address(spoke1), + params: EIP712Types.SetUserPositionManager({ + positionManager: invalidPositionManager, + user: user, + approve: approve, + nonce: nonce, + deadline: deadline + }), + signature: signature }); } @@ -29,22 +65,34 @@ contract SignatureGatewaySetSelfAsUserPositionManagerTest is SignatureGatewayBas address(spoke1), abi.encodeCall( ISpoke.setUserPositionManagerWithSig, - (address(gateway), user, approve, nonce, deadline, signature) + ( + EIP712Types.SetUserPositionManager(address(gateway), user, approve, nonce, deadline), + signature + ) ), 1 ); vm.prank(vm.randomAddress()); gateway.setSelfAsUserPositionManagerWithSig({ spoke: address(spoke1), - user: user, - approve: approve, - nonce: nonce, - deadline: deadline, + params: EIP712Types.SetUserPositionManager({ + positionManager: address(gateway), + user: user, + approve: approve, + nonce: nonce, + deadline: deadline + }), signature: signature }); } function test_setSelfAsUserPositionManagerWithSig_ignores_underlying_spoke_reverts() public { + address user = vm.randomAddress(); + bool approve = vm.randomBool(); + uint256 nonce = vm.randomUint(); + uint256 deadline = vm.randomUint(); + bytes memory signature = vm.randomBytes(72); + vm.mockCallRevert( address(spoke1), ISpoke.setUserPositionManagerWithSig.selector, @@ -54,11 +102,14 @@ contract SignatureGatewaySetSelfAsUserPositionManagerTest is SignatureGatewayBas vm.prank(vm.randomAddress()); gateway.setSelfAsUserPositionManagerWithSig({ spoke: address(spoke1), - user: vm.randomAddress(), - approve: vm.randomBool(), - nonce: vm.randomUint(), - deadline: vm.randomUint(), - signature: vm.randomBytes(72) + params: EIP712Types.SetUserPositionManager({ + positionManager: address(gateway), + user: user, + approve: approve, + nonce: nonce, + deadline: deadline + }), + signature: signature }); assertFalse(spoke1.isPositionManager(alice, address(gateway))); @@ -84,10 +135,7 @@ contract SignatureGatewaySetSelfAsUserPositionManagerTest is SignatureGatewayBas gateway.setSelfAsUserPositionManagerWithSig({ spoke: address(spoke1), - user: p.user, - approve: p.approve, - nonce: p.nonce, - deadline: p.deadline, + params: p, signature: signature }); diff --git a/tests/unit/misc/SignatureGateway/SignatureGateway.t.sol b/tests/unit/misc/SignatureGateway/SignatureGateway.t.sol index e12b26e0a..8c4132c11 100644 --- a/tests/unit/misc/SignatureGateway/SignatureGateway.t.sol +++ b/tests/unit/misc/SignatureGateway/SignatureGateway.t.sol @@ -247,10 +247,7 @@ contract SignatureGatewayTest is SignatureGatewayBaseTest { vm.prank(vm.randomAddress()); gateway.setSelfAsUserPositionManagerWithSig({ spoke: address(spoke1), - user: p.user, - approve: p.approve, - nonce: p.nonce, - deadline: p.deadline, + params: p, signature: signature });