Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/core/SystemSealer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,16 @@ contract SystemSealer {
}

// ─────────────────────────────────────────────────────────────────────────
// INVARIANT 8: Incentives ownership (if deployed)
// INVARIANT 8: Incentives is bound to the vault, and its ownership (if deployed)
// ─────────────────────────────────────────────────────────────────────────
// Live-wiring bind (review §24), same pattern as feeCollector/router/
// bufferManager/healthRegistry above: without this, a correctly-owned
// decoy Incentives contract the vault does not actually read from
// still satisfies the ownership check below and passes the seal.
if (config.incentives != address(0)) {
if (address(vault.incentives()) != config.incentives) {
return (false, "Incentives not bound to vault");
}
if (Incentives(config.incentives).owner() != config.rootTimelock) {
return (false, "Incentives.owner != ROOT_TIMELOCK");
}
Expand Down
23 changes: 23 additions & 0 deletions test/sprint-test/SystemSealer_CanSealAgreement.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { GlobalConfig } from "../../src/core/config/GlobalConfig.sol";
import { SelectorRegistry } from "../../src/core/libraries/SelectorRegistry.sol";
import { SelectorLib } from "../../src/core/libraries/SelectorLib.sol";
import { SystemSealer } from "../../src/core/SystemSealer.sol";
import { Incentives } from "../../src/core/modules/Incentives.sol";
import { IIncentives } from "../../src/interfaces/IIncentives.sol";
import { IAdminModule } from "../../src/interfaces/IAdminModule.sol";
import { IBufferManager } from "../../src/interfaces/IBufferManager.sol";
import { IncentivesTimelock } from "../../src/governance/IncentivesTimelock.sol";
Expand Down Expand Up @@ -228,6 +230,27 @@ contract SystemSealer_CanSealAgreement_Test is Test {
assertFalse(vault.isSystemSealed());
}

function test_canSeal_and_verifyAndSeal_agree_whenIncentivesIsADecoy() public {
IIncentives.Params memory p =
IIncentives.Params({ cliffDays: 30, fullDays: 180, bmaxWad: 3e16, vestingDays: 180 });

Incentives realIncentives = new Incentives(address(rootTimelock), address(vault), treasury, p);
vm.prank(address(rootTimelock));
IAdminModule(address(vault)).setIncentives(address(realIncentives));

Incentives decoy = new Incentives(address(rootTimelock), address(vault), treasury, p);

SystemSealer.SealConfig memory decoyConfig = sealConfig;
decoyConfig.incentives = address(decoy);

(bool ok, string memory reason) = systemSealer.canSeal(decoyConfig);
assertFalse(ok, "canSeal must reject a correctly-governed Incentives the vault does not read from");
assertEq(reason, "Incentives not bound to vault");

_scheduleAndExpectRevert(decoyConfig, "incentives-decoy-salt");
assertFalse(vault.isSystemSealed());
}

function test_canSeal_and_verifyAndSeal_agree_whenFeeCollectorIsADecoy() public {
FeeCollector decoy =
new FeeCollector(address(rootTimelock), treasury, treasury, treasury, 7000, 200, 3000);
Expand Down
Loading