Skip to content

Commit 7105693

Browse files
authored
Change NoncesKeyed._useNonce to return a keyed value (#5312)
1 parent a277d47 commit 7105693

File tree

2 files changed

+63
-17
lines changed

2 files changed

+63
-17
lines changed

contracts/utils/NoncesKeyed.sol

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ abstract contract NoncesKeyed is Nonces {
1313

1414
/// @dev Returns the next unused nonce for an address and key. Result contains the key prefix.
1515
function nonces(address owner, uint192 key) public view virtual returns (uint256) {
16-
return key == 0 ? nonces(owner) : ((uint256(key) << 64) | _nonces[owner][key]);
16+
return key == 0 ? nonces(owner) : _pack(key, _nonces[owner][key]);
1717
}
1818

1919
/**
@@ -27,7 +27,7 @@ abstract contract NoncesKeyed is Nonces {
2727
// decremented or reset. This guarantees that the nonce never overflows.
2828
unchecked {
2929
// It is important to do x++ and not ++x here.
30-
return key == 0 ? _useNonce(owner) : _nonces[owner][key]++;
30+
return key == 0 ? _useNonce(owner) : _pack(key, _nonces[owner][key]++);
3131
}
3232
}
3333

@@ -39,7 +39,13 @@ abstract contract NoncesKeyed is Nonces {
3939
* - use the last 24 bytes for the nonce
4040
*/
4141
function _useCheckedNonce(address owner, uint256 keyNonce) internal virtual override {
42-
_useCheckedNonce(owner, uint192(keyNonce >> 64), uint64(keyNonce));
42+
(uint192 key, ) = _unpack(keyNonce);
43+
if (key == 0) {
44+
super._useCheckedNonce(owner, keyNonce);
45+
} else {
46+
uint256 current = _useNonce(owner, key);
47+
if (keyNonce != current) revert InvalidAccountNonce(owner, current);
48+
}
4349
}
4450

4551
/**
@@ -48,13 +54,16 @@ abstract contract NoncesKeyed is Nonces {
4854
* This version takes the key and the nonce as two different parameters.
4955
*/
5056
function _useCheckedNonce(address owner, uint192 key, uint64 nonce) internal virtual {
51-
if (key == 0) {
52-
super._useCheckedNonce(owner, nonce);
53-
} else {
54-
uint256 current = _useNonce(owner, key);
55-
if (nonce != current) {
56-
revert InvalidAccountNonce(owner, current);
57-
}
58-
}
57+
_useCheckedNonce(owner, _pack(key, nonce));
58+
}
59+
60+
/// @dev Pack key and nonce into a keyNonce
61+
function _pack(uint192 key, uint64 nonce) private pure returns (uint256) {
62+
return (uint256(key) << 64) | nonce;
63+
}
64+
65+
/// @dev Unpack a keyNonce into its key and nonce components
66+
function _unpack(uint256 keyNonce) private pure returns (uint192 key, uint64 nonce) {
67+
return (uint192(keyNonce >> 64), uint64(keyNonce));
5968
}
6069
}

test/utils/Nonces.behavior.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ function shouldBehaveLikeNoncesKeyed() {
8686

8787
await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(0n)))
8888
.to.emit(this.mock, 'return$_useNonce_address_uint192')
89-
.withArgs(1n);
89+
.withArgs(keyOffset(0n) + 1n);
9090

9191
expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 2n);
9292
expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n);
@@ -98,18 +98,18 @@ function shouldBehaveLikeNoncesKeyed() {
9898

9999
await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n)))
100100
.to.emit(this.mock, 'return$_useNonce_address_uint192')
101-
.withArgs(0n);
101+
.withArgs(keyOffset(17n) + 0n);
102102

103103
await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n)))
104104
.to.emit(this.mock, 'return$_useNonce_address_uint192')
105-
.withArgs(1n);
105+
.withArgs(keyOffset(17n) + 1n);
106106

107107
expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n);
108108
expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 2n);
109109
});
110110
});
111111

112-
describe('_useCheckedNonce', function () {
112+
describe('_useCheckedNonce(address, uint256)', function () {
113113
it('default variant uses key 0', async function () {
114114
const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n));
115115

@@ -135,12 +135,49 @@ function shouldBehaveLikeNoncesKeyed() {
135135
// reuse same nonce
136136
await expect(this.mock.$_useCheckedNonce(sender, currentNonce))
137137
.to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
138-
.withArgs(sender, 1);
138+
.withArgs(sender, currentNonce + 1n);
139139

140140
// use "future" nonce too early
141141
await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 10n))
142142
.to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
143-
.withArgs(sender, 1);
143+
.withArgs(sender, currentNonce + 1n);
144+
});
145+
});
146+
147+
describe('_useCheckedNonce(address, uint192, uint64)', function () {
148+
const MASK = 0xffffffffffffffffn;
149+
150+
it('default variant uses key 0', async function () {
151+
const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n));
152+
153+
await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(0n), currentNonce);
154+
155+
expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(currentNonce + 1n);
156+
});
157+
158+
it('use nonce at another key', async function () {
159+
const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(17n));
160+
161+
await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(17n), currentNonce & MASK);
162+
163+
expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(currentNonce + 1n);
164+
});
165+
166+
it('reverts when nonce is not the expected', async function () {
167+
const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(42n));
168+
169+
// use and increment
170+
await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), currentNonce & MASK);
171+
172+
// reuse same nonce
173+
await expect(this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), currentNonce & MASK))
174+
.to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
175+
.withArgs(sender, currentNonce + 1n);
176+
177+
// use "future" nonce too early
178+
await expect(this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), (currentNonce & MASK) + 10n))
179+
.to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
180+
.withArgs(sender, currentNonce + 1n);
144181
});
145182
});
146183
});

0 commit comments

Comments
 (0)