contract CEIReentrancyPoC is Test {
MockSafeHarborRegistry internal registry;
MockConfidencePool internal poolImpl;
address internal constant STAKE_TOKEN = address(0xBEEF);
address internal constant RECOVERY = address(0xCAFE);
uint256 internal constant MIN_STAKE = 1 ether;
uint256 internal expiry;
function setUp() public {
registry = new MockSafeHarborRegistry();
poolImpl = new MockConfidencePool();
expiry = block.timestamp + 60 days;
}
function test_VulnerableFactory_ReentrancyCausesPoolCreationDoS() public {
VulnerableFactory factory = new VulnerableFactory(address(registry), address(poolImpl), address(this));
factory.setAllowedStakeToken(STAKE_TOKEN, true);
MaliciousHook hook = new MaliciousHook(
address(factory), address(0), STAKE_TOKEN, expiry, MIN_STAKE, RECOVERY
);
MockAgreement agreement = new MockAgreement(address(hook));
registry.setValid(address(agreement), true);
hook = new MaliciousHook(address(factory), address(agreement), STAKE_TOKEN, expiry, MIN_STAKE, RECOVERY);
vm.expectRevert();
hook.attack();
assertEq(factory.poolsByAgreementLength(address(agreement)), 0, "no pool should have been recorded");
}
function test_VulnerableFactory_BenignFlowStillSucceeds() public {
VulnerableFactory factory = new VulnerableFactory(address(registry), address(poolImpl), address(this));
factory.setAllowedStakeToken(STAKE_TOKEN, true);
MockAgreement agreement = new MockAgreement(address(this));
registry.setValid(address(agreement), true);
address[] memory accounts = new address[](0);
address pool = factory.createPool(address(agreement), STAKE_TOKEN, expiry, MIN_STAKE, RECOVERY, accounts);
assertTrue(pool != address(0));
assertEq(factory.poolsByAgreementLength(address(agreement)), 1);
}
function test_FixedFactory_ReentrancyAttemptRejectedCleanly() public {
FixedFactory factory = new FixedFactory(address(registry), address(poolImpl), address(this));
factory.setAllowedStakeToken(STAKE_TOKEN, true);
MaliciousHook hook0 = new MaliciousHook(address(factory), address(0), STAKE_TOKEN, expiry, MIN_STAKE, RECOVERY);
MockAgreement agreement = new MockAgreement(address(hook0));
registry.setValid(address(agreement), true);
MaliciousHook hook = new MaliciousHook(address(factory), address(agreement), STAKE_TOKEN, expiry, MIN_STAKE, RECOVERY);
vm.expectRevert();
hook.attack();
assertEq(factory.poolsByAgreementLength(address(agreement)), 0, "no pool should have been recorded");
}
function test_FixedFactory_BenignFlowStillSucceeds() public {
FixedFactory factory = new FixedFactory(address(registry), address(poolImpl), address(this));
factory.setAllowedStakeToken(STAKE_TOKEN, true);
MockAgreement agreement = new MockAgreement(address(this));
registry.setValid(address(agreement), true);
address[] memory accounts = new address[](0);
address pool = factory.createPool(address(agreement), STAKE_TOKEN, expiry, MIN_STAKE, RECOVERY, accounts);
assertTrue(pool != address(0));
assertEq(factory.poolsByAgreementLength(address(agreement)), 1);
}
}
- remove this code
```solidity
function createPool(
address agreement,
address stakeToken,
uint256 expiry,
uint256 minStake,
address recoveryAddress,
address[] calldata accounts
) external whenNotPaused returns (address pool) {
if (agreement == address(0) || stakeToken == address(0)) revert ZeroAddress();
if (recoveryAddress == address(0)) revert ZeroAddress();
if (!allowedStakeToken[stakeToken]) revert StakeTokenNotAllowed();
if (expiry < block.timestamp + _MIN_EXPIRY_LEAD) revert ExpiryTooSoon();
// aderyn-fp-next-line(reentrancy-state-change)
if (!safeHarborRegistry.isAgreementValid(agreement)) revert InvalidAgreement();
// aderyn-fp-next-line(reentrancy-state-change)
if (IAgreement(agreement).owner() != msg.sender) revert UnauthorizedCreator();
// Salt incorporates the per-agreement index so an agreement can back many pools while
// keeping deterministic, collision-free clone addresses.
bytes32 salt = keccak256(abi.encode(agreement, _poolsByAgreement[agreement].length));
pool = Clones.cloneDeterministic(poolImplementation, salt);
// aderyn-fp-next-line(reentrancy-state-change)
IConfidencePool(pool)
.initialize(
agreement,
stakeToken,
address(safeHarborRegistry),
defaultOutcomeModerator,
expiry,
minStake,
recoveryAddress,
msg.sender,
accounts
);
_poolsByAgreement[agreement].push(pool);
emit PoolCreated(
agreement,
pool,
stakeToken,
expiry,
minStake,
recoveryAddress,
defaultOutcomeModerator,
address(safeHarborRegistry)
);
}
```
+ add this code
function createPool(
address agreement,
address stakeToken,
uint256 expiry,
uint256 minStake,
address recoveryAddress,
address[] calldata accounts
) external whenNotPaused nonReentrant returns (address pool) {
if (agreement == address(0) || stakeToken == address(0)) revert ZeroAddress();
if (recoveryAddress == address(0)) revert ZeroAddress();
if (!allowedStakeToken[stakeToken]) revert StakeTokenNotAllowed();
if (expiry < block.timestamp + _MIN_EXPIRY_LEAD) revert ExpiryTooSoon();
if (!safeHarborRegistry.isAgreementValid(agreement)) revert InvalidAgreement();
if (IAgreement(agreement).owner() != msg.sender) revert UnauthorizedCreator();
// Salt incorporates the per-agreement index so an agreement can back many pools while
// keeping deterministic, collision-free clone addresses.
bytes32 salt = keccak256(abi.encode(agreement, _poolsByAgreement[agreement].length));
pool = Clones.cloneDeterministic(poolImplementation, salt);
// Effect before interaction: record the pool against `agreement` BEFORE calling out to
// `initialize()`. This closes the reentrancy window where a callback from `initialize()`
// (e.g. via a hook-bearing address in `accounts`, or `stakeToken`/`agreement` behavior)
// could re-enter `createPool` and recompute the same `salt` before `length` increments,
// causing `cloneDeterministic` to collide and revert the legitimate outer call.
_poolsByAgreement[agreement].push(pool);
IConfidencePool(pool).initialize(
agreement,
stakeToken,
address(safeHarborRegistry),
defaultOutcomeModerator,
expiry,
minStake,
recoveryAddress,
msg.sender,
accounts
);
emit PoolCreated(
agreement,
pool,
stakeToken,
expiry,
minStake,
recoveryAddress,
defaultOutcomeModerator,
address(safeHarborRegistry)
);
}