Skip to content

Commit b718855

Browse files
authored
feat: add meta-tx (erc2771) support to account register and namespace (#17)
* forge install: openzeppelin-contracts * add oz remappings * feat: add erc2771context to account registry * feat: add erc2771 context to namespace * update gas snapshot
1 parent e5a1f87 commit b718855

File tree

9 files changed

+117
-90
lines changed

9 files changed

+117
-90
lines changed

.gas-snapshot

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,70 @@
1-
AccountRegistryTest:testCancelRecoveryFromCustodyAddress() (gas: 134604)
2-
AccountRegistryTest:testCancelRecoveryFromRecoveryAddress() (gas: 134769)
3-
AccountRegistryTest:testCannotCancelRecoveryIfNotStarted() (gas: 92888)
4-
AccountRegistryTest:testCannotCancelRecoveryIfUnauthorized() (gas: 143224)
5-
AccountRegistryTest:testCannotCompleteRecoveryIfNotStarted() (gas: 103867)
6-
AccountRegistryTest:testCannotCompleteRecoveryIfUnauthorized() (gas: 145950)
7-
AccountRegistryTest:testCannotCompleteRecoveryToAddressThatOwnsAnId() (gas: 170083)
8-
AccountRegistryTest:testCannotCompleteRecoveryWhenInEscrow() (gas: 143753)
9-
AccountRegistryTest:testCannotRegisterTwice() (gas: 59151)
10-
AccountRegistryTest:testCannotRequestRecoveryToAddressThatOwnsAnId() (gas: 115813)
11-
AccountRegistryTest:testCannotRequestRecoveryUnlessAuthorized() (gas: 69253)
12-
AccountRegistryTest:testCannotRequestRecoveryUnlessIssued() (gas: 17797)
13-
AccountRegistryTest:testCannotSetRecoveryAddressWithoutId() (gas: 18880)
14-
AccountRegistryTest:testCannotTransferIfNoId() (gas: 21689)
15-
AccountRegistryTest:testCannotTransferToAddressWithId() (gas: 90461)
16-
AccountRegistryTest:testRecoveryCompletion() (gas: 133042)
17-
AccountRegistryTest:testRegister() (gas: 89924)
18-
AccountRegistryTest:testRequestRecovery() (gas: 144812)
19-
AccountRegistryTest:testSetRecoveryAddress() (gas: 95325)
20-
AccountRegistryTest:testTransfer() (gas: 75750)
21-
NameSpaceTest:testBidAfterOneStep() (gas: 220057)
22-
NameSpaceTest:testBidAndOverpay() (gas: 215676)
23-
NameSpaceTest:testBidFlatRate() (gas: 218435)
24-
NameSpaceTest:testBidImmediately() (gas: 231728)
25-
NameSpaceTest:testBidOnHundredthStep() (gas: 218435)
26-
NameSpaceTest:testBidOnPenultimateStep() (gas: 218425)
27-
NameSpaceTest:testBidShouldClearRecovery() (gas: 261975)
28-
NameSpaceTest:testCancelRecoveryFromCustodyAddress() (gas: 223880)
29-
NameSpaceTest:testCancelRecoveryFromRecoveryAddress() (gas: 223486)
1+
AccountRegistryTest:testCancelRecoveryFromCustodyAddress() (gas: 135294)
2+
AccountRegistryTest:testCancelRecoveryFromRecoveryAddress() (gas: 135469)
3+
AccountRegistryTest:testCannotCancelRecoveryIfNotStarted() (gas: 93253)
4+
AccountRegistryTest:testCannotCancelRecoveryIfUnauthorized() (gas: 143722)
5+
AccountRegistryTest:testCannotCompleteRecoveryIfNotStarted() (gas: 104236)
6+
AccountRegistryTest:testCannotCompleteRecoveryIfUnauthorized() (gas: 146409)
7+
AccountRegistryTest:testCannotCompleteRecoveryToAddressThatOwnsAnId() (gas: 170667)
8+
AccountRegistryTest:testCannotCompleteRecoveryWhenInEscrow() (gas: 144212)
9+
AccountRegistryTest:testCannotRegisterTwice() (gas: 59413)
10+
AccountRegistryTest:testCannotRequestRecoveryToAddressThatOwnsAnId() (gas: 116341)
11+
AccountRegistryTest:testCannotRequestRecoveryUnlessAuthorized() (gas: 69534)
12+
AccountRegistryTest:testCannotRequestRecoveryUnlessIssued() (gas: 17887)
13+
AccountRegistryTest:testCannotSetRecoveryAddressWithoutId() (gas: 19024)
14+
AccountRegistryTest:testCannotTransferIfNoId() (gas: 21856)
15+
AccountRegistryTest:testCannotTransferToAddressWithId() (gas: 90944)
16+
AccountRegistryTest:testRecoveryCompletion() (gas: 133409)
17+
AccountRegistryTest:testRegister() (gas: 90240)
18+
AccountRegistryTest:testRequestRecovery() (gas: 145181)
19+
AccountRegistryTest:testSetRecoveryAddress() (gas: 95738)
20+
AccountRegistryTest:testTransfer() (gas: 76111)
21+
NameSpaceTest:testBidAfterOneStep() (gas: 220251)
22+
NameSpaceTest:testBidAndOverpay() (gas: 215870)
23+
NameSpaceTest:testBidFlatRate() (gas: 218629)
24+
NameSpaceTest:testBidImmediately() (gas: 231922)
25+
NameSpaceTest:testBidOnHundredthStep() (gas: 218629)
26+
NameSpaceTest:testBidOnPenultimateStep() (gas: 218619)
27+
NameSpaceTest:testBidShouldClearRecovery() (gas: 262274)
28+
NameSpaceTest:testCancelRecoveryFromCustodyAddress() (gas: 224337)
29+
NameSpaceTest:testCancelRecoveryFromRecoveryAddress() (gas: 223953)
3030
NameSpaceTest:testCannotBidIfRegistrable() (gas: 30917)
31-
NameSpaceTest:testCannotBidUnlessBiddable() (gas: 175767)
32-
NameSpaceTest:testCannotCancelRecoveryIfNotStarted() (gas: 188104)
33-
NameSpaceTest:testCannotCancelRecoveryIfUnauthorized() (gas: 234673)
31+
NameSpaceTest:testCannotBidUnlessBiddable() (gas: 175857)
32+
NameSpaceTest:testCannotCancelRecoveryIfNotStarted() (gas: 188379)
33+
NameSpaceTest:testCannotCancelRecoveryIfUnauthorized() (gas: 235048)
3434
NameSpaceTest:testCannotCommitWithInvalidName() (gas: 22838)
35-
NameSpaceTest:testCannotCompleteRecoveryIfExpired() (gas: 246680)
36-
NameSpaceTest:testCannotCompleteRecoveryIfNotStarted() (gas: 192361)
37-
NameSpaceTest:testCannotCompleteRecoveryIfUnauthorized() (gas: 219947)
38-
NameSpaceTest:testCannotCompleteRecoveryWhenInEscrow() (gas: 236285)
35+
NameSpaceTest:testCannotCompleteRecoveryIfExpired() (gas: 246950)
36+
NameSpaceTest:testCannotCompleteRecoveryIfNotStarted() (gas: 192631)
37+
NameSpaceTest:testCannotCompleteRecoveryIfUnauthorized() (gas: 220397)
38+
NameSpaceTest:testCannotCompleteRecoveryWhenInEscrow() (gas: 236645)
3939
NameSpaceTest:testCannotReclaimUnlessMinted() (gas: 17407)
4040
NameSpaceTest:testCannotRegisterBeforeDelay() (gas: 90808)
4141
NameSpaceTest:testCannotRegisterWithoutPayment() (gas: 85835)
42-
NameSpaceTest:testCannotRenewIfBiddable() (gas: 165411)
43-
NameSpaceTest:testCannotRenewIfRegistered() (gas: 165495)
42+
NameSpaceTest:testCannotRenewIfBiddable() (gas: 165501)
43+
NameSpaceTest:testCannotRenewIfRegistered() (gas: 165585)
4444
NameSpaceTest:testCannotRenewIfRegistrable() (gas: 31908)
4545
NameSpaceTest:testCannotRenewIfRegistrable2() (gas: 31950)
46-
NameSpaceTest:testCannotRenewWithoutPayment() (gas: 158554)
47-
NameSpaceTest:testCannotRequestRecoveryIfRegistrable() (gas: 26459)
48-
NameSpaceTest:testCannotRequestRecoveryToZeroAddr() (gas: 187942)
49-
NameSpaceTest:testCannotRequestRecoveryUnlessAuthorized() (gas: 166372)
50-
NameSpaceTest:testCannotSetRecoveryIfExpired() (gas: 167538)
51-
NameSpaceTest:testCannotSetRecoveryIfRegistrable() (gas: 20896)
52-
NameSpaceTest:testCannotSetRecoveryUnlessOwner() (gas: 161134)
46+
NameSpaceTest:testCannotRenewWithoutPayment() (gas: 158644)
47+
NameSpaceTest:testCannotRequestRecoveryIfRegistrable() (gas: 26549)
48+
NameSpaceTest:testCannotRequestRecoveryToZeroAddr() (gas: 188122)
49+
NameSpaceTest:testCannotRequestRecoveryUnlessAuthorized() (gas: 166552)
50+
NameSpaceTest:testCannotSetRecoveryIfExpired() (gas: 167808)
51+
NameSpaceTest:testCannotSetRecoveryIfRegistrable() (gas: 20986)
52+
NameSpaceTest:testCannotSetRecoveryUnlessOwner() (gas: 161314)
5353
NameSpaceTest:testCurrYear() (gas: 78247)
5454
NameSpaceTest:testCurrYearPayment() (gas: 45881)
5555
NameSpaceTest:testGenerateCommit() (gas: 40350)
56-
NameSpaceTest:testOwnerOfRevertsIfExpired() (gas: 155313)
56+
NameSpaceTest:testOwnerOfRevertsIfExpired() (gas: 155403)
5757
NameSpaceTest:testOwnerOfRevertsIfRegistrable() (gas: 12872)
58-
NameSpaceTest:testReclaimBiddableNames() (gas: 184070)
59-
NameSpaceTest:testReclaimRegisteredNames() (gas: 192240)
60-
NameSpaceTest:testReclaimRenewableNames() (gas: 184124)
61-
NameSpaceTest:testReclaimResetsRecoveryState() (gas: 228103)
62-
NameSpaceTest:testRecoveryCompletion() (gas: 247168)
63-
NameSpaceTest:testRegister() (gas: 259089)
64-
NameSpaceTest:testRenewOther() (gas: 176902)
65-
NameSpaceTest:testRenewSelf() (gas: 174319)
66-
NameSpaceTest:testRenewWithOverpayment() (gas: 179869)
67-
NameSpaceTest:testRequestRecovery() (gas: 244135)
68-
NameSpaceTest:testSetRecoveryAddress() (gas: 192253)
69-
NameSpaceTest:testTransferFromCannotTransferExpiredName() (gas: 173939)
70-
NameSpaceTest:testTransferFromResetsRecovery() (gas: 227445)
58+
NameSpaceTest:testReclaimBiddableNames() (gas: 184160)
59+
NameSpaceTest:testReclaimRegisteredNames() (gas: 192312)
60+
NameSpaceTest:testReclaimRenewableNames() (gas: 184214)
61+
NameSpaceTest:testReclaimResetsRecoveryState() (gas: 228319)
62+
NameSpaceTest:testRecoveryCompletion() (gas: 247456)
63+
NameSpaceTest:testRegister() (gas: 259269)
64+
NameSpaceTest:testRenewOther() (gas: 177082)
65+
NameSpaceTest:testRenewSelf() (gas: 174499)
66+
NameSpaceTest:testRenewWithOverpayment() (gas: 180049)
67+
NameSpaceTest:testRequestRecovery() (gas: 244495)
68+
NameSpaceTest:testSetRecoveryAddress() (gas: 192523)
69+
NameSpaceTest:testTransferFromCannotTransferExpiredName() (gas: 174029)
70+
NameSpaceTest:testTransferFromResetsRecovery() (gas: 227661)

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
[submodule "lib/solmate"]
55
path = lib/solmate
66
url = https://github.com/Rari-Capital/solmate
7+
[submodule "lib/openzeppelin-contracts"]
8+
path = lib/openzeppelin-contracts
9+
url = https://github.com/openzeppelin/openzeppelin-contracts

lib/openzeppelin-contracts

Submodule openzeppelin-contracts added at 580b7ab

remappings.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
ds-test/=lib/forge-std/lib/ds-test/src/
22
forge-std/=lib/forge-std/src/
3+
@openzeppelin/=lib/openzeppelin-contracts/

script/AccountRegistry.s.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import "forge-std/Script.sol";
66
import {AccountRegistry} from "../src/AccountRegistry.sol";
77

88
contract AccountRegistryScript is Script {
9+
address gorliTrustedForwarder = address(0x7A95fA73250dc53556d264522150A940d4C50238);
10+
911
function setUp() public {}
1012

1113
function run() public {
1214
vm.broadcast();
13-
new AccountRegistry();
15+
new AccountRegistry(gorliTrustedForwarder);
1416
}
1517
}

src/AccountRegistry.sol

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity ^0.8.15;
33

4+
import {ERC2771Context} from "../lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol";
5+
46
error Unauthorized(); // The caller does not have the authority to perform this action.
57
error ZeroId(); // The id is zero, which is invalid
68
error HasId(); // The custody address has another id
@@ -20,7 +22,9 @@ error Escrow(); // The recovery request is still in escrow
2022
*
2123
* @dev Function calls use payable to marginally reduce gas usage.
2224
*/
23-
contract AccountRegistry {
25+
contract AccountRegistry is ERC2771Context {
26+
constructor(address _trustedForwarder) ERC2771Context(_trustedForwarder) {}
27+
2428
/*//////////////////////////////////////////////////////////////
2529
REGISTRY EVENTS
2630
//////////////////////////////////////////////////////////////*/
@@ -77,14 +81,16 @@ contract AccountRegistry {
7781
* to overflow given that every increment requires a new on-chain transaction.
7882
*/
7983
function register() external payable {
80-
if (idOf[msg.sender] != 0) revert HasId();
84+
address _msgSender = _msgSender();
85+
86+
if (idOf[_msgSender] != 0) revert HasId();
8187

8288
unchecked {
8389
idCounter++;
8490
}
8591

86-
idOf[msg.sender] = idCounter;
87-
emit Register(msg.sender, idCounter);
92+
idOf[_msgSender] = idCounter;
93+
emit Register(_msgSender, idCounter);
8894
}
8995

9096
/*//////////////////////////////////////////////////////////////
@@ -98,13 +104,14 @@ contract AccountRegistry {
98104
* @param to The address to transfer the id to.
99105
*/
100106
function transfer(address to) external payable {
101-
uint256 id = idOf[msg.sender];
107+
address _msgSender = _msgSender();
108+
uint256 id = idOf[_msgSender];
102109

103110
if (id == 0) revert ZeroId();
104111

105112
if (idOf[to] != 0) revert HasId();
106113

107-
_unsafeTransfer(id, msg.sender, to);
114+
_unsafeTransfer(id, _msgSender, to);
108115
}
109116

110117
/**
@@ -131,12 +138,12 @@ contract AccountRegistry {
131138
//////////////////////////////////////////////////////////////*/
132139

133140
/**
134-
* INVARIANT 1: idOf[address] != 0 if msg.sender == recoveryOf[idOf[address]] during
141+
* INVARIANT 1: idOf[address] != 0 if _msgSender() == recoveryOf[idOf[address]] during
135142
* invocation of requestRecovery, completeRecovery and cancelRecovery
136143
*
137144
* recoveryOf[idOf[address]] != address(0) only if idOf[address] != 0 [setRecoveryAddress]
138145
* when idOf[address] == 0, recoveryof[idOf[address]] also == address(0) [_unsafeTransfer]
139-
* msg.sender != address(0) [by definition]
146+
* _msgSender() != address(0) [by definition]
140147
*
141148
* INVARIANT 2: idOf[address] != 0 if recoveryClockOf[idOf[address]] != 0
142149
*
@@ -155,7 +162,7 @@ contract AccountRegistry {
155162
* @param recoveryAddress the address to set as the recovery.
156163
*/
157164
function setRecoveryAddress(address recoveryAddress) external payable {
158-
uint256 id = idOf[msg.sender];
165+
uint256 id = idOf[_msgSender()];
159166

160167
if (id == 0) revert ZeroId();
161168

@@ -177,7 +184,7 @@ contract AccountRegistry {
177184
function requestRecovery(address from, address to) external payable {
178185
uint256 id = idOf[from];
179186

180-
if (msg.sender != recoveryOf[id]) revert Unauthorized();
187+
if (_msgSender() != recoveryOf[id]) revert Unauthorized();
181188
if (idOf[to] != 0) revert HasId();
182189

183190
recoveryClockOf[id] = block.timestamp;
@@ -197,7 +204,7 @@ contract AccountRegistry {
197204
uint256 id = idOf[from];
198205
address destination = recoveryDestinationOf[id];
199206

200-
if (msg.sender != recoveryOf[id]) revert Unauthorized();
207+
if (_msgSender() != recoveryOf[id]) revert Unauthorized();
201208
if (recoveryClockOf[id] == 0) revert NoRecovery();
202209

203210
if (block.timestamp < recoveryClockOf[id] + 259_200) revert Escrow();
@@ -218,7 +225,9 @@ contract AccountRegistry {
218225
function cancelRecovery(address from) external payable {
219226
uint256 id = idOf[from];
220227

221-
if (msg.sender != from && msg.sender != recoveryOf[id]) revert Unauthorized();
228+
address _msgSender = _msgSender();
229+
230+
if (_msgSender != from && _msgSender != recoveryOf[id]) revert Unauthorized();
222231
if (recoveryClockOf[id] == 0) revert NoRecovery();
223232

224233
emit CancelRecovery(id);

src/Namespace.sol

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
pragma solidity ^0.8.15;
33

44
import {ERC721} from "../lib/solmate/src/tokens/ERC721.sol";
5-
65
import {Owned} from "../lib/solmate/src/auth/Owned.sol";
7-
86
import {FixedPointMathLib} from "../lib/solmate/src/utils/FixedPointMathLib.sol";
7+
import {ERC2771Context} from "../lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol";
98

109
error InsufficientFunds(); // The transaction does not have enough money to pay for this.
1110
error Unauthorized(); // The caller is not authorized to perform this action.
@@ -31,7 +30,7 @@ error InvalidRecovery(); // The recovery address is being set to the custody add
3130
* @title Namespace
3231
* @author varunsrin
3332
*/
34-
contract Namespace is ERC721, Owned {
33+
contract Namespace is ERC721, Owned, ERC2771Context {
3534
using FixedPointMathLib for uint256;
3635

3736
/*//////////////////////////////////////////////////////////////
@@ -116,8 +115,9 @@ contract Namespace is ERC721, Owned {
116115
string memory _name,
117116
string memory _symbol,
118117
address _owner,
119-
address _vault
120-
) ERC721(_name, _symbol) Owned(_owner) {
118+
address _vault,
119+
address _trustedForwarder
120+
) ERC721(_name, _symbol) Owned(_owner) ERC2771Context(_trustedForwarder) {
121121
vault = _vault;
122122
}
123123

@@ -199,7 +199,7 @@ contract Namespace is ERC721, Owned {
199199
expiryOf[tokenId] = timestampOfYear(currYear() + 1);
200200
}
201201

202-
payable(msg.sender).transfer(msg.value - _currYearFee);
202+
payable(_msgSender()).transfer(msg.value - _currYearFee);
203203
}
204204

205205
/**
@@ -227,7 +227,7 @@ contract Namespace is ERC721, Owned {
227227

228228
emit Renew(tokenId, expiryOf[tokenId]);
229229

230-
payable(msg.sender).transfer(msg.value - fee);
230+
payable(_msgSender()).transfer(msg.value - fee);
231231
}
232232

233233
/**
@@ -267,14 +267,16 @@ contract Namespace is ERC721, Owned {
267267

268268
if (msg.value < price) revert InsufficientFunds();
269269

270-
_unsafeTransfer(msg.sender, tokenId);
270+
address _msgSender = _msgSender();
271+
272+
_unsafeTransfer(_msgSender, tokenId);
271273

272274
unchecked {
273275
// timestampOfYear(currentYear) is taken from a pre-determined list and cannot overflow
274276
expiryOf[tokenId] = timestampOfYear(currYear() + 1);
275277
}
276278

277-
payable(msg.sender).transfer(msg.value - price);
279+
payable(_msgSender).transfer(msg.value - price);
278280
}
279281

280282
/*//////////////////////////////////////////////////////////////
@@ -341,7 +343,7 @@ contract Namespace is ERC721, Owned {
341343
* set to zero to disable the recovery functionality.
342344
*/
343345
function setRecoveryAddress(uint256 tokenId, address recoveryAddress) external payable {
344-
if (ownerOf(tokenId) != msg.sender) revert Unauthorized();
346+
if (ownerOf(tokenId) != _msgSender()) revert Unauthorized();
345347

346348
recoveryOf[tokenId] = recoveryAddress;
347349
emit SetRecoveryAddress(recoveryAddress, tokenId);
@@ -365,7 +367,7 @@ contract Namespace is ERC721, Owned {
365367
if (to == address(0)) revert InvalidRecovery();
366368

367369
// Invariant 3 ensures that a request cannot be made after ownership change without consent
368-
if (msg.sender != recoveryOf[tokenId]) revert Unauthorized();
370+
if (_msgSender() != recoveryOf[tokenId]) revert Unauthorized();
369371

370372
recoveryClockOf[tokenId] = block.timestamp;
371373
recoveryDestinationOf[tokenId] = to;
@@ -383,7 +385,7 @@ contract Namespace is ERC721, Owned {
383385
if (block.timestamp >= expiryOf[tokenId]) revert Unauthorized();
384386

385387
// Invariant 3 prevents unauthorized access if the name has been re-posessed by another.
386-
if (msg.sender != recoveryOf[tokenId]) revert Unauthorized();
388+
if (_msgSender() != recoveryOf[tokenId]) revert Unauthorized();
387389

388390
// Invariant 3 ensures that a recovery request cannot be compeleted after a change of
389391
// ownership without explicit consent from the new owner
@@ -408,7 +410,8 @@ contract Namespace is ERC721, Owned {
408410
* @param tokenId the uint256 representation of the username.
409411
*/
410412
function cancelRecovery(uint256 tokenId) external payable {
411-
if (msg.sender != _ownerOf[tokenId] && msg.sender != recoveryOf[tokenId])
413+
address _msgSender = _msgSender();
414+
if (_msgSender != _ownerOf[tokenId] && _msgSender != recoveryOf[tokenId])
412415
revert Unauthorized();
413416

414417
if (recoveryClockOf[tokenId] == 0) revert NoRecovery();

test/AccountRegistry.t.sol

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ contract AccountRegistryTest is Test {
2525
CONSTRUCTORS
2626
//////////////////////////////////////////////////////////////*/
2727

28-
function setUp() public {
29-
accountRegistry = new AccountRegistry();
30-
}
31-
3228
address alice = address(0x123);
3329
address bob = address(0x456);
3430
address charlie = address(0x789);
31+
address trustedForwarder = address(0xC8223c8AD514A19Cc10B0C94c39b52D4B43ee61A);
3532
uint256 escrowPeriod = 259_200;
3633

34+
function setUp() public {
35+
accountRegistry = new AccountRegistry(trustedForwarder);
36+
}
37+
3738
/*//////////////////////////////////////////////////////////////
3839
REGISTRATION TESTS
3940
//////////////////////////////////////////////////////////////*/

0 commit comments

Comments
 (0)