// 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(); } }