diff --git a/src/ContractHelper.sol b/src/ContractHelper.sol index 56fb12d..f635394 100644 --- a/src/ContractHelper.sol +++ b/src/ContractHelper.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.23; /** - * @title A helper contract to get the address of a contract deployed by aan account with a given nonce. + * @title A helper contract to get the address of a contract deployed by an account with a given nonce. * @dev See the following sources for very similar implementations: * - https://github.com/foundry-rs/forge-std/blob/914702ae99c92fcc41db5128ae57d24a11be4a39/src/Script.sol * - https://github.com/foundry-rs/forge-std/blob/578968243529db44acffcb802196ccab9b54db88/src/StdUtils.sol#L90 diff --git a/src/ERC20Extended.sol b/src/ERC20Extended.sol index bfdc373..7a59bb2 100644 --- a/src/ERC20Extended.sol +++ b/src/ERC20Extended.sol @@ -4,12 +4,13 @@ pragma solidity 0.8.23; import { IERC20 } from "./interfaces/IERC20.sol"; import { IERC20Extended } from "./interfaces/IERC20Extended.sol"; +import { IERC5267 } from "./interfaces/IERC5267.sol"; import { ERC3009 } from "./ERC3009.sol"; /// @title An ERC20 token extended with EIP-2612 permits for signed approvals (via EIP-712 and with EIP-1271 -/// compatibility), and extended with EIP-3009 transfer with authorization (via EIP-712). -abstract contract ERC20Extended is IERC20Extended, ERC3009 { +/// and EIP-5267 compatibility), and extended with EIP-3009 transfer with authorization (via EIP-712). +abstract contract ERC20Extended is IERC20Extended, IERC5267, ERC3009 { /** * @inheritdoc IERC20Extended * @dev Keeping this constant, despite `permit` parameter name differences, to ensure max EIP-2612 compatibility. @@ -50,6 +51,32 @@ abstract contract ERC20Extended is IERC20Extended, ERC3009 { return true; } + /// @inheritdoc IERC5267 + function eip712Domain() + external + view + virtual + returns ( + bytes1 fields_, + string memory name_, + string memory version_, + uint256 chainId_, + address verifyingContract_, + bytes32 salt_, + uint256[] memory extensions_ + ) + { + return ( + hex"0f", // 01111 + _name, + "1", + block.chainid, + address(this), + bytes32(0), + new uint256[](0) + ); + } + /// @inheritdoc IERC20Extended function permit( address owner_, diff --git a/src/ERC712.sol b/src/ERC712.sol index 788d02a..978c4d8 100644 --- a/src/ERC712.sol +++ b/src/ERC712.sol @@ -100,8 +100,7 @@ abstract contract ERC712 is IERC712 { * @param expiry_ Timestamp at which the signature expires or max uint256 for no expiry. */ function _revertIfExpired(uint256 expiry_) internal view { - if (expiry_ != type(uint256).max && block.timestamp > expiry_) - revert SignatureExpired(expiry_, block.timestamp); + if (block.timestamp > expiry_) revert SignatureExpired(expiry_, block.timestamp); } /** diff --git a/src/ERC712Extended.sol b/src/ERC712Extended.sol new file mode 100644 index 0000000..826b123 --- /dev/null +++ b/src/ERC712Extended.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.23; + +import { IERC5267 } from "./interfaces/IERC5267.sol"; + +import { ERC712 } from "./ERC712.sol"; + +/// @title Typed structured data hashing and signing via EIP-712, +/// extended with EIP-5267 to describe and retrieve EIP-712 domain. +abstract contract ERC712Extended is ERC712, IERC5267 { + /** + * @notice Constructs ERC712Extended. + * @param name_ The name of the contract. + */ + constructor(string memory name_) ERC712(name_) {} + + /******************************************************************************************************************\ + | Public View/Pure Functions | + \******************************************************************************************************************/ + + /// @inheritdoc IERC5267 + function eip712Domain() + external + view + virtual + returns ( + bytes1 fields_, + string memory name_, + string memory version_, + uint256 chainId_, + address verifyingContract_, + bytes32 salt_, + uint256[] memory extensions_ + ) + { + return ( + hex"0f", // 01111 + _name, + "1", + block.chainid, + address(this), + bytes32(0), + new uint256[](0) + ); + } +} diff --git a/src/interfaces/IERC5267.sol b/src/interfaces/IERC5267.sol new file mode 100644 index 0000000..1912204 --- /dev/null +++ b/src/interfaces/IERC5267.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.23; + +/// @title Extension for EIP-712 to retrieve the EIP-712 domain. +interface IERC5267 { + /// @notice MAY be emitted to signal that the domain could have changed. + event EIP712DomainChanged(); + + /// @notice Returns the fields and values that describe the domain separator used by this contract for EIP-712. + function eip712Domain() + external + view + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ); +} diff --git a/src/libs/SignatureChecker.sol b/src/libs/SignatureChecker.sol index d5f7295..9499664 100644 --- a/src/libs/SignatureChecker.sol +++ b/src/libs/SignatureChecker.sol @@ -56,13 +56,9 @@ library SignatureChecker { * @param signer The address of the account purported to have signed. * @param digest The hash of the data that was signed. * @param signature A byte array signature. - * @return isValid Whether the signature is valid. + * @return Whether the signature is valid or not. */ - function isValidSignature( - address signer, - bytes32 digest, - bytes memory signature - ) internal view returns (bool isValid) { + function isValidSignature(address signer, bytes32 digest, bytes memory signature) internal view returns (bool) { return isValidECDSASignature(signer, digest, signature) || isValidERC1271Signature(signer, digest, signature); } @@ -71,13 +67,13 @@ library SignatureChecker { * @param signer The address of the account purported to have signed. * @param digest The hash of the data that was signed. * @param signature A byte array ECDSA/secp256k1 signature (encoded r, s, v). - * @return isValid Whether the signature is valid. + * @return Whether the signature is valid or not. */ function isValidECDSASignature( address signer, bytes32 digest, bytes memory signature - ) internal pure returns (bool isValid) { + ) internal pure returns (bool) { if (signature.length == 64) { (bytes32 r, bytes32 vs) = decodeShortECDSASignature(signature); return isValidECDSASignature(signer, digest, r, vs); @@ -92,14 +88,9 @@ library SignatureChecker { * @param digest The hash of the data that was signed. * @param r An ECDSA/secp256k1 signature parameter. * @param vs An ECDSA/secp256k1 short signature parameter. - * @return isValid Whether the signature is valid. + * @return Whether the signature is valid or not. */ - function isValidECDSASignature( - address signer, - bytes32 digest, - bytes32 r, - bytes32 vs - ) internal pure returns (bool isValid) { + function isValidECDSASignature(address signer, bytes32 digest, bytes32 r, bytes32 vs) internal pure returns (bool) { return validateECDSASignature(signer, digest, r, vs) == Error.NoError; } @@ -110,7 +101,7 @@ library SignatureChecker { * @param v An ECDSA/secp256k1 signature parameter. * @param r An ECDSA/secp256k1 signature parameter. * @param s An ECDSA/secp256k1 signature parameter. - * @return isValid Whether the signature is valid. + * @return Whether the signature is valid or not. */ function isValidECDSASignature( address signer, @@ -118,7 +109,7 @@ library SignatureChecker { uint8 v, bytes32 r, bytes32 s - ) internal pure returns (bool isValid) { + ) internal pure returns (bool) { return validateECDSASignature(signer, digest, v, r, s) == Error.NoError; } @@ -127,13 +118,13 @@ library SignatureChecker { * @param signer The address of the account purported to have signed. * @param digest The hash of the data that was signed. * @param signature A byte array ERC1271 signature. - * @return isValid Whether the signature is valid. + * @return Whether the signature is valid or not. */ function isValidERC1271Signature( address signer, bytes32 digest, bytes memory signature - ) internal view returns (bool isValid) { + ) internal view returns (bool) { (bool success, bytes memory result) = signer.staticcall( abi.encodeCall(IERC1271.isValidSignature, (digest, signature)) ); @@ -148,13 +139,10 @@ library SignatureChecker { * @dev Returns the signer of an ECDSA/secp256k1 signature for some digest. * @param digest The hash of the data that was signed. * @param signature A byte array ECDSA/secp256k1 signature. - * @return error An error, if any, that occurred during the signer recovery. - * @return signer The address of the account recovered form the signature (0 if error). + * @return An error, if any, that occurred during the signer recovery. + * @return The address of the account recovered form the signature (0 if error). */ - function recoverECDSASigner( - bytes32 digest, - bytes memory signature - ) internal pure returns (Error error, address signer) { + function recoverECDSASigner(bytes32 digest, bytes memory signature) internal pure returns (Error, address) { if (signature.length != 65) return (Error.InvalidSignatureLength, address(0)); (uint8 v, bytes32 r, bytes32 s) = decodeECDSASignature(signature); @@ -168,14 +156,10 @@ library SignatureChecker { * @param digest The hash of the data that was signed. * @param r An ECDSA/secp256k1 signature parameter. * @param vs An ECDSA/secp256k1 short signature parameter. - * @return error An error, if any, that occurred during the signer recovery. - * @return signer The address of the account recovered form the signature (0 if error). + * @return An error, if any, that occurred during the signer recovery. + * @return The address of the account recovered form the signature (0 if error). */ - function recoverECDSASigner( - bytes32 digest, - bytes32 r, - bytes32 vs - ) internal pure returns (Error error, address signer) { + function recoverECDSASigner(bytes32 digest, bytes32 r, bytes32 vs) internal pure returns (Error, address) { unchecked { // We do not check for an overflow here since the shift operation results in 0 or 1. uint8 v = uint8((uint256(vs) >> 255) + 27); @@ -190,7 +174,7 @@ library SignatureChecker { * @param v An ECDSA/secp256k1 signature parameter. * @param r An ECDSA/secp256k1 signature parameter. * @param s An ECDSA/secp256k1 signature parameter. - * @return error An error, if any, that occurred during the signer recovery. + * @return An error, if any, that occurred during the signer recovery. * @return signer The address of the account recovered form the signature (0 if error). */ function recoverECDSASigner( @@ -198,7 +182,7 @@ library SignatureChecker { uint8 v, bytes32 r, bytes32 s - ) internal pure returns (Error error, address signer) { + ) internal pure returns (Error, address signer) { // Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. if (uint256(s) > uint256(0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0)) @@ -216,13 +200,13 @@ library SignatureChecker { * @param signer The address of the account purported to have signed. * @param digest The hash of the data that was signed. * @param signature A byte array ERC1271 signature. - * @return error An error, if any, that occurred during the signer recovery. + * @return An error, if any, that occurred during the signer recovery. */ function validateECDSASignature( address signer, bytes32 digest, bytes memory signature - ) internal pure returns (Error error) { + ) internal pure returns (Error) { (Error recoverError, address recoveredSigner) = recoverECDSASigner(digest, signature); return (recoverError == Error.NoError) ? validateRecoveredSigner(signer, recoveredSigner) : recoverError; @@ -234,14 +218,14 @@ library SignatureChecker { * @param digest The hash of the data that was signed. * @param r An ECDSA/secp256k1 signature parameter. * @param vs An ECDSA/secp256k1 short signature parameter. - * @return error An error, if any, that occurred during the signer recovery. + * @return An error, if any, that occurred during the signer recovery. */ function validateECDSASignature( address signer, bytes32 digest, bytes32 r, bytes32 vs - ) internal pure returns (Error error) { + ) internal pure returns (Error) { (Error recoverError, address recoveredSigner) = recoverECDSASigner(digest, r, vs); return (recoverError == Error.NoError) ? validateRecoveredSigner(signer, recoveredSigner) : recoverError; @@ -254,7 +238,7 @@ library SignatureChecker { * @param v An ECDSA/secp256k1 signature parameter. * @param r An ECDSA/secp256k1 signature parameter. * @param s An ECDSA/secp256k1 signature parameter. - * @return error An error, if any, that occurred during the signer recovery. + * @return An error, if any, that occurred during the signer recovery. */ function validateECDSASignature( address signer, @@ -262,7 +246,7 @@ library SignatureChecker { uint8 v, bytes32 r, bytes32 s - ) internal pure returns (Error error) { + ) internal pure returns (Error) { (Error recoverError, address recoveredSigner) = recoverECDSASigner(digest, v, r, s); return (recoverError == Error.NoError) ? validateRecoveredSigner(signer, recoveredSigner) : recoverError; @@ -272,9 +256,9 @@ library SignatureChecker { * @dev Returns an error if `signer` is not `recoveredSigner`. * @param signer The address of the some signer. * @param recoveredSigner The address of the some recoveredSigner. - * @return error An error if `signer` is not `recoveredSigner`. + * @return An error if `signer` is not `recoveredSigner`. */ - function validateRecoveredSigner(address signer, address recoveredSigner) internal pure returns (Error error) { + function validateRecoveredSigner(address signer, address recoveredSigner) internal pure returns (Error) { return (signer == recoveredSigner) ? Error.NoError : Error.SignerMismatch; } } diff --git a/test/ERC20Extended.t.sol b/test/ERC20Extended.t.sol index fa63a43..bef4c01 100644 --- a/test/ERC20Extended.t.sol +++ b/test/ERC20Extended.t.sol @@ -36,6 +36,27 @@ contract ERC20ExtendedTests is TestUtils { assertEq(_token.decimals(), _TOKEN_DECIMALS); } + /* ============ eip712Domain ============ */ + function test_eip712Domain() public { + ( + bytes1 fields_, + string memory name_, + string memory version_, + uint256 chainId_, + address verifyingContract_, + bytes32 salt_, + uint256[] memory extensions_ + ) = _token.eip712Domain(); + + assertEq(fields_, hex"0f"); + assertEq(name_, _TOKEN_NAME); + assertEq(version_, "1"); + assertEq(chainId_, block.chainid); + assertEq(verifyingContract_, address(_token)); + assertEq(salt_, bytes32(0)); + assertEq(extensions_, new uint256[](0)); + } + /* ============ mint ============ */ function test_mint() public { uint256 amount_ = 1e18; diff --git a/test/ERC712.t.sol b/test/ERC712.t.sol index b286e67..b09bbb4 100644 --- a/test/ERC712.t.sol +++ b/test/ERC712.t.sol @@ -4,12 +4,9 @@ pragma solidity 0.8.23; import { TestUtils } from "./utils/TestUtils.t.sol"; -import { SignatureChecker } from "../src/libs/SignatureChecker.sol"; -import { SignatureCheckerHarness } from "./utils/SignatureCheckerHarness.sol"; import { ERC712Harness } from "./utils/ERC712Harness.sol"; import { IERC712 } from "../src/interfaces/IERC712.sol"; -import { ERC712 } from "../src/ERC712.sol"; contract ERC712Tests is TestUtils { ERC712Harness internal _erc712; diff --git a/test/ERC712Extended.t.sol b/test/ERC712Extended.t.sol new file mode 100644 index 0000000..977c59e --- /dev/null +++ b/test/ERC712Extended.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.23; + +import { TestUtils } from "./utils/TestUtils.t.sol"; + +import { ERC712ExtendedHarness } from "./utils/ERC712ExtendedHarness.sol"; + +contract ERC712ExtendedTests is TestUtils { + ERC712ExtendedHarness internal _ERC712Extended; + + string internal _name = "ERC712Contract"; + + function setUp() external { + _ERC712Extended = new ERC712ExtendedHarness(_name); + } + + /* ============ eip712Domain ============ */ + function test_eip712Domain() external { + ( + bytes1 fields_, + string memory name_, + string memory version_, + uint256 chainId_, + address verifyingContract_, + bytes32 salt_, + uint256[] memory extensions_ + ) = _ERC712Extended.eip712Domain(); + + assertEq(fields_, hex"0f"); + assertEq(name_, _name); + assertEq(version_, "1"); + assertEq(chainId_, block.chainid); + assertEq(verifyingContract_, address(_ERC712Extended)); + assertEq(salt_, bytes32(0)); + assertEq(extensions_, new uint256[](0)); + } +} diff --git a/test/utils/ERC712ExtendedHarness.sol b/test/utils/ERC712ExtendedHarness.sol new file mode 100644 index 0000000..6b1de8c --- /dev/null +++ b/test/utils/ERC712ExtendedHarness.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.23; + +import { ERC712Extended } from "../../src/ERC712Extended.sol"; + +contract ERC712ExtendedHarness is ERC712Extended { + constructor(string memory name_) ERC712Extended(name_) {} +}