Skip to content

Commit fe0477a

Browse files
committed
using bitmap for nonces
1 parent 1bc6d91 commit fe0477a

File tree

2 files changed

+75
-31
lines changed

2 files changed

+75
-31
lines changed

src/allocators/ERC7683Allocator.sol

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ contract ERC7683Allocator is SimpleAllocator, IERC7683Allocator {
2929
/// @notice keccak256("Mandate(uint256 chainId,address tribunal,address recipient,uint256 expires,address token,uint256 minimumAmount,uint256 baselinePriorityFee,uint256 scalingFactor,bytes32 salt)")
3030
bytes32 internal constant MANDATE_TYPEHASH = 0x52c75464356e20084ae43acac75087fbf0e0c678e7ffa326f369f37e88696036;
3131

32-
bytes32 immutable _COMPACT_DOMAIN_SEPARATOR;
32+
/// @notice uint256(uint8(keccak256("ERC7683Allocator.nonce")))
33+
uint8 internal constant NONCE_MASTER_SLOT_SEED = 0x39;
3334

34-
mapping(uint256 nonce => bool nonceUsed) private _userNonce;
35+
bytes32 immutable _COMPACT_DOMAIN_SEPARATOR;
3536

3637
constructor(address compactContract_, uint256 minWithdrawalDelay_, uint256 maxWithdrawalDelay_)
3738
SimpleAllocator(compactContract_, minWithdrawalDelay_, maxWithdrawalDelay_)
@@ -105,22 +106,24 @@ contract ERC7683Allocator is SimpleAllocator, IERC7683Allocator {
105106

106107
/// @inheritdoc IERC7683Allocator
107108
function checkNonce(address sponsor_, uint256 nonce_) external view returns (bool nonceFree_) {
108-
_checkNonce(sponsor_, nonce_);
109-
nonceFree_ = !_userNonce[nonce_];
109+
uint96 nonceWithoutAddress = _checkNonce(sponsor_, nonce_);
110+
uint96 wordPos = uint96(nonceWithoutAddress / 256);
111+
uint96 bitPos = uint96(nonceWithoutAddress % 256);
112+
assembly ("memory-safe") {
113+
let masterSlot := or(shl(248, NONCE_MASTER_SLOT_SEED), or(shl(88, sponsor_), wordPos))
114+
nonceFree_ := iszero(and(sload(masterSlot), shl(bitPos, 1)))
115+
}
110116
return nonceFree_;
111117
}
112118

113119
function _open(OrderData memory orderData_, uint32 fillDeadline_, address sponsor_, bytes memory sponsorSignature_)
114120
internal
115121
{
116122
// Enforce a nonce where the most significant 96 bits are the nonce and the least significant 160 bits are the sponsor
117-
_checkNonce(sponsor_, orderData_.nonce);
123+
uint96 nonceWithoutAddress = _checkNonce(sponsor_, orderData_.nonce);
118124

119-
// Check the nonce
120-
if (_userNonce[orderData_.nonce]) {
121-
revert NonceAlreadyInUse(orderData_.nonce);
122-
}
123-
_userNonce[orderData_.nonce] = true;
125+
// Set a nonce or revert if it is already used
126+
_setNonce(sponsor_, nonceWithoutAddress);
124127

125128
// We do not enforce a specific tribunal or arbiter. This will allow to support new arbiters and tribunals after the deployment of the allocator
126129
// Going with an immutable arbiter and tribunal would limit support for new chains with a fully decentralized allocator
@@ -180,36 +183,31 @@ contract ERC7683Allocator is SimpleAllocator, IERC7683Allocator {
180183
);
181184
}
182185

183-
function _lockTokens(OrderData memory orderData_, address sponsor_, uint256 identifier)
186+
function _lockTokens(OrderData memory orderData_, address sponsor_, uint256 nonce)
184187
internal
185188
returns (bytes32 tokenHash_)
186189
{
187-
return
188-
_lockTokens(orderData_.arbiter, sponsor_, identifier, orderData_.expires, orderData_.id, orderData_.amount);
190+
return _lockTokens(orderData_.arbiter, sponsor_, nonce, orderData_.expires, orderData_.id, orderData_.amount);
189191
}
190192

191-
function _lockTokens(
192-
address arbiter,
193-
address sponsor,
194-
uint256 identifier,
195-
uint256 expires,
196-
uint256 id,
197-
uint256 amount
198-
) internal returns (bytes32 tokenHash_) {
193+
function _lockTokens(address arbiter, address sponsor, uint256 nonce, uint256 expires, uint256 id, uint256 amount)
194+
internal
195+
returns (bytes32 tokenHash_)
196+
{
199197
tokenHash_ = _checkAllocation(
200-
Compact({arbiter: arbiter, sponsor: sponsor, nonce: identifier, expires: expires, id: id, amount: amount})
198+
Compact({arbiter: arbiter, sponsor: sponsor, nonce: nonce, expires: expires, id: id, amount: amount})
201199
);
202200
_claim[tokenHash_] = expires;
203201
_amount[tokenHash_] = amount;
204-
_nonce[tokenHash_] = identifier;
202+
_nonce[tokenHash_] = nonce;
205203

206204
return tokenHash_;
207205
}
208206

209207
function _resolveOrder(
210208
address sponsor,
211209
uint32 fillDeadline,
212-
uint256 identifier,
210+
uint256 nonce,
213211
OrderData memory orderData,
214212
bytes memory sponsorSignature
215213
) internal view returns (ResolvedCrossChainOrder memory) {
@@ -267,26 +265,46 @@ contract ERC7683Allocator is SimpleAllocator, IERC7683Allocator {
267265
originChainId: block.chainid,
268266
openDeadline: uint32(orderData.expires),
269267
fillDeadline: fillDeadline,
270-
orderId: bytes32(identifier),
268+
orderId: bytes32(nonce),
271269
maxSpent: maxSpent,
272270
minReceived: minReceived,
273271
fillInstructions: fillInstructions
274272
});
275273
return resolvedOrder;
276274
}
277275

278-
function _checkNonce(address sponsor_, uint256 nonce_) internal pure {
276+
function _checkNonce(address sponsor_, uint256 nonce_) internal pure returns (uint96 nonce) {
279277
// Enforce a nonce where the least significant 96 bits are the nonce and the most significant 160 bits are the sponsors address
280278
// This ensures that the nonce is unique for a given sponsor
281279
address expectedSponsor;
282280
assembly ("memory-safe") {
283281
expectedSponsor := shr(96, nonce_)
282+
nonce := shr(160, shl(160, nonce_))
284283
}
285284
if (expectedSponsor != sponsor_) {
286285
revert InvalidNonce(nonce_);
287286
}
288287
}
289288

289+
function _setNonce(address sponsor_, uint96 nonce_) internal {
290+
bool used;
291+
uint96 wordPos = nonce_ / 256; // uint96 divided by 256 means it becomes a uint88 (11 bytes)
292+
uint96 bitPos = nonce_ % 256;
293+
assembly ("memory-safe") {
294+
// [NONCE_MASTER_SLOT_SEED - 1 byte][sponsor address - 20 bytes][wordPos - 11 bytes]
295+
let masterSlot := or(shl(248, NONCE_MASTER_SLOT_SEED), or(shl(88, sponsor_), wordPos))
296+
let previouslyUsedNonces := sload(masterSlot)
297+
if and(previouslyUsedNonces, shl(bitPos, 1)) { used := 1 }
298+
{
299+
let usedNonces := or(previouslyUsedNonces, shl(bitPos, 1))
300+
sstore(masterSlot, usedNonces)
301+
}
302+
}
303+
if (used) {
304+
revert NonceAlreadyInUse(uint256(bytes32(abi.encodePacked(sponsor_, nonce_))));
305+
}
306+
}
307+
290308
function _idToToken(uint256 id_) internal pure returns (address token_) {
291309
assembly ("memory-safe") {
292310
token_ := shr(96, shl(96, id_))

test/ERC7683Allocator.t.sol

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -925,10 +925,10 @@ contract ERC7683Allocator_getCompactWitnessTypeString is MocksSetup {
925925
}
926926

927927
contract ERC7683Allocator_checkNonce is OnChainCrossChainOrderData {
928-
function test_revert_checkNonce(uint256 nonce_) public {
928+
function test_revert_invalidNonce(uint256 nonce_) public {
929929
address expectedSponsor;
930930
assembly ("memory-safe") {
931-
expectedSponsor := shr(96, shl(96, nonce_))
931+
expectedSponsor := shr(96, nonce_)
932932
}
933933
vm.assume(user != expectedSponsor);
934934

@@ -962,13 +962,39 @@ contract ERC7683Allocator_checkNonce is OnChainCrossChainOrderData {
962962
bytes32 typeHash = _getTypeHash();
963963
compactContract.register(claimHash, typeHash, defaultResetPeriodTimestamp);
964964

965+
(IOriginSettler.OnchainCrossChainOrder memory onChainCrossChainOrder_) = _getOnChainCrossChainOrder();
966+
erc7683Allocator.open(onChainCrossChainOrder_);
967+
968+
vm.assertEq(erc7683Allocator.checkNonce(user, defaultNonce), false);
965969
vm.stopPrank();
970+
}
971+
972+
function test_checkNonce_fuzz(uint8 nonce_) public {
973+
uint256 nonce = uint256(bytes32(abi.encodePacked(user, uint96(nonce_))));
974+
975+
bool sameNonce = nonce == defaultNonce;
976+
977+
// Deposit tokens
978+
vm.startPrank(user);
979+
usdc.mint(user, defaultAmount);
980+
usdc.approve(address(compactContract), defaultAmount);
981+
compactContract.deposit(
982+
address(usdc), address(erc7683Allocator), defaultResetPeriod, defaultScope, defaultAmount, user
983+
);
984+
985+
// register a claim
986+
Compact memory compact_ = _getCompact();
987+
Mandate memory mandate_ = _getMandate();
988+
989+
bytes32 claimHash = _hashCompact(compact_, mandate_);
990+
bytes32 typeHash = _getTypeHash();
991+
compactContract.register(claimHash, typeHash, defaultResetPeriodTimestamp);
966992

967993
(IOriginSettler.OnchainCrossChainOrder memory onChainCrossChainOrder_) = _getOnChainCrossChainOrder();
968-
vm.prank(user);
969994
erc7683Allocator.open(onChainCrossChainOrder_);
970995

971-
vm.prank(user);
972-
vm.assertEq(erc7683Allocator.checkNonce(user, defaultNonce), false);
996+
vm.assertEq(erc7683Allocator.checkNonce(user, nonce), !sameNonce);
997+
998+
vm.stopPrank();
973999
}
9741000
}

0 commit comments

Comments
 (0)