From 70b2df509a292cea3623573eb8a86e51c30627a9 Mon Sep 17 00:00:00 2001 From: goncaloMagalhaes <95878230+goncaloMagalhaes@users.noreply.github.com> Date: Wed, 10 Jan 2024 09:40:15 +0000 Subject: [PATCH 1/5] tests(fuzz): add fuzz tests to ContractHelper, ERC3009 and SignatureChecker --- test/ContractHelper.t.sol | 12 +++ test/ERC3009.t.sol | 177 ++++++++++++++++++++++++++++++++++++ test/SignatureChecker.t.sol | 124 +++++++++++++++++++++++++ 3 files changed, 313 insertions(+) diff --git a/test/ContractHelper.t.sol b/test/ContractHelper.t.sol index a6f9cec..6427a70 100644 --- a/test/ContractHelper.t.sol +++ b/test/ContractHelper.t.sol @@ -63,4 +63,16 @@ contract ContractHelperTests is Test { vm.setNonce(address(_voidDeployer), 0xffffff + 1); assertEq(_contractHelper.getContractFrom(address(_voidDeployer), 0xffffff + 1), _voidDeployer.deploy()); } + + function testFuzz_full(uint64 nonce) external { + // @dev we want to exclude this unrealistic case, which is not covered by the implementation. + vm.assume(nonce <= 0xffffffff); + vm.assume(nonce != 0x00); + + // @dev deploy new deployer to change address input as well. + VoidDeployer newVoidDeployer = new VoidDeployer{salt: keccak256(abi.encode(nonce))}(); + + vm.setNonce(address(newVoidDeployer), nonce); + assertEq(_contractHelper.getContractFrom(address(newVoidDeployer), nonce), newVoidDeployer.deploy()); + } } diff --git a/test/ERC3009.t.sol b/test/ERC3009.t.sol index 6e8fda1..dac1762 100644 --- a/test/ERC3009.t.sol +++ b/test/ERC3009.t.sol @@ -97,6 +97,37 @@ contract ERC3009Tests is TestUtils { assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); } + function testFuzz_transferWithAuthorization_fullSignature( + uint256 value_, + uint256 validAfter_, + uint256 validBefore_ + ) external { + validBefore_ = bound(validBefore_, block.timestamp, type(uint256).max); + validAfter_ = bound(validAfter_, 0, block.timestamp); + assertFalse(_token.authorizationState(_alice, _SOME_NONCE)); + + (uint8 v_, bytes32 r_, bytes32 s_) = _signDigest( + _aliceKey, + _token.getTransferWithAuthorizationDigest(_alice, _bob, value_, validAfter_, validBefore_, _SOME_NONCE) + ); + + vm.expectEmit(); + emit IERC3009.AuthorizationUsed(_alice, _SOME_NONCE); + + vm.prank(_charlie); + _token.transferWithAuthorization( + _alice, + _bob, + value_, + validAfter_, + validBefore_, + _SOME_NONCE, + _encodeSignature(v_, r_, s_) + ); + + assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); + } + function test_transferWithAuthorization_rvsSignature() external { uint256 value_ = 100; uint256 validAfter_ = 0; @@ -127,6 +158,39 @@ contract ERC3009Tests is TestUtils { assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); } + function testFuzz_transferWithAuthorization_rvsSignature( + uint256 value_, + uint256 validAfter_, + uint256 validBefore_ + ) external { + validBefore_ = bound(validBefore_, block.timestamp, type(uint256).max); + validAfter_ = bound(validAfter_, 0, block.timestamp); + + assertFalse(_token.authorizationState(_alice, _SOME_NONCE)); + + (uint8 v_, bytes32 r_, bytes32 s_) = _signDigest( + _aliceKey, + _token.getTransferWithAuthorizationDigest(_alice, _bob, value_, validAfter_, validBefore_, _SOME_NONCE) + ); + + vm.expectEmit(); + emit IERC3009.AuthorizationUsed(_alice, _SOME_NONCE); + + vm.prank(_charlie); + _token.transferWithAuthorization( + _alice, + _bob, + value_, + validAfter_, + validBefore_, + _SOME_NONCE, + r_, + _getVS(v_, s_) + ); + + assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); + } + function test_transferWithAuthorization_vrsSignature() external { uint256 value_ = 100; uint256 validAfter_ = 0; @@ -148,6 +212,30 @@ contract ERC3009Tests is TestUtils { assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); } + function testFuzz_transferWithAuthorization_vrsSignature( + uint256 value_, + uint256 validAfter_, + uint256 validBefore_ + ) external { + validBefore_ = bound(validBefore_, block.timestamp, type(uint256).max); + validAfter_ = bound(validAfter_, 0, block.timestamp); + + assertFalse(_token.authorizationState(_alice, _SOME_NONCE)); + + (uint8 v_, bytes32 r_, bytes32 s_) = _signDigest( + _aliceKey, + _token.getTransferWithAuthorizationDigest(_alice, _bob, value_, validAfter_, validBefore_, _SOME_NONCE) + ); + + vm.expectEmit(); + emit IERC3009.AuthorizationUsed(_alice, _SOME_NONCE); + + vm.prank(_charlie); + _token.transferWithAuthorization(_alice, _bob, value_, validAfter_, validBefore_, _SOME_NONCE, v_, r_, s_); + + assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); + } + function test_transferWithAuthorization_invalidParameter() external { uint256 value_ = 100; uint256 validAfter_ = 0; @@ -307,6 +395,38 @@ contract ERC3009Tests is TestUtils { assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); } + function testFuzz_receiveWithAuthorization_fullSignature( + uint256 value_, + uint256 validAfter_, + uint256 validBefore_ + ) external { + validBefore_ = bound(validBefore_, block.timestamp, type(uint256).max); + validAfter_ = bound(validAfter_, 0, block.timestamp); + + assertFalse(_token.authorizationState(_alice, _SOME_NONCE)); + + (uint8 v_, bytes32 r_, bytes32 s_) = _signDigest( + _aliceKey, + _token.getReceiveWithAuthorizationDigest(_alice, _bob, value_, validAfter_, validBefore_, _SOME_NONCE) + ); + + vm.expectEmit(); + emit IERC3009.AuthorizationUsed(_alice, _SOME_NONCE); + + vm.prank(_bob); + _token.receiveWithAuthorization( + _alice, + _bob, + value_, + validAfter_, + validBefore_, + _SOME_NONCE, + abi.encodePacked(r_, s_, v_) + ); + + assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); + } + function test_receiveWithAuthorization_rvsSignature() external { uint256 value_ = 100; uint256 validAfter_ = 0; @@ -337,6 +457,39 @@ contract ERC3009Tests is TestUtils { assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); } + function testFuzz_receiveWithAuthorization_rvsSignature( + uint256 value_, + uint256 validAfter_, + uint256 validBefore_ + ) external { + validBefore_ = bound(validBefore_, block.timestamp, type(uint256).max); + validAfter_ = bound(validAfter_, 0, block.timestamp); + + assertFalse(_token.authorizationState(_alice, _SOME_NONCE)); + + (uint8 v_, bytes32 r_, bytes32 s_) = _signDigest( + _aliceKey, + _token.getReceiveWithAuthorizationDigest(_alice, _bob, value_, validAfter_, validBefore_, _SOME_NONCE) + ); + + vm.expectEmit(); + emit IERC3009.AuthorizationUsed(_alice, _SOME_NONCE); + + vm.prank(_bob); + _token.receiveWithAuthorization( + _alice, + _bob, + value_, + validAfter_, + validBefore_, + _SOME_NONCE, + r_, + _getVS(v_, s_) + ); + + assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); + } + function test_receiveWithAuthorization_vrsSignature() external { uint256 value_ = 100; uint256 validAfter_ = 0; @@ -358,6 +511,30 @@ contract ERC3009Tests is TestUtils { assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); } + function testFuzz_receiveWithAuthorization_vrsSignature( + uint256 value_, + uint256 validAfter_, + uint256 validBefore_ + ) external { + validBefore_ = bound(validBefore_, block.timestamp, type(uint256).max); + validAfter_ = bound(validAfter_, 0, block.timestamp); + + assertFalse(_token.authorizationState(_alice, _SOME_NONCE)); + + (uint8 v_, bytes32 r_, bytes32 s_) = _signDigest( + _aliceKey, + _token.getReceiveWithAuthorizationDigest(_alice, _bob, value_, validAfter_, validBefore_, _SOME_NONCE) + ); + + vm.expectEmit(); + emit IERC3009.AuthorizationUsed(_alice, _SOME_NONCE); + + vm.prank(_bob); + _token.receiveWithAuthorization(_alice, _bob, value_, validAfter_, validBefore_, _SOME_NONCE, v_, r_, s_); + + assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); + } + function test_receiveWithAuthorization_callerMustBePayee() external { uint256 value_ = 100; uint256 validAfter_ = 0; diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index 9543378..eeb379b 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -64,6 +64,20 @@ contract SignatureCheckerTests is TestUtils { assertEq(s_, bytes32("TEST_S")); } + function testFuzz_decodeECDSASignature( + uint256 v, + uint256 r, + uint256 s + ) external { + (uint8 v_, bytes32 r_, bytes32 s_) = _signatureChecker.decodeECDSASignature( + _encodeSignature(uint8(v), bytes32(r), bytes32(s)) + ); + + assertEq(v_, uint8(v)); + assertEq(r_, bytes32(r)); + assertEq(s_, bytes32(s)); + } + function test_recoverECDSASigner_vrs_invalidSignatureS() external { (SignatureChecker.Error error_, address signer) = _signatureChecker.recoverECDSASigner( 0x00, @@ -101,6 +115,17 @@ contract SignatureCheckerTests is TestUtils { assertEq(signer_, account_); } + function testFuzz_recoverECDSASigner_vrs(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + (SignatureChecker.Error error_, address signer_) = _signatureChecker.recoverECDSASigner(digest_, v_, r_, s_); + + assertEq(uint8(error_), uint8(SignatureChecker.Error.NoError)); + assertEq(signer_, account_); + } + function test_recoverECDSASigner_rvs_invalidSignatureS() external { (SignatureChecker.Error error_, address signer) = _signatureChecker.recoverECDSASigner( 0x00, @@ -134,6 +159,21 @@ contract SignatureCheckerTests is TestUtils { assertEq(signer_, account_); } + function testFuzz_recoverECDSASigner_rvs(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + (SignatureChecker.Error error_, address signer_) = _signatureChecker.recoverECDSASigner( + digest_, + r_, + _getVS(v_, s_) + ); + + assertEq(uint8(error_), uint8(SignatureChecker.Error.NoError)); + assertEq(signer_, account_); + } + function test_recoverECDSASigner_bytes_invalidSignatureS() external { (SignatureChecker.Error error_, address signer) = _signatureChecker.recoverECDSASigner( 0x00, @@ -178,6 +218,20 @@ contract SignatureCheckerTests is TestUtils { assertEq(signer_, account_); } + function testFuzz_recoverECDSASigner_bytes(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + (SignatureChecker.Error error_, address signer_) = _signatureChecker.recoverECDSASigner( + digest_, + _encodeSignature(v_, r_, s_) + ); + + assertEq(uint8(error_), uint8(SignatureChecker.Error.NoError)); + assertEq(signer_, account_); + } + function test_validateRecoveredSigner_mismatch() external { assertEq( uint8(_signatureChecker.validateRecoveredSigner(address(0), address(1))), @@ -234,6 +288,17 @@ contract SignatureCheckerTests is TestUtils { ); } + function testFuzz_validateECDSASignature_rvs(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + assertEq( + uint8(_signatureChecker.validateECDSASignature(account_, digest_, r_, _getVS(v_, s_))), + uint8(SignatureChecker.Error.NoError) + ); + } + function test_validateECDSASignature_rvs_invalid() external { bytes32 digest_ = "TEST_DIGEST"; (address account_, uint256 privateKey_) = makeAddrAndKey("account"); @@ -278,6 +343,17 @@ contract SignatureCheckerTests is TestUtils { ); } + function testFuzz_validateECDSASignature_vrs(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + assertEq( + uint8(_signatureChecker.validateECDSASignature(account_, digest_, r_, _getVS(v_, s_))), + uint8(SignatureChecker.Error.NoError) + ); + } + function test_validateECDSASignature_bytes_invalid() external { bytes32 invalidS_ = bytes32(_MAX_S + 1); bytes32 digest_ = "TEST_DIGEST"; @@ -321,6 +397,17 @@ contract SignatureCheckerTests is TestUtils { ); } + function testFuzz_validateECDSASignature_bytes(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + assertEq( + uint8(_signatureChecker.validateECDSASignature(account_, digest_, _encodeSignature(v_, r_, s_))), + uint8(SignatureChecker.Error.NoError) + ); + } + function test_isValidECDSASignature_vrs_invalid() external { bytes32 digest_ = "TEST_DIGEST"; (address account_, uint256 privateKey_) = makeAddrAndKey("account"); @@ -342,6 +429,15 @@ contract SignatureCheckerTests is TestUtils { assertTrue(_signatureChecker.isValidECDSASignature(account_, digest_, v_, r_, s_)); } + function testFuzz_isValidECDSASignature_vrs(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + assertTrue(_signatureChecker.isValidECDSASignature(account_, digest_, v_, r_, s_)); + } + function test_isValidECDSASignature_rvs_invalid() external { bytes32 digest_ = "TEST_DIGEST"; (address account_, uint256 privateKey_) = makeAddrAndKey("account"); @@ -365,6 +461,15 @@ contract SignatureCheckerTests is TestUtils { assertTrue(_signatureChecker.isValidECDSASignature(account_, digest_, r_, _getVS(v_, s_))); } + function testFuzz_isValidECDSASignature_rvs(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + assertTrue(_signatureChecker.isValidECDSASignature(account_, digest_, r_, _getVS(v_, s_))); + } + function test_isValidECDSASignature_bytes_invalid() external { bytes32 invalidS_ = bytes32(_MAX_S + 1); bytes32 digest_ = "TEST_DIGEST"; @@ -387,6 +492,15 @@ contract SignatureCheckerTests is TestUtils { assertTrue(_signatureChecker.isValidECDSASignature(account_, digest_, _encodeSignature(v_, r_, s_))); } + function testFuzz_isValidECDSASignature_bytes(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + assertTrue(_signatureChecker.isValidECDSASignature(account_, digest_, _encodeSignature(v_, r_, s_))); + } + function test_isValidERC1271Signature_emptyAccount() external { assertFalse(_signatureChecker.isValidERC1271Signature(makeAddr("account"), "DIGEST", "")); } @@ -477,6 +591,16 @@ contract SignatureCheckerTests is TestUtils { assertTrue(_signatureChecker.isValidSignature(account_, digest_, _encodeShortSignature(r_, _getVS(v_, s_)))); } + function testFuzz_isValidSignature_ecdsa(uint256 digest) external { + bytes32 digest_ = bytes32(digest); + (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); + + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); + + assertTrue(_signatureChecker.isValidSignature(account_, digest_, _encodeSignature(v_, r_, s_))); + assertTrue(_signatureChecker.isValidSignature(account_, digest_, _encodeShortSignature(r_, _getVS(v_, s_)))); + } + function test_isValidSignature_erc1271() external { assertTrue(_signatureChecker.isValidSignature(address(new AccountWithValidFunction()), "DIGEST", "")); } From c8717eadacd6841a7965912c101e955492d4813d Mon Sep 17 00:00:00 2001 From: Pierrick Turelier Date: Mon, 12 Feb 2024 12:20:54 -0500 Subject: [PATCH 2/5] fix(ERC3009): use encodeSignature helper for tests --- test/ERC3009.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ERC3009.t.sol b/test/ERC3009.t.sol index dac1762..9cd7659 100644 --- a/test/ERC3009.t.sol +++ b/test/ERC3009.t.sol @@ -421,7 +421,7 @@ contract ERC3009Tests is TestUtils { validAfter_, validBefore_, _SOME_NONCE, - abi.encodePacked(r_, s_, v_) + _encodeSignature(v_, r_, s_) ); assertTrue(_token.authorizationState(_alice, _SOME_NONCE)); From 0e8acf69638eade753a729cd0717810c1af1d38c Mon Sep 17 00:00:00 2001 From: Pierrick Turelier Date: Mon, 12 Feb 2024 12:21:36 -0500 Subject: [PATCH 3/5] fix(SignatureChecker): rename full signature fuzz tests --- test/SignatureChecker.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index eeb379b..59fbc78 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -218,7 +218,7 @@ contract SignatureCheckerTests is TestUtils { assertEq(signer_, account_); } - function testFuzz_recoverECDSASigner_bytes(uint256 digest) external { + function testFuzz_recoverECDSASigner_fullSignature(uint256 digest) external { bytes32 digest_ = bytes32(digest); (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); From 016c173f05a2221ddadeef1b5260d3e3ce8fbb8b Mon Sep 17 00:00:00 2001 From: Pierrick Turelier Date: Mon, 12 Feb 2024 12:21:43 -0500 Subject: [PATCH 4/5] fix(SignatureChecker): rename full signature fuzz tests --- test/SignatureChecker.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index 59fbc78..6cc3065 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -397,7 +397,7 @@ contract SignatureCheckerTests is TestUtils { ); } - function testFuzz_validateECDSASignature_bytes(uint256 digest) external { + function testFuzz_validateECDSASignature_fullSignature(uint256 digest) external { bytes32 digest_ = bytes32(digest); (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest))); (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(privateKey_, digest_); From 2b857e2f2c2f0eaad8859de0fda4f79409807e87 Mon Sep 17 00:00:00 2001 From: Pierrick Turelier Date: Mon, 12 Feb 2024 12:21:50 -0500 Subject: [PATCH 5/5] fix(SignatureChecker): rename full signature fuzz tests --- test/SignatureChecker.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index 6cc3065..44a131a 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -492,7 +492,7 @@ contract SignatureCheckerTests is TestUtils { assertTrue(_signatureChecker.isValidECDSASignature(account_, digest_, _encodeSignature(v_, r_, s_))); } - function testFuzz_isValidECDSASignature_bytes(uint256 digest) external { + function testFuzz_isValidECDSASignature_fullSignature(uint256 digest) external { bytes32 digest_ = bytes32(digest); (address account_, uint256 privateKey_) = makeAddrAndKey(string(abi.encode("account", digest)));