Skip to content

Commit

Permalink
Merge pull request #48 from allo-protocol/invar/prop-1
Browse files Browse the repository at this point in the history
test(medusa): multiple strat
  • Loading branch information
simon-something authored Oct 29, 2024
2 parents a21de41 + a914f73 commit dce7626
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 72 deletions.
17 changes: 9 additions & 8 deletions test/invariant/PROPERTIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,15 @@
| Function Name | Sighash | Function Signature | Handler |
| ------------------------- | -------- | ---------------------------------------------------------------- | ------- |
| initialize | c4d66de8 | initialize(address) | NA |
| createProfile | 3a92f65f | createProfile(uint256,string,(uint256,string),address,address[]) | [] |
| updateProfileName | cf189ff2 | updateProfileName(bytes32,string) | [] |
| updateProfileMetadata | ac402839 | updateProfileMetadata(bytes32,(uint256,string)) | [] |
| updateProfilePendingOwner | 3b66dacd | updateProfilePendingOwner(bytes32,address) | [] |
| acceptProfileOwnership | 2497f3c6 | acceptProfileOwnership(bytes32) | [] |
| addMembers | 5063f361 | addMembers(bytes32,address[]) | [] |
| removeMembers | e0cf1e4c | removeMembers(bytes32,address[]) | [] |
| recoverFunds | 24ae6a27 | recoverFunds(address,address) | [] |
| createProfile | 3a92f65f | createProfile(uint256,string,(uint256,string),address,address[]) | [x] |
| updateProfileName | cf189ff2 | updateProfileName(bytes32,string) | [x] |
| updateProfileMetadata | ac402839 | updateProfileMetadata(bytes32,(uint256,string)) | [x] |
| updateProfilePendingOwner | 3b66dacd | updateProfilePendingOwner(bytes32,address) | [x] |
| acceptProfileOwnership | 2497f3c6 | acceptProfileOwnership(bytes32) | [x] |
| addMembers | 5063f361 | addMembers(bytes32,address[]) | [x] |
| removeMembers | e0cf1e4c | removeMembers(bytes32,address[]) | [x] |
| recoverFunds | 24ae6a27 | recoverFunds(address,address) | []* |
* via Allo.sol

## Base strategy
| Function Name | Sighash | Function Signature | Handler |
Expand Down
5 changes: 2 additions & 3 deletions test/invariant/fuzz/FuzzTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {PropertiesParent} from "./properties/PropertiesParent.t.sol";

