Skip to content

fix: bind EIP-712 domain separator to proxy, not implementation#52

Merged
PierrickGT merged 1 commit into
mainfrom
fix/erc712-domain-separator
Jun 10, 2026
Merged

fix: bind EIP-712 domain separator to proxy, not implementation#52
PierrickGT merged 1 commit into
mainfrom
fix/erc712-domain-separator

Conversation

@PierrickGT

Copy link
Copy Markdown
Member

The non-upgradeable ERC712Extended cached the domain separator in a constructor immutable keyed only on block.chainid. Behind a Proxy, that immutable is baked with address(this) = implementation and read from the implementation bytecode during delegatecall, so the live proxy reported a separator bound to the implementation rather than itself — breaking standards-compliant permit/ERC-3009 signatures (which wallets build from the proxy address via eip712Domain()) and silently rotating the domain on every upgrade.

Add an _INITIAL_THIS immutable (the construction-time address) and require address(this) == _INITIAL_THIS before trusting the cache. Under a proxy this is always false, so DOMAIN_SEPARATOR() recomputes from the live address(this) (the proxy) and stays stable across upgrades, while direct home-chain calls keep the gas-saving cache.

The upgradeable variant is unaffected (it initializes the separator through the proxy in proxy storage).

The non-upgradeable `ERC712Extended` cached the domain separator in a
constructor `immutable` keyed only on `block.chainid`. Behind a `Proxy`,
that immutable is baked with `address(this) = implementation` and read
from the implementation bytecode during `delegatecall`, so the live proxy
reported a separator bound to the implementation rather than itself —
breaking standards-compliant `permit`/ERC-3009 signatures (which wallets
build from the proxy address via `eip712Domain()`) and silently rotating
the domain on every upgrade.

Add an `_INITIAL_THIS` immutable (the construction-time address) and
require `address(this) == _INITIAL_THIS` before trusting the cache. Under
a proxy this is always false, so `DOMAIN_SEPARATOR()` recomputes from the
live `address(this)` (the proxy) and stays stable across upgrades, while
direct home-chain calls keep the gas-saving cache.

The upgradeable variant is unaffected (it initializes the separator
through the proxy in proxy storage).
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Changes to gas cost

Generated at commit: c041865c9fb46b09f42a4c63bebd42189181be38, compared to commit: 20440d03de21fe62970829245872e9adae6cd822

🧾 Summary (20% most significant diffs)

Contract Method Avg (+/-) %
ERC712ExtendedHarness getDigest +2,669 ❌ +413.16%
Proxy fallback -18,133 ✅ -32.69%
ERC20ExtendedHarness getCancelAuthorizationDigest
getPermitDigest
getReceiveWithAuthorizationDigest
getTransferWithAuthorizationDigest
+48 ❌
+48 ❌
+48 ❌
+48 ❌
+4.59%
+4.00%
+3.56%
+3.56%
Bytes32StringHarness toString -276 ✅ -3.37%

