The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hyperlane-monorepo/solidity/test/isms/AggregationIsm.t.sol

168 lines
5.6 KiB

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import {IAggregationIsm} from "../../contracts/interfaces/isms/IAggregationIsm.sol";
import {StaticAggregationIsmFactory} from "../../contracts/isms/aggregation/StaticAggregationIsmFactory.sol";
import {IThresholdAddressFactory} from "../../contracts/interfaces/IThresholdAddressFactory.sol";
import {StorageAggregationIsmFactory} from "../../contracts/isms/aggregation/StorageAggregationIsm.sol";
import {AggregationIsmMetadata} from "../../contracts/isms/libs/AggregationIsmMetadata.sol";
import {TestIsm, ThresholdTestUtils} from "./IsmTestUtils.sol";
contract AggregationIsmTest is Test {
using Strings for uint256;
using Strings for uint8;
string constant fixtureKey = "fixture";
IThresholdAddressFactory factory;
IAggregationIsm ism;
function setUp() public virtual {
factory = new StaticAggregationIsmFactory();
}
function fixtureAppendMetadata(
uint256 index,
bytes memory metadata
) internal {
vm.serializeBytes(fixtureKey, index.toString(), metadata);
}
function fixtureAppendNull(uint256 index) internal {
vm.serializeString(fixtureKey, index.toString(), "null");
}
function writeFixture(bytes memory metadata, uint8 m) internal {
string memory path = string(
abi.encodePacked("./fixtures/aggregation/", m.toString(), ".json")
);
vm.writeJson(vm.serializeBytes(fixtureKey, "encoded", metadata), path);
}
function deployIsms(
uint8 m,
uint8 n,
bytes32 seed
) internal returns (address[] memory) {
vm.assume(m > 0 && m <= n && n < 10);
bytes32 randomness = seed;
address[] memory isms = new address[](n);
for (uint256 i = 0; i < n; i++) {
randomness = keccak256(abi.encode(randomness));
TestIsm subIsm = new TestIsm(abi.encode(randomness));
isms[i] = address(subIsm);
}
ism = IAggregationIsm(factory.deploy(isms, m));
return isms;
}
function getMetadata(uint8 m, bytes32 seed) private returns (bytes memory) {
(address[] memory choices, ) = ism.modulesAndThreshold("");
address[] memory chosen = ThresholdTestUtils.choose(m, choices, seed);
bytes memory offsets;
uint32 start = 8 * uint32(choices.length);
bytes memory metametadata;
for (uint256 i = 0; i < choices.length; i++) {
bool included = false;
for (uint256 j = 0; j < chosen.length; j++) {
included = included || choices[i] == chosen[j];
}
if (included) {
bytes memory metadata = TestIsm(choices[i]).requiredMetadata();
uint32 end = start + uint32(metadata.length);
uint64 offset = (uint64(start) << 32) | uint64(end);
offsets = bytes.concat(offsets, abi.encodePacked(offset));
start = end;
metametadata = abi.encodePacked(metametadata, metadata);
fixtureAppendMetadata(i, metadata);
} else {
offsets = bytes.concat(offsets, abi.encodePacked(uint64(0)));
fixtureAppendNull(i);
}
}
bytes memory encoded = abi.encodePacked(offsets, metametadata);
writeFixture(encoded, m);
return encoded;
}
function testVerify(uint8 m, uint8 n, bytes32 seed) public {
deployIsms(m, n, seed);
bytes memory metadata = getMetadata(m, seed);
assertTrue(ism.verify(metadata, ""));
}
function testVerifyNoMetadataRequired(
uint8 m,
uint8 n,
uint8 i,
bytes32 seed
) public {
vm.assume(i < n);
deployIsms(m, n, seed);
(address[] memory modules, ) = ism.modulesAndThreshold("");
bytes memory noMetadata;
TestIsm(modules[i]).setRequiredMetadata(noMetadata);
bytes memory metadata = getMetadata(m, seed);
assertTrue(ism.verify(metadata, ""));
}
function testVerifyMissingMetadata(uint8 m, uint8 n, bytes32 seed) public {
deployIsms(m, n, seed);
// Populate metadata for one fewer ISMs than needed.
bytes memory metadata = getMetadata(m - 1, seed);
vm.expectRevert(bytes("!threshold"));
ism.verify(metadata, "");
}
function testVerifyIncorrectMetadata(
uint8 m,
uint8 n,
bytes32 seed
) public {
deployIsms(m, n, seed);
bytes memory metadata = getMetadata(m, seed);
// Modify the last byte in metadata. This should affect
// the content of the metadata passed to the last ISM.
metadata[metadata.length - 1] = ~metadata[metadata.length - 1];
vm.expectRevert(bytes("!verify"));
ism.verify(metadata, "");
}
function testModulesAndThreshold(uint8 m, uint8 n, bytes32 seed) public {
address[] memory expectedIsms = deployIsms(m, n, seed);
(address[] memory actualIsms, uint8 actualThreshold) = ism
.modulesAndThreshold("");
assertEq(abi.encode(actualIsms), abi.encode(expectedIsms));
assertEq(actualThreshold, m);
}
function testZeroThreshold() public {
vm.expectRevert("Invalid threshold");
factory.deploy(new address[](1), 0);
}
function testThresholdExceedsLength() public {
vm.expectRevert("Invalid threshold");
factory.deploy(new address[](1), 2);
}
}
contract StorageAggregationIsmTest is AggregationIsmTest {
function setUp() public override {
factory = new StorageAggregationIsmFactory();
}
}