contract FuzzTest is PropertiesParent {
/// @custom:property-id 0
/// @custom:property Check if
/// @custom:property Check sanity
function property_sanityCheck() public {
assertTrue(address(allo) != address(0), "sanity check");
assertTrue(address(registry) != address(0), "sanity check");
Expand All @@ -15,6 +15,5 @@ contract FuzzTest is PropertiesParent {
assertTrue(allo.isTrustedForwarder(forwarder), "sanity check");
}

// This is a good place to include Forge test for debugging purposes
function test_forgeDebug() public {}
function test_debug() public {}
}
116 changes: 111 additions & 5 deletions test/invariant/fuzz/Setup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,30 @@ import {Allo, IAllo, Metadata} from "contracts/core/Allo.sol";
import {Registry, Anchor} from "contracts/core/Anchor.sol";
import {IRegistry} from "contracts/core/interfaces/IRegistry.sol";
import {DirectAllocationStrategy} from "contracts/strategies/examples/direct-allocation/DirectAllocation.sol";
import {QVSimple} from "contracts/strategies/examples/quadratic-voting/QVSimple.sol";
import {SQFSuperfluid} from "contracts/strategies/examples/sqf-superfluid/SQFSuperfluid.sol";

import {IRecipientsExtension} from "strategies/extensions/register/IRecipientsExtension.sol";

import {Actors} from "./helpers/Actors.t.sol";
import {Pools} from "./helpers/Pools.t.sol";
import {Utils} from "./helpers/Utils.t.sol";
import {FuzzERC20, ERC20} from "./helpers/FuzzERC20.sol";

contract Setup is Actors {
contract Setup is Actors, Pools {
uint256 percentFee;
uint256 baseFee;

uint64 defaultRegistrationStartTime;
uint64 defaultRegistrationEndTime;
uint256 defaultAllocationStartTime;
uint256 defaultAllocationEndTime;
uint256 defaultWithdrawalCooldown;
uint256 DEFAULT_MAX_BID;

Allo allo;
Registry registry;

DirectAllocationStrategy strategy_directAllocation;

ERC20 token;

address protocolDeployer = makeAddr("protocolDeployer");
Expand Down Expand Up @@ -58,8 +68,8 @@ contract Setup is Actors {
forwarder
);

// Deploy base strategy
strategy_directAllocation = new DirectAllocationStrategy(address(allo));
// Deploy strategies implementations
_initImplementations(address(allo));

// Deploy token
token = ERC20(address(new FuzzERC20()));
Expand All @@ -79,5 +89,101 @@ contract Setup is Actors {
registry.getProfileById(_id).anchor
);
}

// Create pools for each strategy
_initPools();
}

function _initPools() internal {
defaultRegistrationStartTime = uint64(block.timestamp);
defaultRegistrationEndTime = uint64(block.timestamp + 7 days);
defaultAllocationStartTime = uint64(block.timestamp + 7 days + 1);
defaultAllocationEndTime = uint64(block.timestamp + 10 days);
defaultWithdrawalCooldown = 1 days;
DEFAULT_MAX_BID = 1000;

for (uint256 i = 1; i <= uint256(type(PoolStrategies).max); i++) {
address _deployer = _ghost_actors[i % 4];

IRegistry.Profile memory profile = registry.getProfileByAnchor(
_ghost_anchorOf[_deployer]
);

bytes memory _metadata;

if (PoolStrategies(i) == PoolStrategies.DirectAllocation) {
_metadata = "";
} else if (PoolStrategies(i) == PoolStrategies.DonationVoting) {
_metadata = abi.encode(
IRecipientsExtension.RecipientInitializeData({
metadataRequired: false,
registrationStartTime: defaultRegistrationStartTime,
registrationEndTime: defaultRegistrationEndTime
}),
defaultAllocationStartTime,
defaultAllocationEndTime,
defaultWithdrawalCooldown,
token,
true
);
} else if (
PoolStrategies(i) == PoolStrategies.EasyRPGF
) {} else if (PoolStrategies(i) == PoolStrategies.ImpactStream) {
_metadata = abi.encode(
IRecipientsExtension.RecipientInitializeData({
metadataRequired: false,
registrationStartTime: uint64(block.timestamp),
registrationEndTime: uint64(block.timestamp + 7 days)
}),
QVSimple.QVSimpleInitializeData({
allocationStartTime: uint64(block.timestamp),
allocationEndTime: uint64(block.timestamp + 7 days),
maxVoiceCreditsPerAllocator: 100,
isUsingAllocationMetadata: false
})
);
} else if (PoolStrategies(i) == PoolStrategies.QuadraticVoting) {
_metadata = abi.encode(
IRecipientsExtension.RecipientInitializeData({
metadataRequired: false,
registrationStartTime: uint64(block.timestamp),
registrationEndTime: uint64(block.timestamp + 7 days)
}),
QVSimple.QVSimpleInitializeData({
allocationStartTime: uint64(block.timestamp),
allocationEndTime: uint64(block.timestamp + 7 days),
maxVoiceCreditsPerAllocator: 100,
isUsingAllocationMetadata: false
})
);
} else if (PoolStrategies(i) == PoolStrategies.RFP) {
_metadata = abi.encode(
IRecipientsExtension.RecipientInitializeData({
metadataRequired: false,
registrationStartTime: uint64(block.timestamp),
registrationEndTime: uint64(block.timestamp + 7 days)
}),
DEFAULT_MAX_BID
);
} else if (PoolStrategies(i) == PoolStrategies.SQFSuperfluid) {
// Skip for now - mock?
return;
}

vm.prank(_deployer);
uint256 _poolId = allo.createPool(
profile.id,
_strategyImplementations[PoolStrategies(i)],
_metadata,
address(token),
0,
profile.metadata,
new address[](0)
);

ghost_poolAdmins[_poolId] = _deployer;

_recordPool(_poolId, PoolStrategies(i));
}
}
}
27 changes: 16 additions & 11 deletions test/invariant/fuzz/handlers/HandlerAllo.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ pragma solidity ^0.8.19;

import {Setup} from "../Setup.t.sol";
import {IRegistry} from "contracts/core/Registry.sol";
import {Allo, Metadata} from "contracts/core/Allo.sol";
import {Allo, IAllo, Metadata} from "contracts/core/Allo.sol";
import {FuzzERC20} from "../helpers/FuzzERC20.sol";

