Skip to content

Commit

Permalink
rewrite as a proper override to Nonces (that calls super)
Browse files Browse the repository at this point in the history
  • Loading branch information
Amxx committed Oct 22, 2024
1 parent d9ea82e commit 38cee68
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 36 deletions.
2 changes: 2 additions & 0 deletions contracts/mocks/Stateless.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {Heap} from "../utils/structs/Heap.sol";
import {Math} from "../utils/math/Math.sol";
import {MerkleProof} from "../utils/cryptography/MerkleProof.sol";
import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol";
import {Nonces} from "../utils/Nonces.sol";
import {NoncesKeyed} from "../utils/NoncesKeyed.sol";
import {P256} from "../utils/cryptography/P256.sol";
import {Panic} from "../utils/Panic.sol";
import {Packing} from "../utils/Packing.sol";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,26 @@ import {Nonces} from "./Nonces.sol";
*
* Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system].
*/
abstract contract NoncesSemiAbstracted is Nonces {
abstract contract NoncesKeyed is Nonces {
mapping(address owner => mapping(uint192 key => uint64)) private _nonces;

/**
* @dev Returns the next unused nonce for an address.
*/
function nonces(address owner) public view virtual override returns (uint256) {
return nonces(owner, 0);
}

/**
* @dev Returns the next unused nonce for an address and key. Result contains the key prefix.
*/
/// @dev Returns the next unused nonce for an address and key. Result contains the key prefix.
function nonces(address owner, uint192 key) public view virtual returns (uint256) {
return (uint256(key) << 64) | _nonces[owner][key];
}

/**
* @dev Consumes a nonce from the default key.
*
* Returns the current value and increments nonce.
*/
function _useNonce(address owner) internal virtual override returns (uint256) {
return _useNonce(owner, 0);
return key == 0 ? nonces(owner) : (uint256(key) << 64) | _nonces[owner][key];
}

/**
* @dev Consumes a nonce from the given key.
* @dev Consumes the next unsused nonce for an address and key.

Check failure on line 20 in contracts/utils/NoncesKeyed.sol

View workflow job for this annotation

GitHub Actions / codespell

unsused ==> unused
*
* Returns the current value and increments nonce.
* Returns the current value without the key prefix. Consumed nonce is increased, so calling this functions twice
* with the same arguments will return different (sequential) results.
*/
function _useNonce(address owner, uint192 key) internal virtual returns (uint256) {
// For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
// decremented or reset. This guarantees that the nonce never overflows.
unchecked {
// It is important to do x++ and not ++x here.
return _nonces[owner][key]++;
return key == 0 ? _useNonce(owner) : _nonces[owner][key]++;
}
}

Expand All @@ -65,9 +48,13 @@ abstract contract NoncesSemiAbstracted is Nonces {
* This version takes the key and the nonce as two different parameters.
*/
function _useCheckedNonce(address owner, uint192 key, uint64 nonce) internal virtual {
uint256 current = _useNonce(owner, key);
if (nonce != current) {
revert InvalidAccountNonce(owner, current);
if (key == 0) {
super._useCheckedNonce(owner, nonce);
} else {
uint256 current = _useNonce(owner, key);
if (nonce != current) {
revert InvalidAccountNonce(owner, current);
}
}
}
}
4 changes: 2 additions & 2 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {ReentrancyGuardTransient}: Variant of {ReentrancyGuard} that uses transient storage (https://eips.ethereum.org/EIPS/eip-1153[EIP-1153]).
* {Pausable}: A common emergency response mechanism that can pause functionality while a remediation is pending.
* {Nonces}: Utility for tracking and verifying address nonces that only increment.
* {NoncesSemiAbstracted}: Alternative to {Nonces}, that support key-ed nonces following https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 speciciations].
* {NoncesKeyed}: Alternative to {Nonces}, that support key-ed nonces following https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 speciciations].
* {ERC165}, {ERC165Checker}: Utilities for inspecting interfaces supported by contracts.
* {BitMaps}: A simple library to manage boolean value mapped to a numerical index in an efficient way.
* {EnumerableMap}: A type like Solidity's https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`], but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`).
Expand Down Expand Up @@ -86,7 +86,7 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable

{{Nonces}}

{{NoncesSemiAbstracted}}
{{NoncesKeyed}}

== Introspection

Expand Down
6 changes: 3 additions & 3 deletions test/utils/Nonces.behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ function shouldBehaveLikeNonces() {
});
}

function shouldBehaveLikeNoncesSemiAbstracted() {
describe("should implement ERC-4337's semi-abstracted nonces", function () {
function shouldBehaveLikeNoncesKeyed() {
describe('should support nonces with keys', function () {
const sender = ethers.Wallet.createRandom();

const keyOffset = key => key << 64n;
Expand Down Expand Up @@ -148,5 +148,5 @@ function shouldBehaveLikeNoncesSemiAbstracted() {

module.exports = {
shouldBehaveLikeNonces,
shouldBehaveLikeNoncesSemiAbstracted,
shouldBehaveLikeNoncesKeyed,
};
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
const { ethers } = require('hardhat');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { shouldBehaveLikeNonces, shouldBehaveLikeNoncesSemiAbstracted } = require('./Nonces.behavior');
const { shouldBehaveLikeNonces, shouldBehaveLikeNoncesKeyed } = require('./Nonces.behavior');

async function fixture() {
const mock = await ethers.deployContract('$NoncesSemiAbstracted');
const mock = await ethers.deployContract('$NoncesKeyed');
return { mock };
}

describe('NoncesSemiAbstracted', function () {
describe('NoncesKeyed', function () {
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});

shouldBehaveLikeNonces();
shouldBehaveLikeNoncesSemiAbstracted();
shouldBehaveLikeNoncesKeyed();
});

0 comments on commit 38cee68

Please # to comment.