Make router domains enumerable (#1460)

* Make router domains enumerable

* Fix tests

* Make routers fn backwards compatible

* Add external domains fn
pull/1479/head
Yorke Rhodes 2 years ago committed by GitHub
parent 40a145d988
commit d0f14c1b88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 46
      solidity/contracts/Router.sol
  2. 72
      solidity/contracts/libs/EnumerableMapExtended.sol
  3. 2
      solidity/contracts/middleware/liquidity-layer/adapters/CircleBridgeAdapter.sol
  4. 13
      solidity/test/router.test.ts

@ -6,14 +6,16 @@ import {HyperlaneConnectionClient} from "./HyperlaneConnectionClient.sol";
import {IInterchainGasPaymaster} from "../interfaces/IInterchainGasPaymaster.sol"; import {IInterchainGasPaymaster} from "../interfaces/IInterchainGasPaymaster.sol";
import {IMessageRecipient} from "../interfaces/IMessageRecipient.sol"; import {IMessageRecipient} from "../interfaces/IMessageRecipient.sol";
import {IMailbox} from "../interfaces/IMailbox.sol"; import {IMailbox} from "../interfaces/IMailbox.sol";
import {EnumerableMapExtended} from "./libs/EnumerableMapExtended.sol";
abstract contract Router is HyperlaneConnectionClient, IMessageRecipient { abstract contract Router is HyperlaneConnectionClient, IMessageRecipient {
using EnumerableMapExtended for EnumerableMapExtended.UintToBytes32Map;
string constant NO_ROUTER_ENROLLED_REVERT_MESSAGE = string constant NO_ROUTER_ENROLLED_REVERT_MESSAGE =
"No router enrolled for domain. Did you specify the right domain ID?"; "No router enrolled for domain. Did you specify the right domain ID?";
// ============ Mutable Storage ============ // ============ Mutable Storage ============
EnumerableMapExtended.UintToBytes32Map internal _routers;
mapping(uint32 => bytes32) public routers;
uint256[49] private __GAP; // gap for upgrade safety uint256[49] private __GAP; // gap for upgrade safety
// ============ Events ============ // ============ Events ============
@ -55,6 +57,22 @@ abstract contract Router is HyperlaneConnectionClient, IMessageRecipient {
} }
// ============ External functions ============ // ============ External functions ============
function domains() external view returns (uint32[] memory) {
bytes32[] storage rawKeys = _routers.keys();
uint32[] memory keys = new uint32[](rawKeys.length);
for (uint256 i = 0; i < rawKeys.length; i++) {
keys[i] = uint32(uint256(rawKeys[i]));
}
return keys;
}
function routers(uint32 _domain) public view returns (bytes32) {
if (_routers.contains(_domain)) {
return _routers.get(_domain);
} else {
return bytes32(0); // for backwards compatibility with storage mapping
}
}
/** /**
* @notice Register the address of a Router contract for the same Application on a remote chain * @notice Register the address of a Router contract for the same Application on a remote chain
@ -72,15 +90,15 @@ abstract contract Router is HyperlaneConnectionClient, IMessageRecipient {
/** /**
* @notice Batch version of `enrollRemoteRouter` * @notice Batch version of `enrollRemoteRouter`
* @param _domains The domaisn of the remote Application Routers * @param _domains The domaisn of the remote Application Routers
* @param _routers The addresses of the remote Application Routers * @param _addresses The addresses of the remote Application Routers
*/ */
function enrollRemoteRouters( function enrollRemoteRouters(
uint32[] calldata _domains, uint32[] calldata _domains,
bytes32[] calldata _routers bytes32[] calldata _addresses
) external virtual onlyOwner { ) external virtual onlyOwner {
require(_domains.length == _routers.length, "!length"); require(_domains.length == _addresses.length, "!length");
for (uint256 i = 0; i < _domains.length; i += 1) { for (uint256 i = 0; i < _domains.length; i += 1) {
_enrollRemoteRouter(_domains[i], _routers[i]); _enrollRemoteRouter(_domains[i], _addresses[i]);
} }
} }
@ -111,24 +129,24 @@ abstract contract Router is HyperlaneConnectionClient, IMessageRecipient {
/** /**
* @notice Set the router for a given domain * @notice Set the router for a given domain
* @param _domain The domain * @param _domain The domain
* @param _router The new router * @param _address The new router
*/ */
function _enrollRemoteRouter(uint32 _domain, bytes32 _router) internal { function _enrollRemoteRouter(uint32 _domain, bytes32 _address) internal {
routers[_domain] = _router; _routers.set(_domain, _address);
emit RemoteRouterEnrolled(_domain, _router); emit RemoteRouterEnrolled(_domain, _address);
} }
/** /**
* @notice Return true if the given domain / router is the address of a remote Application Router * @notice Return true if the given domain / router is the address of a remote Application Router
* @param _domain The domain of the potential remote Application Router * @param _domain The domain of the potential remote Application Router
* @param _router The address of the potential remote Application Router * @param _address The address of the potential remote Application Router
*/ */
function _isRemoteRouter(uint32 _domain, bytes32 _router) function _isRemoteRouter(uint32 _domain, bytes32 _address)
internal internal
view view
returns (bool) returns (bool)
{ {
return routers[_domain] == _router; return routers(_domain) == _address;
} }
/** /**
@ -141,7 +159,7 @@ abstract contract Router is HyperlaneConnectionClient, IMessageRecipient {
view view
returns (bytes32 _router) returns (bytes32 _router)
{ {
_router = routers[_domain]; _router = routers(_domain);
require(_router != bytes32(0), NO_ROUTER_ENROLLED_REVERT_MESSAGE); require(_router != bytes32(0), NO_ROUTER_ENROLLED_REVERT_MESSAGE);
} }

@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
// ============ External Imports ============
import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
// extends EnumerableMap with uint256 => bytes32 type
// modelled after https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/structs/EnumerableMap.sol
library EnumerableMapExtended {
using EnumerableMap for EnumerableMap.Bytes32ToBytes32Map;
struct UintToBytes32Map {
EnumerableMap.Bytes32ToBytes32Map _inner;
}
// ============ Library Functions ============
function keys(UintToBytes32Map storage map)
internal
view
returns (bytes32[] storage)
{
return map._inner._keys._inner._values;
}
function set(
UintToBytes32Map storage map,
uint256 key,
bytes32 value
) internal {
map._inner.set(bytes32(key), value);
}
function get(UintToBytes32Map storage map, uint256 key)
internal
view
returns (bytes32)
{
return map._inner.get(bytes32(key));
}
function remove(UintToBytes32Map storage map, uint256 key)
internal
returns (bool)
{
return map._inner.remove(bytes32(key));
}
function contains(UintToBytes32Map storage map, uint256 key)
internal
view
returns (bool)
{
return map._inner.contains(bytes32(key));
}
function length(UintToBytes32Map storage map)
internal
view
returns (uint256)
{
return map._inner.length();
}
function at(UintToBytes32Map storage map, uint256 index)
internal
view
returns (uint256, bytes32)
{
(bytes32 key, bytes32 value) = map._inner.at(index);
return (uint256(key), value);
}
}

@ -96,7 +96,7 @@ contract CircleBridgeAdapter is ILiquidityLayerAdapter, Router {
uint32 _circleDomain = hyperlaneDomainToCircleDomain[ uint32 _circleDomain = hyperlaneDomainToCircleDomain[
_destinationDomain _destinationDomain
]; ];
bytes32 _remoteRouter = routers[_destinationDomain]; bytes32 _remoteRouter = routers(_destinationDomain);
require( require(
_remoteRouter != bytes32(0), _remoteRouter != bytes32(0),
"CircleBridgeAdapter: No router for domain" "CircleBridgeAdapter: No router for domain"

@ -133,6 +133,19 @@ describe('Router', async () => {
expect(await router.mustHaveRemoteRouter(origin)).to.equal(remoteBytes); expect(await router.mustHaveRemoteRouter(origin)).to.equal(remoteBytes);
}); });
describe('#domains', () => {
it('returns the domains', async () => {
await router.enrollRemoteRouters(
[origin, destination],
[
utils.addressToBytes32(nonOwner.address),
utils.addressToBytes32(nonOwner.address),
],
);
expect(await router.domains()).to.deep.equal([origin, destination]);
});
});
it('non-owner cannot enroll remote router', async () => { it('non-owner cannot enroll remote router', async () => {
await expect( await expect(
router router

Loading…
Cancel
Save