pragma solidity 0.8.26;
import {Test} from "forge-std/Test.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {IAttackRegistry} from "@battlechain/interface/IAttackRegistry.sol";
import {ConfidencePool} from "src/ConfidencePool.sol";
import {MockAgreement} from "test/mocks/MockAgreement.sol";
import {MockERC20} from "test/mocks/MockERC20.sol";
import {MockSafeHarborRegistry} from "test/mocks/MockSafeHarborRegistry.sol";
contract BindingAwareAttackRegistryForScopePoC {
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 BindingAgreementScopePoC is Test {
uint256 internal constant ONE = 1e18;
address internal constant INITIAL_ACCOUNT = address(0xC0FFEE);
address internal constant REPLACEMENT_ACCOUNT = address(0xBEEF);
MockERC20 internal token;
MockSafeHarborRegistry internal registry;
BindingAwareAttackRegistryForScopePoC internal attackRegistry;
MockAgreement internal selectedAgreement;
MockAgreement internal bindingAgreement;
ConfidencePool internal pool;
address internal owner = makeAddr("owner");
address internal moderator = makeAddr("moderator");
address internal recovery = makeAddr("recovery");
address internal staker = makeAddr("staker");
function setUp() public {
token = new MockERC20();
registry = new MockSafeHarborRegistry();
attackRegistry = new BindingAwareAttackRegistryForScopePoC();
selectedAgreement = new MockAgreement(owner);
bindingAgreement = new MockAgreement(makeAddr("bindingAgreementOwner"));
selectedAgreement.setContractInScope(INITIAL_ACCOUNT, true);
selectedAgreement.setContractInScope(REPLACEMENT_ACCOUNT, true);
registry.setAgreementValid(address(selectedAgreement), true);
registry.setAgreementValid(address(bindingAgreement), true);
registry.setAttackRegistry(address(attackRegistry));
ConfidencePool implementation = new ConfidencePool();
pool = ConfidencePool(Clones.clone(address(implementation)));
}
function testInitializeAcceptsAccountWhoseBindingAgreementDiffersFromSelectedAgreement() external {
attackRegistry.setAgreementForContract(INITIAL_ACCOUNT, address(bindingAgreement));
attackRegistry.setAgreementState(address(selectedAgreement), IAttackRegistry.ContractState.ATTACK_REQUESTED);
attackRegistry.setAgreementState(address(bindingAgreement), IAttackRegistry.ContractState.CORRUPTED);
pool.initialize(
address(selectedAgreement),
address(token),
address(registry),
moderator,
block.timestamp + 31 days,
ONE,
recovery,
owner,
_single(INITIAL_ACCOUNT)
);
assertEq(
attackRegistry.getAgreementForContract(INITIAL_ACCOUNT),
address(bindingAgreement),
"test precondition: Binding Agreement differs"
);
assertEq(pool.agreement(), address(selectedAgreement), "pool stores selected agreement");
assertTrue(pool.isAccountInScope(INITIAL_ACCOUNT), "initializer accepted selected-agreement scope");
token.mint(staker, ONE);
vm.startPrank(staker);
token.approve(address(pool), ONE);
pool.stake(ONE);
vm.stopPrank();
assertEq(token.balanceOf(address(pool)), ONE, "stake followed selected state instead of binding state");
}
function testSetPoolScopeAcceptsReplacementAccountWhoseBindingAgreementDiffers() external {
attackRegistry.setAgreementState(address(selectedAgreement), IAttackRegistry.ContractState.NOT_DEPLOYED);
attackRegistry.setAgreementState(address(bindingAgreement), IAttackRegistry.ContractState.CORRUPTED);
attackRegistry.setAgreementForContract(REPLACEMENT_ACCOUNT, address(bindingAgreement));
pool.initialize(
address(selectedAgreement),
address(token),
address(registry),
moderator,
block.timestamp + 31 days,
ONE,
recovery,
owner,
_single(INITIAL_ACCOUNT)
);
vm.prank(owner);
pool.setPoolScope(_single(REPLACEMENT_ACCOUNT));
assertEq(
attackRegistry.getAgreementForContract(REPLACEMENT_ACCOUNT),
address(bindingAgreement),
"test precondition: replacement Binding Agreement differs"
);
assertFalse(pool.isAccountInScope(INITIAL_ACCOUNT), "old scope cleared");
assertTrue(pool.isAccountInScope(REPLACEMENT_ACCOUNT), "setPoolScope accepted non-binding account");
}
function _single(address account) internal pure returns (address[] memory accounts) {
accounts = new address[](1);
accounts[0] = account;
}
}