contract HandlerAllo is Setup {
uint256[] ghost_poolIds;
mapping(uint256 _poolId => address[] _managers) ghost_poolManagers;
mapping(uint256 _poolId => address _poolAdmin) ghost_poolAdmins;
mapping(uint256 _poolId => address[] _recipients) ghost_recipients;

function handler_createPool(uint256 _msgValue) public {
function handler_createPool(
uint256 _msgValue,
uint256 _seedPoolStrategy
) public {
_seedPoolStrategy = bound(
_seedPoolStrategy,
uint256(type(PoolStrategies).min) + 1, // Avoid None elt
uint256(type(PoolStrategies).max)
);

// Get the profile ID
IRegistry.Profile memory profile = registry.getProfileByAnchor(
_ghost_anchorOf[msg.sender]
Expand All @@ -21,6 +28,10 @@ contract HandlerAllo is Setup {
// Avoid EOA
if (profile.anchor == address(0)) return;

// Avoid redeploying pool with a strategy already tested
if (_strategyHasImplementation(PoolStrategies(_seedPoolStrategy)))
return;

// Create a pool
(bool succ, bytes memory ret) = targetCall(
address(allo),
Expand All @@ -29,7 +40,7 @@ contract HandlerAllo is Setup {
allo.createPool,
(
profile.id,
address(strategy_directAllocation),
_strategyImplementations[PoolStrategies(_seedPoolStrategy)],
bytes(""),
address(token),
0,
Expand All @@ -38,12 +49,6 @@ contract HandlerAllo is Setup {
)
)
);

if (succ) {
uint256 _poolId = abi.decode(ret, (uint256));
ghost_poolIds.push(_poolId);
ghost_poolAdmins[_poolId] = msg.sender;
}
}

function handler_updatePoolMetadata(
Expand Down
4 changes: 2 additions & 2 deletions test/invariant/fuzz/handlers/HandlerStrategy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ contract HandlerStrategy is HandlerAllo {

// Withdraw
(bool succ, ) = targetCall(
address(allo),
address(_pool.strategy),
0,
abi.encodeCall(
strategy_directAllocation.withdraw,
BaseStrategy.withdraw,
(_pool.token, _amount, _recipient)
)
);
Expand Down
88 changes: 88 additions & 0 deletions test/invariant/fuzz/helpers/Pools.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Utils} from "./Utils.t.sol";
import {Anchor} from "contracts/core/Anchor.sol";
import {Allo, IAllo} from "contracts/core/Allo.sol";

import {DirectAllocationStrategy} from "contracts/strategies/examples/direct-allocation/DirectAllocation.sol";
import {DonationVotingOnchain} from "contracts/strategies/examples/donation-voting/DonationVotingOnchain.sol";
import {EasyRPGF} from "contracts/strategies/examples/easy-rpgf/EasyRPGF.sol";
import {QVImpactStream} from "contracts/strategies/examples/impact-stream/QVImpactStream.sol";
import {QVSimple} from "contracts/strategies/examples/quadratic-voting/QVSimple.sol";
import {RFPSimple} from "contracts/strategies/examples/rfp/RFPSimple.sol";
import {SQFSuperfluid} from "contracts/strategies/examples/sqf-superfluid/SQFSuperfluid.sol";

contract Pools is Utils {
Allo private allo;

enum PoolStrategies {
None,
DirectAllocation,
DonationVoting,
EasyRPGF,
ImpactStream,
QuadraticVoting,
RFP,
SQFSuperfluid
}

uint256[] internal ghost_poolIds;
mapping(uint256 _poolId => address _poolAdmin) ghost_poolAdmins;

mapping(PoolStrategies _strategy => address _implementation)
internal _strategyImplementations;

function _initImplementations(address _allo) internal {
_strategyImplementations[PoolStrategies.DirectAllocation] = address(
new DirectAllocationStrategy(_allo)
);
_strategyImplementations[PoolStrategies.DonationVoting] = address(
new DonationVotingOnchain(_allo, "MyFancyName")
);
_strategyImplementations[PoolStrategies.EasyRPGF] = address(
new EasyRPGF(_allo)
);
_strategyImplementations[PoolStrategies.ImpactStream] = address(
new QVImpactStream(_allo)
);
_strategyImplementations[PoolStrategies.QuadraticVoting] = address(
new QVSimple(_allo, "MyFancyName")
);
_strategyImplementations[PoolStrategies.RFP] = address(
new RFPSimple(_allo)
);
_strategyImplementations[PoolStrategies.SQFSuperfluid] = address(
new SQFSuperfluid(_allo)
);

allo = Allo(_allo);
}

function _recordPool(uint256 _poolId, PoolStrategies _strategy) internal {
ghost_poolIds.push(_poolId);
}

// reverse lookup pool id -> strategy type
function _poolStrategy(uint256 _poolId) internal returns (PoolStrategies) {
IAllo.Pool memory _pool = allo.getPool(_poolId);
for (uint256 i; i < uint256(type(PoolStrategies).max); i++)
if (
_strategyImplementations[PoolStrategies(i)] ==
address(_pool.strategy)
) return PoolStrategies(i);

emit TestFailure("Wrong pool strategy implementation address");
}

function _strategyHasImplementation(
PoolStrategies _strategy
) internal returns (bool) {
for (uint256 i; i < ghost_poolIds.length; i++) {
if (_poolStrategy(ghost_poolIds[i]) == _strategy) return true;
}

return false;
}
}
Loading

0 comments on commit dce7626

Please sign in to comment.