Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit 92eb577

Browse files
haider-rsdvc94ch
andauthored
Replace EnumerableSet with OZ EnumerableMap (#56)
Co-authored-by: David Craven <[email protected]>
1 parent 467c3a9 commit 92eb577

16 files changed

+645
-992
lines changed

src/GasUtils.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33

44
pragma solidity >=0.8.20;
55

6-
import {BranchlessMath} from "./utils/BranchlessMath.sol";
6+
import {PrimitiveUtils} from "./Primitives.sol";
77

88
/**
99
* @dev Utilities for compute the GMP gas price, gas cost and gas needed.
1010
*/
1111
library GasUtils {
12-
using BranchlessMath for uint256;
12+
using PrimitiveUtils for uint256;
1313

1414
/**
1515
* @dev Compute the amount of gas used by the `GatewayProxy`.

src/Gateway.sol

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
pragma solidity >=0.8.0;
55

66
import {Schnorr} from "../lib/frost-evm/sol/Schnorr.sol";
7-
import {BranchlessMath} from "./utils/BranchlessMath.sol";
87
import {GasUtils} from "./GasUtils.sol";
98
import {RouteStore} from "./storage/Routes.sol";
109
import {ShardStore} from "./storage/Shards.sol";
@@ -32,7 +31,9 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
3231
using PrimitiveUtils for GmpMessage;
3332
using PrimitiveUtils for GmpCallback;
3433
using PrimitiveUtils for address;
35-
using BranchlessMath for uint256;
34+
using PrimitiveUtils for uint256;
35+
using PrimitiveUtils for uint64;
36+
using PrimitiveUtils for bool;
3637
using ShardStore for ShardStore.MainStorage;
3738
using RouteStore for RouteStore.MainStorage;
3839
using RouteStore for RouteStore.NetworkInfo;
@@ -221,9 +222,8 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
221222
uint64 nextNonce = uint64(nonces[msg.sender]++);
222223

223224
// Create GMP message and update nonce
224-
GmpMessage memory message = GmpMessage(
225-
source, networkId(), destinationAddress, network, gasLimit, nextNonce, data
226-
);
225+
GmpMessage memory message =
226+
GmpMessage(source, networkId(), destinationAddress, network, gasLimit, nextNonce, data);
227227

228228
bytes32 messageId = message.messageId();
229229
emit GmpCreated(
@@ -276,7 +276,7 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
276276
// Cap the GMP gas limit to 50% of the block gas limit
277277
// OBS: we assume the remaining 50% is enough for the Gateway execution, which is a safe assumption
278278
// once most EVM blockchains have gas limits above 10M and don't need more than 60k gas for the Gateway execution.
279-
uint256 gasLimit = BranchlessMath.min(callback.gasLimit, block.gaslimit >> 1);
279+
uint256 gasLimit = callback.gasLimit.min(block.gaslimit >> 1);
280280
unchecked {
281281
// Add `all but one 64th` to the gas needed, as the defined by EIP-150
282282
// https://eips.ethereum.org/EIPS/eip-150
@@ -313,8 +313,7 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
313313
}
314314

315315
// Update GMP status
316-
GmpStatus status =
317-
GmpStatus(BranchlessMath.ternary(success, uint256(GmpStatus.SUCCESS), uint256(GmpStatus.REVERT)));
316+
GmpStatus status = GmpStatus(success.ternary(uint256(GmpStatus.SUCCESS), uint256(GmpStatus.REVERT)));
318317

319318
// Persist gmp execution status on storage
320319
messages[msgId] = status;
@@ -470,9 +469,7 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
470469
// Compute the Batch signing hash
471470
rootHash = PrimitiveUtils.hash(batch.version, batch.batchId, uint256(rootHash));
472471
bytes32 signingHash = keccak256(
473-
abi.encodePacked(
474-
"Analog GMP v2", networkId(), bytes32(uint256(uint160(address(this)))), rootHash
475-
)
472+
abi.encodePacked("Analog GMP v2", networkId(), bytes32(uint256(uint160(address(this)))), rootHash)
476473
);
477474

478475
// Load shard from storage
@@ -495,7 +492,7 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
495492
gasUsed += initialGas - gasleft();
496493

497494
// Compute refund amount
498-
uint256 refund = BranchlessMath.min(gasUsed * tx.gasprice, address(this).balance);
495+
uint256 refund = (gasUsed * tx.gasprice).min(address(this).balance);
499496

500497
// Refund the gas used
501498
assembly ("memory-safe") {

src/GmpProxy.sol

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ pragma solidity >=0.8.0;
55

66
import {IGmpReceiver} from "../../src/interfaces/IGmpReceiver.sol";
77
import {IGateway} from "../../src/interfaces/IGateway.sol";
8-
import {BranchlessMath} from "../../src/utils/BranchlessMath.sol";
98
import {console} from "forge-std/console.sol";
109

1110
contract GmpProxy is IGmpReceiver {
12-
using BranchlessMath for uint256;
13-
1411
event MessageReceived(GmpMessage msg);
1512

1613
struct GmpMessage {
@@ -32,8 +29,8 @@ contract GmpProxy is IGmpReceiver {
3229
}
3330

3431
function sendMessage(GmpMessage calldata message) external payable returns (bytes32) {
35-
uint256 value = address(this).balance.min(msg.value);
36-
return GATEWAY.submitMessage{value: value}(message.dest, message.destNetwork, message.gasLimit, message.data);
32+
return
33+
GATEWAY.submitMessage{value: msg.value}(message.dest, message.destNetwork, message.gasLimit, message.data);
3734
}
3835

3936
function onGmpReceived(bytes32 id, uint128 srcNetwork, bytes32 src, uint64 nonce, bytes calldata payload)

src/Primitives.sol

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
pragma solidity >=0.8.0;
55

6-
import {BranchlessMath} from "./utils/BranchlessMath.sol";
7-
86
uint8 constant GMP_VERSION = 1;
97

108
/**
@@ -209,6 +207,74 @@ library PrimitiveUtils {
209207
}
210208
}
211209

210+
/**
211+
* @dev Returns the smallest of two numbers.
212+
*/
213+
function min(uint256 x, uint256 y) internal pure returns (uint256) {
214+
return ternary(x < y, x, y);
215+
}
216+
217+
/**
218+
* @dev Returns the largest of two numbers.
219+
*/
220+
function max(uint256 x, uint256 y) internal pure returns (uint256) {
221+
return ternary(x > y, x, y);
222+
}
223+
224+
/**
225+
* @dev If `condition` is true returns `a`, otherwise returns `b`.
226+
*/
227+
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256 r) {
228+
// branchless select, works because:
229+
// b ^ (a ^ b) == a
230+
// b ^ 0 == b
231+
//
232+
// This is better than doing `condition ? a : b` because:
233+
// - Consumes less gas
234+
// - Constant gas cost regardless the inputs
235+
// - Reduces the final bytecode size
236+
assembly {
237+
r := xor(b, mul(xor(a, b), condition))
238+
}
239+
}
240+
241+
/**
242+
* @dev If `condition` is true returns `a`, otherwise returns `b`.
243+
* see `BranchlessMath.ternary`
244+
*/
245+
function ternaryU64(bool condition, uint64 a, uint64 b) internal pure returns (uint64 r) {
246+
assembly {
247+
r := xor(b, mul(xor(a, b), condition))
248+
}
249+
}
250+
251+
/**
252+
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
253+
*/
254+
function toUint(bool b) internal pure returns (uint256 u) {
255+
assembly ("memory-safe") {
256+
u := iszero(iszero(b))
257+
}
258+
}
259+
260+
/**
261+
* @dev Aligns `x` to 32 bytes.
262+
*/
263+
function align32(uint256 x) internal pure returns (uint256 r) {
264+
unchecked {
265+
r = (x + 31) & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0;
266+
}
267+
}
268+
269+
/**
270+
* @dev Convert byte count to 256bit word count, rounded up.
271+
*/
272+
function toWordCount(uint256 byteCount) internal pure returns (uint256 words) {
273+
assembly {
274+
words := add(shr(5, byteCount), gt(and(byteCount, 0x1f), 0))
275+
}
276+
}
277+
212278
function toAddress(bytes32 sender) internal pure returns (address) {
213279
return address(uint160(uint256(sender)));
214280
}

src/storage/Routes.sol

Lines changed: 41 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,14 @@
33
pragma solidity ^0.8.20;
44

55
import {Signature, Route, MAX_PAYLOAD_SIZE} from "../Primitives.sol";
6-
import {EnumerableSet, Pointer} from "../utils/EnumerableSet.sol";
7-
import {BranchlessMath} from "../utils/BranchlessMath.sol";
8-
import {StoragePtr} from "../utils/Pointer.sol";
6+
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
97
import {GasUtils} from "../GasUtils.sol";
108

119
/**
1210
* @dev EIP-7201 Route's Storage
1311
*/
1412
library RouteStore {
15-
using Pointer for StoragePtr;
16-
using Pointer for uint256;
17-
using EnumerableSet for EnumerableSet.Map;
18-
using BranchlessMath for uint256;
13+
using EnumerableMap for EnumerableMap.UintToUintMap;
1914

2015
/**
2116
* @dev Namespace of the routes storage `analog.one.gateway.routes`.
@@ -69,32 +64,26 @@ library RouteStore {
6964
* @custom:storage-location erc7201:analog.one.gateway.routes
7065
*/
7166
struct MainStorage {
72-
EnumerableSet.Map routes;
67+
EnumerableMap.UintToUintMap routeIds;
68+
mapping(uint16 => NetworkInfo) routes;
7369
}
7470

7571
error RouteNotExists(uint16 id);
7672
error IndexOutOfBounds(uint256 index);
73+
error ZeroGatewayForNewRoute();
74+
error InvalidRouteParameters();
7775

7876
function getMainStorage() internal pure returns (MainStorage storage $) {
7977
assembly {
8078
$.slot := _EIP7201_NAMESPACE
8179
}
8280
}
8381

84-
/**
85-
* @dev Converts a `StoragePtr` into an `NetworkInfo`.
86-
*/
87-
function pointerToRoute(StoragePtr ptr) private pure returns (NetworkInfo storage route) {
88-
assembly {
89-
route.slot := ptr
90-
}
91-
}
92-
9382
/**
9483
* @dev Returns true if the value is in the set. O(1).
9584
*/
9685
function has(MainStorage storage store, uint16 networkId) internal view returns (bool) {
97-
return store.routes.has(bytes32(uint256(networkId)));
86+
return store.routeIds.contains(uint256(networkId));
9887
}
9988

10089
/**
@@ -104,8 +93,11 @@ library RouteStore {
10493
* already present.
10594
*/
10695
function getOrAdd(MainStorage storage store, uint16 networkId) private returns (bool, NetworkInfo storage) {
107-
(bool success, StoragePtr ptr) = store.routes.tryAdd(bytes32(uint256(networkId)));
108-
return (success, pointerToRoute(ptr));
96+
bool exists = store.routeIds.contains(uint256(networkId));
97+
if (!exists) {
98+
store.routeIds.set(uint256(networkId), 1);
99+
}
100+
return (!exists, store.routes[networkId]);
109101
}
110102

111103
/**
@@ -115,18 +107,18 @@ library RouteStore {
115107
* present.
116108
*/
117109
function remove(MainStorage storage store, uint16 id) internal returns (bool) {
118-
StoragePtr ptr = store.routes.remove(bytes32(uint256(id)));
119-
if (ptr.isNull()) {
120-
return false;
110+
bool existed = store.routeIds.remove(uint256(id));
111+
if (existed) {
112+
delete store.routes[id];
121113
}
122-
return true;
114+
return existed;
123115
}
124116

125117
/**
126118
* @dev Returns the number of values on the set. O(1).
127119
*/
128120
function length(MainStorage storage store) internal view returns (uint256) {
129-
return store.routes.length();
121+
return store.routeIds.length();
130122
}
131123

132124
/**
@@ -140,11 +132,12 @@ library RouteStore {
140132
* - `index` must be strictly less than {length}.
141133
*/
142134
function at(MainStorage storage store, uint256 index) internal view returns (uint16, NetworkInfo storage) {
143-
(bytes32 key, StoragePtr value) = store.routes.at(index);
144-
if (value.isNull()) {
135+
if (index >= store.routeIds.length()) {
145136
revert IndexOutOfBounds(index);
146137
}
147-
return (uint16(uint256(key)), pointerToRoute(value));
138+
(uint256 key,) = store.routeIds.at(index);
139+
uint16 networkId = uint16(key);
140+
return (networkId, store.routes[networkId]);
148141
}
149142

150143
/**
@@ -154,32 +147,27 @@ library RouteStore {
154147
* - `NetworkInfo` must be in the map.
155148
*/
156149
function get(MainStorage storage store, uint16 id) internal view returns (NetworkInfo storage) {
157-
StoragePtr ptr = store.routes.get(bytes32(uint256(id)));
158-
if (ptr.isNull()) {
150+
if (!store.routeIds.contains(uint256(id))) {
159151
revert RouteNotExists(id);
160152
}
161-
return pointerToRoute(ptr);
162-
}
163-
164-
/**
165-
* @dev Returns the value associated with `NetworkInfo`. O(1).
166-
*/
167-
function tryGet(MainStorage storage store, uint16 id) internal view returns (bool, NetworkInfo storage) {
168-
(bool exists, StoragePtr ptr) = store.routes.tryGet(bytes32(uint256(id)));
169-
return (exists, pointerToRoute(ptr));
153+
return store.routes[id];
170154
}
171155

172156
function createOrUpdateRoute(MainStorage storage store, Route calldata route) internal {
173-
// Update network info
174157
(bool created, NetworkInfo storage stored) = getOrAdd(store, route.networkId);
175-
require((created && route.gateway != bytes32(0)) || !created, "domain separator cannot be zero");
158+
if (created) {
159+
if (route.gateway == bytes32(0)) revert ZeroGatewayForNewRoute();
160+
stored.gateway = route.gateway;
161+
}
176162

177-
// Update gas limit if it's not zero
178-
if (route.gasLimit > 0) {
179-
stored.gasLimit = route.gasLimit;
163+
if (route.relativeGasPriceDenominator == 0 && route.relativeGasPriceNumerator > 0) {
164+
revert InvalidRouteParameters();
180165
}
181166

182-
// Update relative gas price and base fee if any of them are greater than zero
167+
// Update gas limit if it's not zero
168+
if (route.gasLimit > 0) stored.gasLimit = route.gasLimit;
169+
170+
// Update relative gas price and base fee if denominator is greater than zero
183171
if (route.relativeGasPriceDenominator > 0) {
184172
stored.relativeGasPriceNumerator = route.relativeGasPriceNumerator;
185173
stored.relativeGasPriceDenominator = route.relativeGasPriceDenominator;
@@ -208,12 +196,14 @@ library RouteStore {
208196
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
209197
*/
210198
function listRoutes(MainStorage storage store) internal view returns (Route[] memory) {
211-
bytes32[] memory idx = store.routes.keys;
212-
Route[] memory routes = new Route[](idx.length);
213-
for (uint256 i = 0; i < idx.length; i++) {
214-
uint16 networkId = uint16(uint256(idx[i]));
215-
(bool success, NetworkInfo storage route) = tryGet(store, networkId);
216-
require(success, "route not found");
199+
uint256 len = store.routeIds.length();
200+
Route[] memory routes = new Route[](len);
201+
202+
for (uint256 i = 0; i < len; i++) {
203+
(uint256 key,) = store.routeIds.at(i);
204+
uint16 networkId = uint16(key);
205+
NetworkInfo storage route = store.routes[networkId];
206+
217207
routes[i] = Route({
218208
networkId: networkId,
219209
gasLimit: route.gasLimit,

0 commit comments

Comments
 (0)