Full diff report 👇
Contract Deployment Cost (+/-) Method Min (+/-) % Avg (+/-) % Median (+/-) % Max (+/-) % # Calls (+/-)
ERC712ExtendedHarness 799,373 (+11,053) DOMAIN_SEPARATOR
getDigest
292 (+48)
694 (+48)
+19.67%
+7.43%
4,079 (+2,084)
3,315 (+2,669)
+104.46%
+413.16%
5,535 (+5,291)
3,315 (+2,669)
+2168.44%
+413.16%
5,545 (+48)
5,937 (+5,291)
+0.87%
+819.04%
18 (+12)
2 (+1)
Proxy 104,329 (0) fallback 5,070 (-37) -0.72% 37,341 (-18,133) -32.69% 16,681 (-34,935) -67.68% 165,559 (+46,752) +39.35% 19,346 (+14,402)
ERC20ExtendedHarness 1,677,854 (+11,136) approve
burn
cancelAuthorization(address,bytes32,bytes)
cancelAuthorization(address,bytes32,bytes32,bytes32)
cancelAuthorization(address,bytes32,uint8,bytes32,bytes32)
getCancelAuthorizationDigest
getPermitDigest
getReceiveWithAuthorizationDigest
getTransferWithAuthorizationDigest
mint
permit
receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)
receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes32,bytes32)
receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)
transferFrom
transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)
transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes32,bytes32)
transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)
24,253 (0)
24,132 (+12)
52,599 (+48)
51,793 (+48)
30,212 (+48)
1,094 (+48)
1,249 (+48)
1,395 (+48)
1,395 (+48)
28,461 (0)
23,868 (+36)
60,830 (-44)
59,975 (-44)
29,183 (+48)
24,606 (0)
60,670 (-32)
59,947 (-32)
29,086 (+48)
0.00%
+0.05%
+0.09%
+0.09%
+0.16%
+4.59%
+4.00%
+3.56%
+3.56%
0.00%
+0.15%
-0.07%
-0.07%
+0.16%
0.00%
-0.05%
-0.05%
+0.17%
43,305 (+10)
29,414 (-2)
52,599 (+48)
51,793 (+48)
41,085 (+48)
1,094 (+48)
1,249 (+48)
1,395 (+48)
1,395 (+48)
48,665 (-31)
60,400 (+10)
78,761 (-89)
77,906 (-89)
75,630 (-74)
33,250 (+22)
78,600 (-91)
77,877 (-91)
75,665 (-76)
+0.02%
-0.01%
+0.09%
+0.09%
+0.12%
+4.59%
+4.00%
+3.56%
+3.56%
-0.06%
+0.02%
-0.11%
-0.11%
-0.10%
+0.07%
-0.12%
-0.12%
-0.10%
46,237 (0)
28,711 (0)
52,599 (+48)
51,793 (+48)
41,085 (+48)
1,094 (+48)
1,249 (+48)
1,395 (+48)
1,395 (+48)
51,425 (0)
74,981 (+48)
79,038 (+48)
78,183 (+48)
78,404 (+56)
31,885 (0)
78,878 (+48)
78,155 (+48)
78,258 (+40)
0.00%
0.00%
+0.09%
+0.09%
+0.12%
+4.59%
+4.00%
+3.56%
+3.56%
0.00%
+0.06%
+0.06%
+0.06%
+0.07%
0.00%
+0.06%
+0.06%
+0.05%
46,537 (0)
34,695 (0)
52,599 (+48)
51,793 (+48)
51,959 (+48)
1,094 (+48)
1,249 (+48)
1,395 (+48)
1,395 (+48)
68,873 (0)
75,893 (+60)
79,410 (+36)
78,555 (+36)
78,776 (+36)
54,671 (0)
79,262 (+48)
78,539 (+48)
78,650 (+48)
0.00%
0.00%
+0.09%
+0.09%
+0.09%
+4.59%
+4.00%
+3.56%
+3.56%
0.00%
+0.08%
+0.05%
+0.05%
+0.05%
0.00%
+0.06%
+0.06%
+0.06%
14,328 (+21)
13,274 (+2)
1 (0)
1 (0)
2 (0)
4 (0)
1,290 (+5)
783 (+3)
782 (+3)
16,420 (+15)
1,548 (+6)
258 (+1)
258 (+1)
272 (+1)
13,610 (+2)
258 (+1)
258 (+1)
271 (+1)
Bytes32StringHarness 238,492 (0) toString 698 (0) 0.00% 7,910 (-276) -3.37% 9,651 (-330) -3.31% 11,261 (0) 0.00% 293 (+1)
ERC20ExtendedUpgradeableHarness 1,964,420 (0) approve
burn
mint
permit
receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)
receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes32,bytes32)
receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)
transferFrom
transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)
transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes32,bytes32)
transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)
4,725 (0)
2,789 (0)
7,154 (0)
862 (0)
41,138 (-20)
40,798 (-20)
9,766 (0)
2,936 (0)
41,087 (0)
40,770 (0)
9,689 (0)
0.00%
0.00%
0.00%
0.00%
-0.05%
-0.05%
0.00%
0.00%
0.00%
0.00%
0.00%
24,490 (+78)
7,672 (+22)
46,502 (-53)
40,511 (-38)
63,495 (-176)
63,155 (-176)
60,476 (-157)
15,137 (+105)
63,445 (-175)
63,128 (-175)
60,529 (-157)
+0.32%
+0.29%
-0.11%
-0.09%
-0.28%
-0.28%
-0.26%
+0.70%
-0.28%
-0.28%
-0.26%
24,625 (0)
7,176 (0)
46,954 (0)
55,898 (0)
63,838 (0)
63,498 (0)
63,557 (0)
8,262 (0)
63,807 (+20)
63,490 (+20)
63,439 (+20)
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
+0.03%
+0.03%
+0.03%
24,625 (0)
12,776 (0)
46,954 (0)
56,167 (0)
63,858 (0)
63,518 (0)
63,577 (0)
35,516 (0)
63,807 (0)
63,490 (0)
63,439 (0)
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
1,033 (+4)
515 (+2)
3,612 (+14)
1,548 (+6)
258 (+1)
258 (+1)
272 (+1)
775 (+3)
258 (+1)
258 (+1)
271 (+1)
ContractHelperHarness 221,136 (0) getContractFrom 697 (0) 0.00% 750 (-1) -0.13% 768 (0) 0.00% 781 (0) 0.00% 270 (+1)
ERC20ExtendedHandler 766,618 (0) burn
mint
transfer
transferFrom
42,191 (+1)
381 (0)
477 (0)
488 (0)
+0.00%
0.00%
0.00%
0.00%
45,735 (-6)
53,790 (-59)
66,209 (+63)
62,649 (+7)
-0.01%
-0.11%
+0.10%
+0.01%
44,691 (0)
62,135 (-120)
60,908 (0)
61,141 (0)
0.00%
-0.19%
0.00%
0.00%
53,627 (0)
96,839 (0)
131,421 (+24)
133,995 (+204)
0.00%
0.00%
+0.02%
+0.15%
12,759 (0)
12,809 (0)
12,845 (0)
12,840 (0)
TransferHelperHarness 472,638 (0) safeApprove
safeTransfer
safeTransferExact
safeTransferExactFrom
safeTransferFrom
25,909 (0)
28,406 (+24)
32,918 (0)
36,230 (0)
34,039 (0)
0.00%
+0.08%
0.00%
0.00%
0.00%
37,132 (+8)
41,029 (+43)
68,235 (+43)
74,338 (+48)
47,179 (+48)
+0.02%
+0.10%
+0.06%
+0.06%
+0.10%
27,204 (-96)
29,679 (0)
68,274 (0)
74,383 (0)
34,839 (0)
-0.35%
0.00%
0.00%
0.00%
0.00%
48,875 (0)
53,780 (0)
80,919 (0)
87,024 (0)
59,931 (0)
0.00%
0.00%
0.00%
0.00%
0.00%
1,028 (+4)
1,028 (+4)
514 (+2)
514 (+2)
1,028 (+4)
SignatureCheckerHarness 618,084 (0) isValidECDSASignature(address,bytes32,bytes)
isValidECDSASignature(address,bytes32,bytes32,bytes32)
validateECDSASignature(address,bytes32,bytes)
1,349 (0)
938 (0)
1,230 (0)
0.00%
0.00%
0.00%
4,844 (-1)
4,446 (-1)
4,725 (-1)
-0.02%
-0.02%
-0.02%
4,881 (0)
4,470 (0)
4,762 (0)
0.00%
0.00%
0.00%
4,897 (0)
4,486 (0)
4,778 (0)
0.00%
0.00%
0.00%
263 (+1)
263 (+1)
263 (+1)

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

LCOV of commit cb402eb during Forge Coverage #161

Summary coverage rate:
  lines......: 93.8% (466 of 497 lines)
  functions..: 93.2% (150 of 161 functions)
  branches...: no data found

Files changed coverage rate:
                                     |Lines       |Functions  |Branches    
  Filename                           |Rate     Num|Rate    Num|Rate     Num
  =========================================================================
  src/ERC712Extended.sol             |29.7%     37| 0.0%    11|    -      0

@PierrickGT PierrickGT merged commit 489fae6 into main Jun 10, 2026
2 of 3 checks passed
@PierrickGT PierrickGT deleted the fix/erc712-domain-separator branch June 10, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants