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
Binary file not shown.
Binary file added audit/20260521-cantina-report-sky-pas.pdf
Binary file not shown.
58 changes: 53 additions & 5 deletions deploy/PASInit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ interface PASMomLike {
function setAuthority(address) external;
}

struct InitCBeamConfig {
address cBeam;
address[] rateLimits;
address[] controllers;
}

struct InitRateLimitConfig {
bytes32 key;
address rateLimits;
uint256 maxAmount;
uint256 slope;
}

struct InitControllerActionConfig {
bytes data;
address controller;
}

library PASInit {

// It is noted that delayed and immediate operations order is non deterministic.
Expand Down Expand Up @@ -137,25 +155,55 @@ library PASInit {
PASInstance memory pasInstance,
uint256 hop,
uint256 maxChange,
address[] memory cBeams,
address[] memory rateLimits,
address[] memory controllers
address[] memory controllers,
InitCBeamConfig[] memory cBeamConfigs
) internal {
require(hop > 0, "PASInit/hop-is-zero");

BeamStateLike beamState = BeamStateLike(pasInstance.beamState);

beamState.setHop(address(0), hop);
beamState.setMaxChange(address(0), maxChange);
for(uint256 i; i < cBeams.length; i++) {
beamState.addCBeam(cBeams[i]);
}
for(uint256 i; i < rateLimits.length; i++) {
beamState.addRateLimits(rateLimits[i]);
}
for(uint256 i; i < controllers.length; i++) {
beamState.addController(controllers[i]);
}
for(uint256 i; i < cBeamConfigs.length; i++) {
InitCBeamConfig memory c = cBeamConfigs[i];
beamState.addCBeam(c.cBeam);
for(uint256 j; j < c.rateLimits.length; j++) {
beamState.setCBeamForRateLimits(c.rateLimits[j], c.cBeam);
}
for(uint256 j; j < c.controllers.length; j++) {
beamState.setCBeamForController(c.controllers[j], c.cBeam);
}
}
}

function initLimitsAndControllerData(
PASInstance memory pasInstance,
InitRateLimitConfig[] memory rateLimitConfigs,
InitControllerActionConfig[] memory controllerActionConfigs
) internal {
BeamStateLike beamState = BeamStateLike(pasInstance.beamState);

for (uint256 i; i < rateLimitConfigs.length; i++) {
beamState.addInitRateLimits(
rateLimitConfigs[i].key,
rateLimitConfigs[i].rateLimits,
rateLimitConfigs[i].maxAmount,
rateLimitConfigs[i].slope
);
}
for (uint256 i; i < controllerActionConfigs.length; i++) {
beamState.addInitControllerActions(
controllerActionConfigs[i].data,
controllerActionConfigs[i].controller
);
}
}

function addCoreToChainlog(
Expand Down
133 changes: 125 additions & 8 deletions test/Integration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import "dss-test/DssTest.sol";
import { MCD, DssInstance } from "dss-test/MCD.sol";
import { PASInstance } from "deploy/PASInstance.sol";
import { PASDeploy } from "deploy/PASDeploy.sol";
import { PASInit } from "deploy/PASInit.sol";
import { PASInit, InitRateLimitConfig, InitControllerActionConfig, InitCBeamConfig } from "deploy/PASInit.sol";
import { BeamState } from "src/BeamState.sol";
import { Configurator, RateLimitsLike } from "src/Configurator.sol";
import { Timelock } from "src/timelock/Timelock.sol";
Expand Down Expand Up @@ -234,37 +234,107 @@ contract IntegrationTest is DssTest {
testControllers[0] = SPARK_CONTROLLER;
testControllers[1] = address(0x22);

// cBeam[0] is paired with both rateLimits and both controllers; cBeam[1] gets only the second rateLimits
InitCBeamConfig[] memory cBeamConfigs = new InitCBeamConfig[](2);
cBeamConfigs[0] = InitCBeamConfig({
cBeam: testCBeams[0],
rateLimits: testRateLimits,
controllers: testControllers
});
address[] memory cBeam1RateLimits = new address[](1);
cBeam1RateLimits[0] = testRateLimits[1];
cBeamConfigs[1] = InitCBeamConfig({
cBeam: testCBeams[1],
rateLimits: cBeam1RateLimits,
controllers: new address[](0)
});

vm.startPrank(pauseProxy);
PASInit.initExtras(freshPas, hop, maxChange, testCBeams, testRateLimits, testControllers);
PASInit.initExtras(freshPas, hop, maxChange, testRateLimits, testControllers, cBeamConfigs);
vm.stopPrank();

// Verify default hop and maxChange are set
assertEq(freshBeamState.getHop(address(0)), hop, "default hop should be set");
assertEq(freshBeamState.getMaxChange(address(0)), maxChange, "default maxChange should be set");

// Verify cBeams are added
assertEq(freshBeamState.cBeams(testCBeams[0]), 1, "first cBeam should be added");
assertEq(freshBeamState.cBeams(testCBeams[1]), 1, "second cBeam should be added");

// Verify rateLimits are added
assertEq(freshBeamState.rateLimits(testRateLimits[0]), 1, "first rateLimits should be added");
assertEq(freshBeamState.rateLimits(testRateLimits[1]), 1, "second rateLimits should be added");

// Verify controllers are added
assertEq(freshBeamState.controllers(testControllers[0]), 1, "first controller should be added");
assertEq(freshBeamState.controllers(testControllers[1]), 1, "second controller should be added");

// Verify cBeams are added
assertEq(freshBeamState.cBeams(testCBeams[0]), 1, "first cBeam should be added");
assertEq(freshBeamState.cBeams(testCBeams[1]), 1, "second cBeam should be added");

// Verify cBeam pairings
assertEq(freshBeamState.rateLimitsCBeams(testRateLimits[0], testCBeams[0]), 1, "cBeam0<->rateLimits0 paired");
assertEq(freshBeamState.rateLimitsCBeams(testRateLimits[1], testCBeams[0]), 1, "cBeam0<->rateLimits1 paired");
assertEq(freshBeamState.controllersCBeams(testControllers[0], testCBeams[0]), 1, "cBeam0<->controller0 paired");
assertEq(freshBeamState.controllersCBeams(testControllers[1], testCBeams[0]), 1, "cBeam0<->controller1 paired");
assertEq(freshBeamState.rateLimitsCBeams(testRateLimits[0], testCBeams[1]), 0, "cBeam1<->rateLimits0 not paired");
assertEq(freshBeamState.rateLimitsCBeams(testRateLimits[1], testCBeams[1]), 1, "cBeam1<->rateLimits1 paired");
assertEq(freshBeamState.controllersCBeams(testControllers[0], testCBeams[1]), 0, "cBeam1<->controller0 not paired");
assertEq(freshBeamState.controllersCBeams(testControllers[1], testCBeams[1]), 0, "cBeam1<->controller1 not paired");
}

function initExtras() external {
PASInstance memory freshPas = PASDeploy.deploy(address(this), address(this), MIN_DELAY);
PASInit.initExtras(freshPas, 0, 1.5 ether, new address[](0), new address[](0), new address[](0));
PASInit.initExtras(freshPas, 0, 1.5 ether, new address[](0), new address[](0), new InitCBeamConfig[](0));
}

function testInitExtrasRevertsWhenHopIsZero() public {
vm.expectRevert("PASInit/hop-is-zero");
this.initExtras();
}

function testInitLimitsAndControllerData() public {
PASInstance memory freshPas = PASDeploy.deploy(address(this), pauseProxy, MIN_DELAY);

// Setup rate limit configs
InitRateLimitConfig[] memory rlConfigs = new InitRateLimitConfig[](2);
rlConfigs[0] = InitRateLimitConfig({
key: bytes32(0),
rateLimits: SPARK_RATE_LIMITS,
maxAmount: 1_000_000 ether,
slope: 100 ether
});
rlConfigs[1] = InitRateLimitConfig({
key: bytes32(uint256(1)),
rateLimits: address(0x42),
maxAmount: 500_000 ether,
slope: 50 ether
});

// Setup controller action configs
bytes memory actionData1 = abi.encodeWithSelector(bytes4(0xdeadbeef), uint256(123));
bytes memory actionData2 = abi.encodeWithSelector(bytes4(0xcafebabe), address(0x99));
InitControllerActionConfig[] memory caConfigs = new InitControllerActionConfig[](2);
caConfigs[0] = InitControllerActionConfig({data: actionData1, controller: SPARK_CONTROLLER});
caConfigs[1] = InitControllerActionConfig({data: actionData2, controller: address(0)});

BeamState freshBeamState = BeamState(freshPas.beamState);

vm.startPrank(pauseProxy);
PASInit.initLimitsAndControllerData(freshPas, rlConfigs, caConfigs);
vm.stopPrank();

// Verify init rate limits
(uint256 maxAmount0, uint256 slope0) = freshBeamState.initRateLimits(bytes32(0), SPARK_RATE_LIMITS);
assertEq(maxAmount0, 1_000_000 ether, "first rate limit maxAmount");
assertEq(slope0, 100 ether, "first rate limit slope");

(uint256 maxAmount1, uint256 slope1) = freshBeamState.initRateLimits(bytes32(uint256(1)), address(0x42));
assertEq(maxAmount1, 500_000 ether, "second rate limit maxAmount");
assertEq(slope1, 50 ether, "second rate limit slope");

// Verify init controller actions
assertEq(freshBeamState.initControllerActions(keccak256(actionData1), SPARK_CONTROLLER), 1, "first controller action");
assertEq(freshBeamState.initControllerActions(keccak256(actionData2), address(0)), 1, "second controller action");
}

// ============================================================================
// CoreCouncil Direct Actions (IMMEDIATE Role) Tests
// ============================================================================
Expand Down Expand Up @@ -872,6 +942,32 @@ contract IntegrationTest is DssTest {
bytes32(uint256(uint160(testRecipient)))
);

// ========================================
// Phase 0: Init some defaults via spell (PASInit.initLimitsAndControllerData)
// ========================================
bytes32 spellRateLimitKey = keccak256("spell-init-key");
bytes memory spellControllerAction = abi.encodeWithSelector(
ControllerLike.setMintRecipient.selector,
uint32(7), // different domain than the timelock-onboarded action
bytes32(uint256(uint160(testRecipient)))
);
{
InitRateLimitConfig[] memory rlConfigs = new InitRateLimitConfig[](1);
rlConfigs[0] = InitRateLimitConfig({
key: spellRateLimitKey,
rateLimits: SPARK_RATE_LIMITS,
maxAmount: 2_000_000e18,
slope: 200_000e18
});

InitControllerActionConfig[] memory caConfigs = new InitControllerActionConfig[](1);
caConfigs[0] = InitControllerActionConfig({data: spellControllerAction, controller: SPARK_CONTROLLER});

vm.startPrank(pauseProxy);
PASInit.initLimitsAndControllerData(pas, rlConfigs, caConfigs);
vm.stopPrank();
}

// ========================================
// Phase 1: Onboard via Timelock (Role 1)
// ========================================
Expand Down Expand Up @@ -932,6 +1028,13 @@ contract IntegrationTest is DssTest {
assertEq(limits.slope, 100_000e18, "init rate limit slope should be set");
}
assertTrue(beamState.isControllerActionEnabled(keccak256(setMintRecipientAction), SPARK_CONTROLLER), "controller action should be enabled");
// Verify spell-configured defaults
{
BeamState.DefaultRateLimits memory spellLimits = beamState.getInitRateLimits(spellRateLimitKey, SPARK_RATE_LIMITS);
assertEq(spellLimits.maxAmount, 2_000_000e18, "spell init rate limit maxAmount should be set");
assertEq(spellLimits.slope, 200_000e18, "spell init rate limit slope should be set");
}
assertTrue(beamState.isControllerActionEnabled(keccak256(spellControllerAction), SPARK_CONTROLLER), "spell controller action should be enabled");

// ========================================
// Phase 2: Grant admin role to configurator and associate cBeam (Role 2 - direct)
Expand Down Expand Up @@ -973,6 +1076,20 @@ contract IntegrationTest is DssTest {
bytes32 expectedRecipient = bytes32(uint256(uint160(testRecipient)));
assertEq(ControllerLike(SPARK_CONTROLLER).mintRecipients(6), expectedRecipient, "mintRecipient should be set on real controller");

// 3c. cBeam sets rate limit using spell-configured defaults
vm.prank(cBeam);
configurator.setRateLimit(SPARK_RATE_LIMITS, spellRateLimitKey, 1_500_000e18, 150_000e18);
{
RateLimitsLike.RateLimitData memory data = RateLimitsLike(SPARK_RATE_LIMITS).getRateLimitData(spellRateLimitKey);
assertEq(data.maxAmount, 1_500_000e18, "spell rate limit maxAmount should be set by cBeam on real contract");
assertEq(data.slope, 150_000e18, "spell rate limit slope should be set by cBeam on real contract");
}

// 3d. cBeam calls spell-configured controller action
vm.prank(cBeam);
configurator.callControllerAction(SPARK_CONTROLLER, spellControllerAction);
assertEq(ControllerLike(SPARK_CONTROLLER).mintRecipients(7), expectedRecipient, "spell mintRecipient should be set on real controller");

// ========================================
// Phase 4: Verify restrictions
// ========================================
Expand All @@ -991,7 +1108,7 @@ contract IntegrationTest is DssTest {
// 4c. cBeam cannot call non-whitelisted action
bytes memory nonWhitelistedAction = abi.encodeWithSelector(
ControllerLike.setMintRecipient.selector,
uint32(7), // different domain
uint32(8), // domain not whitelisted by either timelock or spell
bytes32(uint256(uint160(testRecipient)))
);
vm.prank(cBeam);
Expand Down
Loading