Skip to content

Commit

Permalink
Merge branch 'test/invariants' of github-defi:allo-protocol/allo-v2.1…
Browse files Browse the repository at this point in the history
… into test/invariant-basic-properties

Signed-off-by: 0xRaccoon <[email protected]>
  • Loading branch information
0xRaccoon committed Oct 29, 2024
2 parents 38cf7c3 + dce7626 commit 7224964
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 28 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 {}
}
114 changes: 109 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 All @@ -47,8 +57,8 @@ contract Setup is Actors {
vm.prank(protocolDeployer);
allo.initialize(protocolDeployer, address(registry), payable(treasury), percentFee, baseFee, 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 @@ -61,5 +71,99 @@ contract Setup is Actors {

_addAnchorToActor(_ghost_actors[i], registry.getProfileById(_id).anchor, _id);
}

// 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));
}
}
}
25 changes: 14 additions & 11 deletions test/invariant/fuzz/handlers/HandlerAllo.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,31 @@ pragma solidity ^0.8.19;

import {Setup} from "../Setup.t.sol";
import {IRegistry} from "contracts/core/Registry.sol";
import {IAllo, 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]);

// 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 @@ -27,7 +36,7 @@ contract HandlerAllo is Setup {
allo.createPool,
(
profile.id,
address(strategy_directAllocation),
_strategyImplementations[PoolStrategies(_seedPoolStrategy)],
bytes(""),
address(token),
0,
Expand All @@ -36,12 +45,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(uint256 _idSeed, uint256 _metadataProtocol, string calldata _data) public {
Expand Down
2 changes: 1 addition & 1 deletion test/invariant/fuzz/handlers/HandlerStrategy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ contract HandlerStrategy is HandlerAllo {

// Withdraw
(bool succ,) = targetCall(
address(allo), 0, abi.encodeCall(strategy_directAllocation.withdraw, (_pool.token, _amount, _recipient))
address(_pool.strategy), 0, abi.encodeCall(BaseStrategy.withdraw, (_pool.token, _amount, _recipient))
);
}
}
70 changes: 70 additions & 0 deletions test/invariant/fuzz/helpers/Pools.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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;
}
}

0 comments on commit 7224964

Please sign in to comment.