pragma solidity 0.8.26;
import {Test} from "forge-std/Test.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {IAttackRegistry} from "@battlechain/interface/IAttackRegistry.sol";
import {ConfidencePool} from "src/ConfidencePool.sol";
import {ConfidencePoolFactory} from "src/ConfidencePoolFactory.sol";
import {MockAgreement} from "test/mocks/MockAgreement.sol";
import {MockERC20} from "test/mocks/MockERC20.sol";
import {MockSafeHarborRegistry} from "test/mocks/MockSafeHarborRegistry.sol";
contract BindingAwareAttackRegistry {
mapping(address agreement => IAttackRegistry.ContractState state) internal stateByAgreement;
mapping(address account => address agreement) internal bindingAgreementByAccount;
function setAgreementState(address agreement, IAttackRegistry.ContractState state) external {
stateByAgreement[agreement] = state;
}
function setAgreementForContract(address account, address agreement) external {
bindingAgreementByAccount[account] = agreement;
}
function getAgreementState(address agreement) external view returns (IAttackRegistry.ContractState) {
return stateByAgreement[agreement];
}
function getAgreementForContract(address account) external view returns (address) {
return bindingAgreementByAccount[account];
}
}
contract BindingAgreementFactoryPoC is Test {
uint256 internal constant ONE = 1e18;
address internal constant SCOPE_ACCOUNT = address(0xC0FFEE);
MockERC20 internal token;
MockSafeHarborRegistry internal registry;
BindingAwareAttackRegistry internal attackRegistry;
ConfidencePoolFactory internal factory;
MockAgreement internal selectedAgreement;
MockAgreement internal bindingAgreement;
address internal agreementOwner = makeAddr("agreementOwner");
address internal defaultModerator = makeAddr("defaultModerator");
address internal recovery = makeAddr("recovery");
address internal staker = makeAddr("staker");
function setUp() public {
token = new MockERC20();
registry = new MockSafeHarborRegistry();
attackRegistry = new BindingAwareAttackRegistry();
selectedAgreement = new MockAgreement(agreementOwner);
bindingAgreement = new MockAgreement(makeAddr("bindingAgreementOwner"));
selectedAgreement.setContractInScope(SCOPE_ACCOUNT, true);
registry.setAgreementValid(address(selectedAgreement), true);
registry.setAgreementValid(address(bindingAgreement), true);
registry.setAttackRegistry(address(attackRegistry));
ConfidencePoolFactory impl = new ConfidencePoolFactory();
ConfidencePool poolImplementation = new ConfidencePool();
ERC1967Proxy proxy = new ERC1967Proxy(
address(impl),
abi.encodeCall(
ConfidencePoolFactory.initialize, (address(registry), address(poolImplementation), defaultModerator)
)
);
factory = ConfidencePoolFactory(address(proxy));
factory.setStakeTokenAllowed(address(token), true);
}
function testCreatePoolAcceptsAccountBoundToDifferentAgreementAndUsesSelectedAgreementState() external {
attackRegistry.setAgreementForContract(SCOPE_ACCOUNT, address(bindingAgreement));
attackRegistry.setAgreementState(address(selectedAgreement), IAttackRegistry.ContractState.ATTACK_REQUESTED);
attackRegistry.setAgreementState(address(bindingAgreement), IAttackRegistry.ContractState.CORRUPTED);
vm.prank(agreementOwner);
address created = factory.createPool(
address(selectedAgreement), address(token), block.timestamp + 31 days, ONE, recovery, _scope()
);
assertEq(
attackRegistry.getAgreementForContract(SCOPE_ACCOUNT),
address(bindingAgreement),
"test precondition: account Binding Agreement differs"
);
assertEq(ConfidencePool(created).agreement(), address(selectedAgreement), "pool stores selected agreement");
assertTrue(ConfidencePool(created).isAccountInScope(SCOPE_ACCOUNT), "selected scope accepted");
token.mint(staker, ONE);
vm.startPrank(staker);
token.approve(created, ONE);
ConfidencePool(created).stake(ONE);
vm.stopPrank();
assertEq(token.balanceOf(created), ONE, "stake used selected agreement state, not Binding Agreement state");
assertEq(factory.poolCountByAgreement(address(selectedAgreement)), 1, "factory registered mismatched pool");
}
function testCreatePoolAcceptsAccountWithNoBindingAgreement() external {
attackRegistry.setAgreementForContract(SCOPE_ACCOUNT, address(0));
attackRegistry.setAgreementState(address(selectedAgreement), IAttackRegistry.ContractState.ATTACK_REQUESTED);
vm.prank(agreementOwner);
address created = factory.createPool(
address(selectedAgreement), address(token), block.timestamp + 31 days, ONE, recovery, _scope()
);
assertEq(
attackRegistry.getAgreementForContract(SCOPE_ACCOUNT),
address(0),
"test precondition: account has no Binding Agreement"
);
assertEq(ConfidencePool(created).agreement(), address(selectedAgreement), "pool stores selected agreement");
assertTrue(ConfidencePool(created).isAccountInScope(SCOPE_ACCOUNT), "selected scope accepted");
}
function _scope() internal pure returns (address[] memory accounts) {
accounts = new address[](1);
accounts[0] = SCOPE_ACCOUNT;
}
}