Skip to content

Commit f85aa15

Browse files
authored
fix: add commit-reveal delay to prevent front-running (#15)
* fix: update foundry settings * fix: add commit-reveal delay to prevent frontrunning
1 parent 3069752 commit f85aa15

File tree

4 files changed

+75
-46
lines changed

4 files changed

+75
-46
lines changed

.gas-snapshot

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,52 +19,53 @@ AccountRegistryTest:testRegister() (gas: 89924)
1919
AccountRegistryTest:testRequestRecovery() (gas: 144852)
2020
AccountRegistryTest:testSetRecoveryAddress() (gas: 95427)
2121
AccountRegistryTest:testTransfer() (gas: 75750)
22-
NameSpaceTest:testBidAfterOneStep() (gas: 217603)
23-
NameSpaceTest:testBidAndOverpay() (gas: 213134)
24-
NameSpaceTest:testBidFlatRate() (gas: 215981)
25-
NameSpaceTest:testBidImmediately() (gas: 229318)
26-
NameSpaceTest:testBidOnHundredthStep() (gas: 215959)
27-
NameSpaceTest:testBidOnPenultimateStep() (gas: 215971)
28-
NameSpaceTest:testBidShouldClearRecovery() (gas: 259941)
29-
NameSpaceTest:testCancelRecoveryFromCustodyAddress() (gas: 221316)
30-
NameSpaceTest:testCancelRecoveryFromRecoveryAddress() (gas: 220922)
31-
NameSpaceTest:testCannotBidIfRegistrable() (gas: 30961)
32-
NameSpaceTest:testCannotBidUnlessBiddable() (gas: 173313)
33-
NameSpaceTest:testCannotCancelRecoveryIfNotStarted() (gas: 185540)
34-
NameSpaceTest:testCannotCancelRecoveryIfUnauthorized() (gas: 232109)
35-
NameSpaceTest:testCannotCommitWithInvalidName() (gas: 22905)
36-
NameSpaceTest:testCannotCompleteRecoveryIfExpired() (gas: 244138)
37-
NameSpaceTest:testCannotCompleteRecoveryIfNotStarted() (gas: 189819)
38-
NameSpaceTest:testCannotCompleteRecoveryIfUnauthorized() (gas: 217405)
39-
NameSpaceTest:testCannotCompleteRecoveryWhenInEscrow() (gas: 233743)
22+
NameSpaceTest:testBidAfterOneStep() (gas: 220057)
23+
NameSpaceTest:testBidAndOverpay() (gas: 215676)
24+
NameSpaceTest:testBidFlatRate() (gas: 218435)
25+
NameSpaceTest:testBidImmediately() (gas: 231728)
26+
NameSpaceTest:testBidOnHundredthStep() (gas: 218435)
27+
NameSpaceTest:testBidOnPenultimateStep() (gas: 218425)
28+
NameSpaceTest:testBidShouldClearRecovery() (gas: 261975)
29+
NameSpaceTest:testCancelRecoveryFromCustodyAddress() (gas: 223880)
30+
NameSpaceTest:testCancelRecoveryFromRecoveryAddress() (gas: 223486)
31+
NameSpaceTest:testCannotBidIfRegistrable() (gas: 30917)
32+
NameSpaceTest:testCannotBidUnlessBiddable() (gas: 175767)
33+
NameSpaceTest:testCannotCancelRecoveryIfNotStarted() (gas: 188104)
34+
NameSpaceTest:testCannotCancelRecoveryIfUnauthorized() (gas: 234673)
35+
NameSpaceTest:testCannotCommitWithInvalidName() (gas: 22838)
36+
NameSpaceTest:testCannotCompleteRecoveryIfExpired() (gas: 246680)
37+
NameSpaceTest:testCannotCompleteRecoveryIfNotStarted() (gas: 192361)
38+
NameSpaceTest:testCannotCompleteRecoveryIfUnauthorized() (gas: 219947)
39+
NameSpaceTest:testCannotCompleteRecoveryWhenInEscrow() (gas: 236285)
4040
NameSpaceTest:testCannotReclaimUnlessMinted() (gas: 17407)
41-
NameSpaceTest:testCannotRegisterWithoutPayment() (gas: 85851)
42-
NameSpaceTest:testCannotRenewIfBiddable() (gas: 162869)
43-
NameSpaceTest:testCannotRenewIfRegistered() (gas: 162953)
41+
NameSpaceTest:testCannotRegisterBeforeDelay() (gas: 90808)
42+
NameSpaceTest:testCannotRegisterWithoutPayment() (gas: 85835)
43+
NameSpaceTest:testCannotRenewIfBiddable() (gas: 165411)
44+
NameSpaceTest:testCannotRenewIfRegistered() (gas: 165495)
4445
NameSpaceTest:testCannotRenewIfRegistrable() (gas: 31908)
4546
NameSpaceTest:testCannotRenewIfRegistrable2() (gas: 31950)
46-
NameSpaceTest:testCannotRenewWithoutPayment() (gas: 156012)
47+
NameSpaceTest:testCannotRenewWithoutPayment() (gas: 158554)
4748
NameSpaceTest:testCannotRequestRecoveryIfRegistrable() (gas: 26459)
48-
NameSpaceTest:testCannotRequestRecoveryToZeroAddr() (gas: 185400)
49-
NameSpaceTest:testCannotRequestRecoveryUnlessAuthorized() (gas: 163808)
50-
NameSpaceTest:testCannotSetRecoveryIfExpired() (gas: 164996)
49+
NameSpaceTest:testCannotRequestRecoveryToZeroAddr() (gas: 187942)
50+
NameSpaceTest:testCannotRequestRecoveryUnlessAuthorized() (gas: 166372)
51+
NameSpaceTest:testCannotSetRecoveryIfExpired() (gas: 167538)
5152
NameSpaceTest:testCannotSetRecoveryIfRegistrable() (gas: 20896)
52-
NameSpaceTest:testCannotSetRecoveryUnlessOwner() (gas: 158592)
53+
NameSpaceTest:testCannotSetRecoveryUnlessOwner() (gas: 161134)
5354
NameSpaceTest:testCurrYear() (gas: 78247)
5455
NameSpaceTest:testCurrYearPayment() (gas: 45881)
5556
NameSpaceTest:testGenerateCommit() (gas: 40350)
56-
NameSpaceTest:testOwnerOfRevertsIfExpired() (gas: 152771)
57+
NameSpaceTest:testOwnerOfRevertsIfExpired() (gas: 155313)
5758
NameSpaceTest:testOwnerOfRevertsIfRegistrable() (gas: 12872)
58-
NameSpaceTest:testReclaimBiddableNames() (gas: 181528)
59-
NameSpaceTest:testReclaimRegisteredNames() (gas: 190207)
60-
NameSpaceTest:testReclaimRenewableNames() (gas: 181560)
61-
NameSpaceTest:testReclaimResetsRecoveryState() (gas: 226069)
62-
NameSpaceTest:testRecoveryCompletion() (gas: 245134)
63-
NameSpaceTest:testRegister() (gas: 255587)
64-
NameSpaceTest:testRenewOther() (gas: 174360)
65-
NameSpaceTest:testRenewSelf() (gas: 171777)
66-
NameSpaceTest:testRenewWithOverpayment() (gas: 177327)
67-
NameSpaceTest:testRequestRecovery() (gas: 241593)
68-
NameSpaceTest:testSetRecoveryAddress() (gas: 189689)
69-
NameSpaceTest:testTransferFromCannotTransferExpiredName() (gas: 171463)
70-
NameSpaceTest:testTransferFromResetsRecovery() (gas: 225429)
59+
NameSpaceTest:testReclaimBiddableNames() (gas: 184070)
60+
NameSpaceTest:testReclaimRegisteredNames() (gas: 192240)
61+
NameSpaceTest:testReclaimRenewableNames() (gas: 184124)
62+
NameSpaceTest:testReclaimResetsRecoveryState() (gas: 228103)
63+
NameSpaceTest:testRecoveryCompletion() (gas: 247168)
64+
NameSpaceTest:testRegister() (gas: 259089)
65+
NameSpaceTest:testRenewOther() (gas: 176902)
66+
NameSpaceTest:testRenewSelf() (gas: 174319)
67+
NameSpaceTest:testRenewWithOverpayment() (gas: 179869)
68+
NameSpaceTest:testRequestRecovery() (gas: 244135)
69+
NameSpaceTest:testSetRecoveryAddress() (gas: 192253)
70+
NameSpaceTest:testTransferFromCannotTransferExpiredName() (gas: 173939)
71+
NameSpaceTest:testTransferFromResetsRecovery() (gas: 227445)

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[default]
1+
[profile.default]
22
src = 'src'
33
out = 'out'
44
libs = ['lib']

src/Namespace.sol

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ contract Namespace is ERC721, Owned {
5454
REGISTRATION STORAGE
5555
//////////////////////////////////////////////////////////////*/
5656

57-
// Mapping from commitment hash to state
58-
mapping(bytes32 => bool) public stateOf;
57+
// Mapping from commitment hash to block number
58+
mapping(bytes32 => uint256) public blockOf;
5959

6060
// Mapping from tokenID to expiration year
6161
mapping(uint256 => uint256) public expiryOf;
@@ -162,12 +162,15 @@ contract Namespace is ERC721, Owned {
162162
* @param commit the commitment hash to be persisted on-chain
163163
*/
164164
function makeCommit(bytes32 commit) public {
165-
stateOf[commit] = true;
165+
blockOf[commit] = block.timestamp;
166166
}
167167

168168
/**
169169
* @notice Mint a new username if a commitment was made previously and send it to the owner.
170170
*
171+
* @dev The registration must be made at least 5 blocks after commit to minimize front-running,
172+
* or approximately 1 minute after commit.
173+
*
171174
* @param username the username to register
172175
* @param owner the address that will claim the username
173176
* @param secret the secret that protects the commitment
@@ -182,8 +185,9 @@ contract Namespace is ERC721, Owned {
182185
uint256 _currYearFee = currYearFee();
183186
if (msg.value < _currYearFee) revert InsufficientFunds();
184187

185-
if (!stateOf[commit]) revert InvalidCommit();
186-
delete stateOf[commit];
188+
uint256 _commitBlock = blockOf[commit];
189+
if (_commitBlock == 0 || _commitBlock + 60 > block.timestamp) revert InvalidCommit();
190+
delete blockOf[commit];
187191

188192
uint256 tokenId = uint256(bytes32(username));
189193
if (expiryOf[tokenId] != 0) revert NotRegistrable();

test/Namespace.t.sol

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ contract NameSpaceTest is Test {
3131
address david = address(0x531);
3232

3333
uint256 escrowPeriod = 3 days;
34+
uint256 commitRegisterDelay = 60;
3435

3536
uint256 timestamp2023 = 1672531200; // Sun, Jan 1, 2023 0:00:00 GMT
3637
uint256 timestamp2024 = 1704067200; // Sun, Jan 1, 2024 0:00:00 GMT
@@ -109,6 +110,7 @@ contract NameSpaceTest is Test {
109110
namespace.makeCommit(commitHash);
110111

111112
// 3. Register the name alice, and deliver it to bob
113+
vm.warp(block.timestamp + commitRegisterDelay);
112114
vm.expectEmit(true, true, true, false);
113115
emit Transfer(address(0), bob, uint256(bytes32("alice")));
114116
uint256 balance = alice.balance;
@@ -122,11 +124,14 @@ contract NameSpaceTest is Test {
122124
// 5. Check that comitting and minting again fails
123125
namespace.makeCommit(commitHash);
124126
vm.expectRevert(NotRegistrable.selector);
127+
vm.warp(block.timestamp + commitRegisterDelay);
125128
namespace.register{value: 0.01 ether}("alice", bob, "secret");
126129

127130
// 6. Check that alice can still mint another name to bob
128131
bytes32 commitHashMorty = namespace.generateCommit("morty", bob, "secret");
129132
namespace.makeCommit(commitHashMorty);
133+
vm.warp(block.timestamp + commitRegisterDelay);
134+
130135
namespace.register{value: 0.01 ether}("morty", bob, "secret");
131136
assertEq(namespace.ownerOf(uint256(bytes32("morty"))), bob);
132137
assertEq(namespace.balanceOf(bob), 2);
@@ -181,6 +186,24 @@ contract NameSpaceTest is Test {
181186
vm.stopPrank();
182187
}
183188

189+
function testCannotRegisterBeforeDelay() public {
190+
// 1. Give alice money and fast forward to 2022 to begin registration.
191+
vm.deal(alice, 10_000 ether);
192+
vm.warp(aliceRegisterTs);
193+
194+
// 2. Make the commitment to register the name alice, but deliver it to bob
195+
vm.startPrank(alice);
196+
bytes32 commitHash = namespace.generateCommit("alice", bob, "secret");
197+
namespace.makeCommit(commitHash);
198+
199+
// 3. Try to register the name and fail
200+
vm.warp(block.timestamp + commitRegisterDelay - 1);
201+
vm.expectRevert(InvalidCommit.selector);
202+
namespace.register{value: 0.01 ether}("alice", bob, "secret");
203+
204+
vm.stopPrank();
205+
}
206+
184207
function testCannotRegisterWithInvalidNames(address owner, bytes32 secret) public {
185208
bytes16 incorrectUsername = "al{ce";
186209
bytes32 invalidCommit = keccak256(abi.encode(incorrectUsername, owner, secret));
@@ -1062,6 +1085,7 @@ contract NameSpaceTest is Test {
10621085
vm.startPrank(alice);
10631086
bytes32 commitHash = namespace.generateCommit("alice", alice, "secret");
10641087
namespace.makeCommit(commitHash);
1088+
vm.warp(block.timestamp + commitRegisterDelay);
10651089

10661090
namespace.register{value: namespace.fee()}("alice", alice, "secret");
10671091
assertEq(namespace.expiryOf(aliceTokenId), timestamp2023);

0 commit comments

Comments
 (0)