diff --git a/src/Morpho.sol b/src/Morpho.sol index 7e6942851..40e179bbe 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -395,6 +395,7 @@ contract Morpho is IMorpho { /// @dev The signature is malleable, but it has no impact on the security here. function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external { require(block.timestamp < authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED); + require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE); bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization)); bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct)); @@ -402,7 +403,7 @@ contract Morpho is IMorpho { require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE); - emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, nonce[authorization.authorizer]++); + emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce); isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized; diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 24f10b16e..14c94b8c1 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -54,6 +54,9 @@ library ErrorsLib { /// @notice Thrown when the authorization signature is expired. string internal constant SIGNATURE_EXPIRED = "signature expired"; + /// @notice Thrown when the nonce is invalid. + string internal constant INVALID_NONCE = "invalid nonce"; + /// @notice Thrown when a token transfer has failed. string internal constant TRANSFER_FAILED = "transfer failed"; diff --git a/test/forge/Morpho.t.sol b/test/forge/Morpho.t.sol index 27d57929a..00e48339f 100644 --- a/test/forge/Morpho.t.sol +++ b/test/forge/Morpho.t.sol @@ -890,6 +890,38 @@ contract MorphoTest is morpho.setAuthorizationWithSig(authorization, sig); } + function testAuthorizationWithSigWrongNonce(Authorization memory authorization, uint256 privateKey) public { + vm.assume(authorization.deadline > block.timestamp); + vm.assume(authorization.nonce != 0); + + // Private key must be less than the secp256k1 curve order. + privateKey = bound(privateKey, 1, type(uint32).max); + authorization.authorizer = vm.addr(privateKey); + + Signature memory sig; + bytes32 digest = SigUtils.getTypedDataHash(morpho.DOMAIN_SEPARATOR(), authorization); + (sig.v, sig.r, sig.s) = vm.sign(privateKey, digest); + + vm.expectRevert(bytes(ErrorsLib.INVALID_NONCE)); + morpho.setAuthorizationWithSig(authorization, sig); + } + + function testAuthorizationWithSigDeadline(Authorization memory authorization, uint256 privateKey) public { + vm.assume(authorization.deadline <= block.timestamp); + + // Private key must be less than the secp256k1 curve order. + privateKey = bound(privateKey, 1, type(uint32).max); + authorization.nonce = 0; + authorization.authorizer = vm.addr(privateKey); + + Signature memory sig; + bytes32 digest = SigUtils.getTypedDataHash(morpho.DOMAIN_SEPARATOR(), authorization); + (sig.v, sig.r, sig.s) = vm.sign(privateKey, digest); + + vm.expectRevert(bytes(ErrorsLib.SIGNATURE_EXPIRED)); + morpho.setAuthorizationWithSig(authorization, sig); + } + function testFlashLoan(uint256 assets) public { assets = bound(assets, 1, 2 ** 64);