Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add EIP-5267 and optimize #319

Merged
merged 2 commits into from
Feb 10, 2023
Merged
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: 9 additions & 3 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,15 @@ ECDSATest:testTryRecoverWithV1SignatureWithWrongVersionReturnsZero() (gas: 5192)
ECDSATest:testTryRecoverWithValidSignature() (gas: 5262)
ECDSATest:testTryRecoverWithWrongSigner() (gas: 5255)
ECDSATest:test__codesize() (gas: 11541)
EIP712Test:testDomainSeparator() (gas: 6016)
EIP712Test:testHashTypedData() (gas: 38124)
EIP712Test:test__codesize() (gas: 4887)
EIP712Test:testDomainSeparator() (gas: 5798)
EIP712Test:testDomainSeparatorOnClone() (gas: 8839)
EIP712Test:testDomainSeparatorOnCloneWithChainIdChange() (gas: 13395)
EIP712Test:testDomainSeparatorWithChainIdChange() (gas: 10116)
EIP712Test:testHashTypedData() (gas: 37858)
EIP712Test:testHashTypedDataOnClone() (gas: 41341)
EIP712Test:testHashTypedDataOnCloneWithChaindIdChange() (gas: 51418)
EIP712Test:testHashTypedDataWithChaindIdChange() (gas: 47763)
EIP712Test:test__codesize() (gas: 6680)
FixedPointMathLibTest:testAbs() (gas: 577)
FixedPointMathLibTest:testAbs(int256) (runs: 256, μ: 515, ~: 484)
FixedPointMathLibTest:testAbsEdgeCases() (gas: 433)
Expand Down
126 changes: 93 additions & 33 deletions src/utils/EIP712.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,58 @@ pragma solidity ^0.8.4;
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol)
abstract contract EIP712 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/* CONSTANTS AND IMMUTABLES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 internal constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;

address private immutable _cachedThis;
uint256 private immutable _cachedChainId;
bytes32 private immutable _cachedDomainSeparator;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* FUNCTIONS TO OVERRIDE */
/* CONSTRUCTOR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Please override this function to return the keccak256 of the domain name.
/// ```
/// function _domainNameHash() internal pure override returns (bytes32) {
/// return keccak256("Solady");
/// }
/// ```
function _domainNameHash() internal pure virtual returns (bytes32);
constructor() {
_cachedThis = address(this);
_cachedChainId = block.chainid;
_cachedDomainSeparator = _buildDomainSeparator();
}

/// @dev Please override this function to return the keccak256 of the domain version.
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* FUNCTIONS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Please override this function to return the domain name and version.
/// ```
/// function _domainVersionHash() internal pure override returns (bytes32) {
/// return keccak256("1");
/// function _domainNameAndVersion()
/// internal
/// pure
/// virtual
/// returns (string memory name, string memory version)
/// {
/// name = "Solady";
/// version = "1";
/// }
/// ```
function _domainVersionHash() internal pure virtual returns (bytes32);
function _domainNameAndVersion()
internal
pure
virtual
returns (string memory name, string memory version);

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Returns the EIP-712 domain separator.
function _domainSeparator() internal view virtual returns (bytes32 separator) {
bytes32 domainNameHash = _domainNameHash();
bytes32 domainVersionHash = _domainVersionHash();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), domainNameHash)
mstore(add(m, 0x40), domainVersionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
separator = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) {
separator = _buildDomainSeparator();
}
}

Expand All @@ -68,23 +76,75 @@ abstract contract EIP712 {
/// address signer = ECDSA.recover(digest, signature);
/// ```
function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) {
bytes32 domainNameHash = _domainNameHash();
bytes32 domainVersionHash = _domainVersionHash();
bytes32 separator = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) {
separator = _buildDomainSeparator();
}
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), domainNameHash)
mstore(add(m, 0x40), domainVersionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
// Compute the digest.
mstore(0x00, 0x1901000000000000) // Store "\x19\x01".
mstore(0x1a, keccak256(m, 0xa0)) // Store the domain separator.
mstore(0x1a, separator) // Store the domain separator.
mstore(0x3a, structHash) // Store the struct hash.
digest := keccak256(0x18, 0x42)
// Restore the part of the free memory slot that was overwritten.
mstore(0x3a, 0)
}
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-5267 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev See: https://eips.ethereum.org/EIPS/eip-5267
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
fields = hex"0f";
(name, version) = _domainNameAndVersion();
chainId = block.chainid;
verifyingContract = address(this);
salt = salt;
extensions = extensions;
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Returns the EIP-712 domain separator.
function _buildDomainSeparator() private view returns (bytes32 separator) {
(string memory name, string memory version) = _domainNameAndVersion();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), keccak256(add(0x20, name), mload(name)))
mstore(add(m, 0x40), keccak256(add(0x20, version), mload(version)))
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
}
}

/// @dev Returns if the cached domain separator has been invalidated.
function _cachedDomainSeparatorInvalidated() private view returns (bool result) {
uint256 cachedChainId = _cachedChainId;
address cachedThis = _cachedThis;
/// @solidity memory-safe-assembly
assembly {
result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis)))
}
}
}
51 changes: 47 additions & 4 deletions test/EIP712.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,38 @@ pragma solidity ^0.8.4;

import "./utils/TestPlus.sol";
import {MockEIP712} from "./utils/mocks/MockEIP712.sol";
import {LibClone} from "../src/utils/LibClone.sol";

contract EIP712Test is TestPlus {
MockEIP712 mock;
MockEIP712 mockClone;

function setUp() public {
mock = new MockEIP712();
mockClone = MockEIP712(LibClone.clone(address(mock)));
}

function testHashTypedData() public {
_testHashTypedDataOnClone(mock);
}

function testHashTypedDataOnClone() public {
_testHashTypedDataOnClone(mockClone);
}

function testHashTypedDataWithChaindIdChange() public {
_testHashTypedDataOnClone(mock);
vm.chainId(32123);
_testHashTypedDataOnClone(mock);
}

function testHashTypedDataOnCloneWithChaindIdChange() public {
_testHashTypedDataOnClone(mockClone);
vm.chainId(32123);
_testHashTypedDataOnClone(mockClone);
}

function _testHashTypedDataOnClone(MockEIP712 mockToTest) internal {
(address signer, uint256 privateKey) = _randomSigner();

(address to,) = _randomSigner();
Expand All @@ -21,9 +44,9 @@ contract EIP712Test is TestPlus {
bytes32 structHash =
keccak256(abi.encode("Message(address to,string message)", to, message));
bytes32 expectedDigest =
keccak256(abi.encodePacked("\x19\x01", mock.DOMAIN_SEPARATOR(), structHash));
keccak256(abi.encodePacked("\x19\x01", mockToTest.DOMAIN_SEPARATOR(), structHash));

assertEq(mock.hashTypedData(structHash), expectedDigest);
assertEq(mockToTest.hashTypedData(structHash), expectedDigest);

(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, expectedDigest);

Expand All @@ -33,6 +56,26 @@ contract EIP712Test is TestPlus {
}

function testDomainSeparator() public {
_testDomainSeparator(mock);
}

function testDomainSeparatorOnClone() public {
_testDomainSeparator(mockClone);
}

function testDomainSeparatorWithChainIdChange() public {
_testDomainSeparator(mock);
vm.chainId(32123);
_testDomainSeparator(mock);
}

function testDomainSeparatorOnCloneWithChainIdChange() public {
_testDomainSeparator(mockClone);
vm.chainId(32123);
_testDomainSeparator(mockClone);
}

function _testDomainSeparator(MockEIP712 mockToTest) internal {
bytes32 expectedDomainSeparator = keccak256(
abi.encode(
keccak256(
Expand All @@ -41,10 +84,10 @@ contract EIP712Test is TestPlus {
keccak256("Milady"),
keccak256("1"),
block.chainid,
address(mock)
address(mockToTest)
)
);

assertEq(mock.DOMAIN_SEPARATOR(), expectedDomainSeparator);
assertEq(mockToTest.DOMAIN_SEPARATOR(), expectedDomainSeparator);
}
}
14 changes: 8 additions & 6 deletions test/utils/mocks/MockEIP712.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ pragma solidity ^0.8.4;
import "../../../src/utils/EIP712.sol";

contract MockEIP712 is EIP712 {
function _domainNameHash() internal pure override returns (bytes32) {
return keccak256("Milady");
}

function _domainVersionHash() internal pure override returns (bytes32) {
return keccak256("1");
function _domainNameAndVersion()
internal
pure
override
returns (string memory name, string memory version)
{
name = "Milady";
version = "1";
}

function hashTypedData(bytes32 structHash) external view returns (bytes32) {
Expand Down