From 5439759421f86b25cbc6c9a6a372423cf5cc980a Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 11 Dec 2025 21:49:05 -0300 Subject: [PATCH 001/150] Options to evaluate --- src/Option 1/ATWLState.sol | 104 ++++++++++++++++++++ src/Option 1/Configurator.sol | 136 ++++++++++++++++++++++++++ src/Option 2/Configurator.sol | 178 ++++++++++++++++++++++++++++++++++ src/Option 3/Configurator.sol | 142 +++++++++++++++++++++++++++ src/Option 3/Role.sol | 60 ++++++++++++ 5 files changed, 620 insertions(+) create mode 100644 src/Option 1/ATWLState.sol create mode 100644 src/Option 1/Configurator.sol create mode 100644 src/Option 2/Configurator.sol create mode 100644 src/Option 3/Configurator.sol create mode 100644 src/Option 3/Role.sol diff --git a/src/Option 1/ATWLState.sol b/src/Option 1/ATWLState.sol new file mode 100644 index 0000000..6978edb --- /dev/null +++ b/src/Option 1/ATWLState.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +contract ATWLState { + + // --- Storage variables --- + + mapping(address usr => uint256 allowed) public wards; + mapping(address usr => uint256 allowed) public bud; + mapping(bytes32 key => DefaultRateLimits) public defaults; + mapping(bytes32 prime => address) public rateLimits; + + struct DefaultRateLimits { + uint256 maxAmount; + uint256 slope; + } + + // --- Events --- + + event Rely(address indexed usr); + event Deny(address indexed usr); + event Kiss(address indexed usr); + event Diss(address indexed usr); + event AddDefault(bytes32 indexed key, uint256 maxAmount, uint256 slope); + event DelDefault(bytes32 indexed key); + event AddPrime(bytes32 indexed prime, address rateLimits_); + event DelPrime(bytes32 indexed prime); + + // --- Modifiers --- + + modifier auth() { + require(wards[msg.sender] == 1, "Configurator/not-authorized"); + _; + } + + modifier toll() { + require(bud[msg.sender] == 1, "Configurator/not-bud"); + _; + } + + // --- Constructor --- + + constructor() { + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Admin functions --- + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + function kiss(address usr) external auth { + bud[usr] = 1; + emit Kiss(usr); + } + + function diss(address usr) external auth { + bud[usr] = 0; + emit Diss(usr); + } + + // Tolled functions + + function addDefault( + bytes32 key, + uint256 maxAmount, + uint256 slope + ) external toll { + defaults[key] = DefaultRateLimits(maxAmount, slope); + + emit AddDefault(key, maxAmount, slope); + } + + function delDefault(bytes32 key) external toll { + delete defaults[key]; + + emit DelDefault(key); + } + + function addPrime( + bytes32 prime, + address rateLimits_ + ) external toll { + rateLimits[prime] = rateLimits_; + + emit AddPrime(prime, rateLimits_); + } + + function delPrime( + bytes32 prime + ) external toll { + rateLimits[prime] = address(0); + + emit DelPrime(prime); + } +} diff --git a/src/Option 1/Configurator.sol b/src/Option 1/Configurator.sol new file mode 100644 index 0000000..8531ddb --- /dev/null +++ b/src/Option 1/Configurator.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +interface RateLimitsLike { + struct RateLimitData { + uint256 maxAmount; + uint256 slope; + uint256 lastAmount; + uint256 lastUpdated; + } + + function getRateLimitData(bytes32 key) external view returns (RateLimitData memory); + function setRateLimitData(bytes32 key, uint256 maxAmount, uint256 slope) external; +} + +interface ATWLStateLike { + function defaults(bytes32) external view returns (uint256, uint256); + function rateLimits(bytes32) external view returns (RateLimitsLike); +} + +contract Configurator { + + // --- Storage variables --- + + mapping(address usr => uint256 allowed) public wards; + mapping(address usr => uint256 allowed) public bud; + mapping(bytes32 prime => mapping(bytes32 key => uint256 timestamp)) public zzz; + uint256 public hop; + uint256 public maxChange; + + // --- Immutables --- + + ATWLStateLike public immutable atwlState; + + // --- Constants --- + + uint256 internal constant WAD = 10**18; + + // --- Events --- + + event Rely(address indexed usr); + event Deny(address indexed usr); + event Kiss(address indexed usr); + event Diss(address indexed usr); + event File(bytes32 indexed what, uint256 data); + event SetRateLimit(bytes32 indexed prime, bytes32 indexed key, uint256 maxAmount, uint256 slope, bool isIcrement, uint256 timestamp); + + // --- Modifiers --- + + modifier auth() { + require(wards[msg.sender] == 1, "Configurator/not-authorized"); + _; + } + + modifier toll() { + require(bud[msg.sender] == 1, "Configurator/not-bud"); + _; + } + + // --- Constructor --- + + constructor(address atwlState_) { + require(atwlState_ != address(0), "Configurator/null-atwlState"); + + atwlState = ATWLStateLike(atwlState_); + + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Admin functions --- + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + function kiss(address usr) external auth { + bud[usr] = 1; + emit Kiss(usr); + } + + function diss(address usr) external auth { + bud[usr] = 0; + emit Diss(usr); + } + + function file(bytes32 what, uint256 data) external auth { + if (what == "hop") { + hop = data; + } else if (what == "maxChange") { + require(data >= WAD, "Configurator/maxChange-below-1x"); + maxChange = data; + } else { + revert("Configurator/file-unrecognized-param"); + } + emit File(what, data); + } + + // Tolled functions + + function initRateLimit(bytes32 prime, bytes32 key) external toll { + (uint256 maxAmount, uint256 slope) = atwlState.defaults(key); + require(maxAmount > 0 && slope > 0, "Configurator/wrong-defaults"); + RateLimitsLike.RateLimitData memory current = atwlState.rateLimits(prime).getRateLimitData(key); + require(current.maxAmount == 0 || current.slope == 0, "Configurator/already-init"); + + atwlState.rateLimits(prime).setRateLimitData(key, maxAmount, slope); + + zzz[prime][key] = block.timestamp; + + emit SetRateLimit(prime, key, maxAmount, slope, true, block.timestamp); + } + + function setRateLimit(bytes32 prime, bytes32 key, uint256 maxAmount, uint256 slope) external toll { + RateLimitsLike.RateLimitData memory current = atwlState.rateLimits(prime).getRateLimitData(key); + bool isIncrement = maxAmount > current.maxAmount || slope > current.slope; + require(current.maxAmount > 0 && current.slope > 0, "Configurator/needs-init"); + require(!isIncrement || block.timestamp >= zzz[prime][key] + hop, "Configurator/increment-too-soon"); + require(maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD + require(slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); + + if (isIncrement) { + zzz[prime][key] = block.timestamp; + } + + atwlState.rateLimits(prime).setRateLimitData(key, maxAmount, slope); + + emit SetRateLimit(prime, key, maxAmount, slope, isIncrement, block.timestamp); + } +} diff --git a/src/Option 2/Configurator.sol b/src/Option 2/Configurator.sol new file mode 100644 index 0000000..5550aee --- /dev/null +++ b/src/Option 2/Configurator.sol @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +interface RateLimitsLike { + struct RateLimitData { + uint256 maxAmount; + uint256 slope; + uint256 lastAmount; + uint256 lastUpdated; + } + + function getRateLimitData(bytes32 key) external view returns (RateLimitData memory); + function setRateLimitData(bytes32 key, uint256 maxAmount, uint256 slope) external; +} + +contract Configurator { + + // --- Storage variables --- + + mapping(address usr => uint256 allowed) public wards; + mapping(address usr => uint256 allowed) public can; + mapping(address usr => uint256 allowed) public bud; + mapping(bytes32 key => DefaultRateLimits) public defaults; + mapping(bytes32 prime => RateLimitsLike) public rateLimits; + mapping(bytes32 prime => mapping(bytes32 key => uint256 timestamp)) public zzz; + uint256 public hop; + uint256 public maxChange; + + struct DefaultRateLimits { + uint256 maxAmount; + uint256 slope; + } + + // --- Constants --- + + uint256 internal constant WAD = 10**18; + + // --- Events --- + + event Rely(address indexed usr); + event Deny(address indexed usr); + event Hope(address indexed usr); + event Nope(address indexed usr); + event Kiss(address indexed usr); + event Diss(address indexed usr); + event File(bytes32 indexed what, uint256 data); + event AddDefault(bytes32 indexed key, uint256 maxAmount, uint256 slope); + event DelDefault(bytes32 indexed key); + event AddPrime(bytes32 indexed prime, address rateLimits_); + event DelPrime(bytes32 indexed prime); + event SetRateLimit(bytes32 indexed prime, bytes32 indexed key, uint256 maxAmount, uint256 slope, bool isIcrement, uint256 timestamp); + + // --- Modifiers --- + + modifier auth() { + require(wards[msg.sender] == 1, "Configurator/not-authorized"); + _; + } + + modifier time() { + require(can[msg.sender] == 1, "Configurator/not-can"); + _; + } + + modifier toll() { + require(bud[msg.sender] == 1, "Configurator/not-bud"); + _; + } + + // --- Constructor --- + + constructor() { + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Admin functions --- + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + function hope(address usr) external auth { + can[usr] = 1; + emit Hope(usr); + } + + function nope(address usr) external auth { + can[usr] = 0; + emit Nope(usr); + } + + function kiss(address usr) external auth { + bud[usr] = 1; + emit Kiss(usr); + } + + function diss(address usr) external auth { + bud[usr] = 0; + emit Diss(usr); + } + + function file(bytes32 what, uint256 data) external auth { + if (what == "hop") { + hop = data; + } else if (what == "maxChange") { + require(data >= WAD, "Configurator/maxChange-below-1x"); + maxChange = data; + } else { + revert("Configurator/file-unrecognized-param"); + } + emit File(what, data); + } + + // Timelocked functions + + function addDefault(bytes32 key, uint256 maxAmount, uint256 slope) external time { + defaults[key] = DefaultRateLimits(maxAmount, slope); + + emit AddDefault(key, maxAmount, slope); + } + + function delDefault(bytes32 key) external time { + delete defaults[key]; + + emit DelDefault(key); + } + + function addPrime(bytes32 prime, address rateLimits_) external time { + rateLimits[prime] = RateLimitsLike(rateLimits_); + + emit AddPrime(prime, rateLimits_); + } + + function delPrime(bytes32 prime) external time { + rateLimits[prime] = RateLimitsLike(address(0)); + + emit DelPrime(prime); + } + + // Tolled functions + + function initRateLimit(bytes32 prime, bytes32 key) external toll { + DefaultRateLimits memory d = defaults[key]; + require(d.maxAmount > 0 && d.slope > 0, "Configurator/wrong-defaults"); + RateLimitsLike.RateLimitData memory current = rateLimits[prime].getRateLimitData(key); + require(current.maxAmount == 0 || current.slope == 0, "Configurator/already-init"); + + rateLimits[prime].setRateLimitData(key, d.maxAmount, d.slope); + + zzz[prime][key] = block.timestamp; + + emit SetRateLimit(prime, key, d.maxAmount, d.slope, true, block.timestamp); + } + + function setRateLimit(bytes32 prime, bytes32 key, uint256 maxAmount, uint256 slope) external toll { + RateLimitsLike.RateLimitData memory current = rateLimits[prime].getRateLimitData(key); + bool isIncrement = maxAmount > current.maxAmount || slope > current.slope; + require(current.maxAmount > 0 && current.slope > 0, "Configurator/needs-init"); + require(!isIncrement || block.timestamp >= zzz[prime][key] + hop, "Configurator/increment-too-soon"); + require(maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD + require(slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); + + if (isIncrement) { + zzz[prime][key] = block.timestamp; + } + + rateLimits[prime].setRateLimitData(key, maxAmount, slope); + + emit SetRateLimit(prime, key, maxAmount, slope, isIncrement, block.timestamp); + } +} diff --git a/src/Option 3/Configurator.sol b/src/Option 3/Configurator.sol new file mode 100644 index 0000000..d852945 --- /dev/null +++ b/src/Option 3/Configurator.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +interface RateLimitsLike { + struct RateLimitData { + uint256 maxAmount; + uint256 slope; + uint256 lastAmount; + uint256 lastUpdated; + } + + function getRateLimitData(bytes32 key) external view returns (RateLimitData memory); + function setRateLimitData(bytes32 key, uint256 maxAmount, uint256 slope) external; +} + +contract Configurator { + + // --- Storage variables --- + + mapping(address usr => uint256 allowed) public wards; + mapping(bytes32 key => DefaultRateLimits) public defaults; + mapping(bytes32 prime => RateLimitsLike) public rateLimits; + mapping(bytes32 prime => mapping(bytes32 key => uint256 timestamp)) public zzz; + uint256 public hop; + uint256 public maxChange; + + struct DefaultRateLimits { + uint256 maxAmount; + uint256 slope; + } + + // --- Constants --- + + uint256 internal constant WAD = 10**18; + + // --- Events --- + + event Rely(address indexed usr); + event Deny(address indexed usr); + event Hope(address indexed usr); + event Nope(address indexed usr); + event Kiss(address indexed usr); + event Diss(address indexed usr); + event File(bytes32 indexed what, uint256 data); + event AddDefault(bytes32 indexed key, uint256 maxAmount, uint256 slope); + event DelDefault(bytes32 indexed key); + event AddPrime(bytes32 indexed prime, address rateLimits_); + event DelPrime(bytes32 indexed prime); + event SetRateLimit(bytes32 indexed prime, bytes32 indexed key, uint256 maxAmount, uint256 slope, bool isIcrement, uint256 timestamp); + + // --- Modifiers --- + + modifier auth() { + require(wards[msg.sender] == 1, "Configurator/not-authorized"); + _; + } + + // --- Constructor --- + + constructor() { + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Admin functions --- + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + function file(bytes32 what, uint256 data) external auth { + if (what == "hop") { + hop = data; + } else if (what == "maxChange") { + require(data >= WAD, "Configurator/maxChange-below-1x"); + maxChange = data; + } else { + revert("Configurator/file-unrecognized-param"); + } + emit File(what, data); + } + + function addDefault(bytes32 key, uint256 maxAmount, uint256 slope) external auth { + defaults[key] = DefaultRateLimits(maxAmount, slope); + + emit AddDefault(key, maxAmount, slope); + } + + function delDefault(bytes32 key) external auth { + delete defaults[key]; + + emit DelDefault(key); + } + + function addPrime(bytes32 prime, address rateLimits_) external auth { + rateLimits[prime] = RateLimitsLike(rateLimits_); + + emit AddPrime(prime, rateLimits_); + } + + function delPrime(bytes32 prime) external auth { + rateLimits[prime] = RateLimitsLike(address(0)); + + emit DelPrime(prime); + } + + function initRateLimit(bytes32 prime, bytes32 key) external auth { + DefaultRateLimits memory d = defaults[key]; + require(d.maxAmount > 0 && d.slope > 0, "Configurator/wrong-defaults"); + RateLimitsLike.RateLimitData memory current = rateLimits[prime].getRateLimitData(key); + require(current.maxAmount == 0 || current.slope == 0, "Configurator/already-init"); + + rateLimits[prime].setRateLimitData(key, d.maxAmount, d.slope); + + zzz[prime][key] = block.timestamp; + + emit SetRateLimit(prime, key, d.maxAmount, d.slope, true, block.timestamp); + } + + function setRateLimit(bytes32 prime, bytes32 key, uint256 maxAmount, uint256 slope) external auth { + RateLimitsLike.RateLimitData memory current = rateLimits[prime].getRateLimitData(key); + bool isIncrement = maxAmount > current.maxAmount || slope > current.slope; + require(current.maxAmount > 0 && current.slope > 0, "Configurator/needs-init"); + require(!isIncrement || block.timestamp >= zzz[prime][key] + hop, "Configurator/increment-too-soon"); + require(maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD + require(slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); + + if (isIncrement) { + zzz[prime][key] = block.timestamp; + } + + rateLimits[prime].setRateLimitData(key, maxAmount, slope); + + emit SetRateLimit(prime, key, maxAmount, slope, isIncrement, block.timestamp); + } +} diff --git a/src/Option 3/Role.sol b/src/Option 3/Role.sol new file mode 100644 index 0000000..a91f21d --- /dev/null +++ b/src/Option 3/Role.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +interface ConfiguratorLike { + function addDefault(bytes32, uint256, uint256) external; + function delDefault(bytes32) external; + function addPrime(bytes32, address) external; + function delPrime(bytes32) external; + function initRateLimit(bytes32, bytes32) external; + function setRateLimit(bytes32, bytes32, uint256, uint256) external; +} + +abstract contract Role { + address immutable public owner; + ConfiguratorLike immutable public configurator; + + modifier onlyOwner() { + require(msg.sender == owner, "Role/not-owner"); + _; + } + + constructor(address owner_, address configurator_) { + owner = owner_; + configurator = ConfiguratorLike(configurator_); + } +} + +contract TimeLocked is Role { + constructor(address owner_, address configurator_) Role(owner_, configurator_) { + } + + function addDefault(bytes32 key, uint256 maxAmount, uint256 slope) external onlyOwner { + configurator.addDefault(key, maxAmount, slope); + } + + function delDefault(bytes32 key) external onlyOwner { + configurator.delDefault(key); + } + + function addPrime(bytes32 prime, address rateLimits) external onlyOwner { + configurator.addPrime(prime, rateLimits); + } + + function delPrime(bytes32 prime) external onlyOwner { + configurator.delPrime(prime); + } +} + +contract GovOps is Role { + constructor(address owner_, address configurator_) Role(owner_, configurator_) { + } + + function initRateLimit(bytes32 prime, bytes32 key) external onlyOwner { + configurator.initRateLimit(prime, key); + } + + function setRateLimit(bytes32 prime, bytes32 key, uint256 maxAmount, uint256 slope) external onlyOwner { + configurator.setRateLimit(prime, key, maxAmount, slope); + } +} From 27bc5ce3e601f85e095c4a42c559fc4b7aa084e8 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 17 Dec 2025 20:03:26 -0300 Subject: [PATCH 002/150] Option 4 --- src/Option 4/ATWLState.sol | 109 +++++++++++++++++++++++++++ src/Option 4/Configurator.sol | 138 ++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 src/Option 4/ATWLState.sol create mode 100644 src/Option 4/Configurator.sol diff --git a/src/Option 4/ATWLState.sol b/src/Option 4/ATWLState.sol new file mode 100644 index 0000000..32d2f5b --- /dev/null +++ b/src/Option 4/ATWLState.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +contract ATWLState { + + // --- Storage variables --- + + mapping(address usr => uint256 allowed) public wards; + mapping(address pau => mapping(address usr => uint256 allowed)) public govOps; + mapping(bytes32 key => mapping(address pau => DefaultRateLimits limit)) public initRateLimits; // pau == address(0) every pau allowed + mapping(bytes32 key => mapping(address pau => bool allowed)) public initControllerActions; // pau == address(0) every pau allowed + + struct DefaultRateLimits { + uint256 maxAmount; + uint256 slope; + } + + // --- Events --- + + event Rely(address indexed usr); + event Deny(address indexed usr); + event AddGovOps(address indexed pau, address indexed usr); + event DelGovOps(address indexed pau, address indexed usr); + event AddInitRateLimits(bytes32 indexed key, address indexed pau, uint256 maxAmount, uint256 slope); + event DelInitRateLimits(bytes32 indexed key, address indexed pau); + event AddInitControllerActions(bytes32 indexed key, address indexed pau); + event DelInitControllerActions(bytes32 indexed key, address indexed pau); + + // --- Modifiers --- + + modifier auth() { + require(wards[msg.sender] == 1, "Configurator/not-authorized"); + _; + } + + // --- Constructor --- + + constructor() { + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- External getters --- + + function getInitRateLimits(bytes32 key, address pau) external view returns (DefaultRateLimits memory defaultRateLimits) { + defaultRateLimits = initRateLimits[key][pau]; + if (defaultRateLimits.maxAmount == 0 || defaultRateLimits.slope == 0) { + // If not set for specific pau, check in general + defaultRateLimits = initRateLimits[key][address(0)]; + } + } + + function isControllerActionEnabled(bytes32 key, address pau) external view returns (bool ok) { + ok = initControllerActions[key][address(0)] || // address(0) enabled for every pau + initControllerActions[key][pau]; + } + + // --- Admin functions --- + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + // TODO: for now roles system is outsourced to an external contract for the following functions: + + function addGovOps(address pau, address usr) external auth { + govOps[pau][usr] = 1; + emit AddGovOps(pau, usr); + } + + function delGovOps(address pau, address usr) external auth { + govOps[pau][usr] = 0; + emit DelGovOps(pau, usr); + } + + function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external auth { + initRateLimits[key][pau] = DefaultRateLimits(maxAmount, slope); + emit AddInitRateLimits(key, pau, maxAmount, slope); + } + + function delInitRateLimits(bytes32 key, address pau) external auth { + delete initRateLimits[key][pau]; + emit DelInitRateLimits(key, pau); + } + + function addInitControllerActions(bytes calldata data, address pau) external auth returns (bytes32 key) { + key = keccak256(data); + initControllerActions[key][pau] = true; + emit AddInitControllerActions(key, pau); + } + + function addInitControllerActions(bytes32 key, address pau) external auth { + // TODO: We will have to remove this function if finally having to save or log the raw data for enumeration purposes + initControllerActions[key][pau] = true; + emit AddInitControllerActions(key, pau); + } + + function delInitControllerActions(bytes32 key, address pau) external auth { + delete initControllerActions[key][pau]; + emit DelInitControllerActions(key, pau); + } + +} diff --git a/src/Option 4/Configurator.sol b/src/Option 4/Configurator.sol new file mode 100644 index 0000000..cf53e3e --- /dev/null +++ b/src/Option 4/Configurator.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +interface RateLimitsLike { + struct RateLimitData { + uint256 maxAmount; + uint256 slope; + uint256 lastAmount; + uint256 lastUpdated; + } + + function getRateLimitData(bytes32) external view returns (RateLimitData memory); + function setRateLimitData(bytes32, uint256, uint256, uint256, uint256) external; + function setUnlimitedRateLimitData(bytes32) external; +} + +interface ATWLStateLike { + function govOps(address, address) external view returns (uint256); + function getInitRateLimits(bytes32, address) external view returns (uint256, uint256); + function isControllerActionEnabled(bytes32, address) external view returns (bool); +} + +contract Configurator { + + // --- Storage variables --- + + mapping(address usr => uint256 allowed) public wards; + mapping(address usr => uint256 allowed) public bud; + mapping(address pau => mapping(bytes32 key => uint256 timestamp)) public zzz; + uint256 public hop; + uint256 public maxChange; + + // --- Immutables --- + + ATWLStateLike public immutable atwlState; + + // --- Constants --- + + uint256 internal constant WAD = 10**18; + + // --- Events --- + + event Rely(address indexed usr); + event Deny(address indexed usr); + event Kiss(address indexed usr); + event Diss(address indexed usr); + event File(bytes32 indexed what, uint256 data); + event SetRateLimit(address indexed pau, bytes32 indexed key, uint256 maxAmount, uint256 slope); + + // --- Modifiers --- + + modifier auth() { + require(wards[msg.sender] == 1, "Configurator/not-authorized"); + _; + } + + modifier govOps(address pau) { + require(atwlState.govOps(pau, msg.sender) == 1, "Configurator/not-authorized-govOps"); + _; + } + + // --- Constructor --- + + constructor(address atwlState_) { + require(atwlState_ != address(0), "Configurator/null-atwlState"); + atwlState = ATWLStateLike(atwlState_); + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Internal functions --- + + function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x < y ? x : y; + } + + // --- Admin functions --- + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + function kiss(address usr) external auth { + bud[usr] = 1; + emit Kiss(usr); + } + + function diss(address usr) external auth { + bud[usr] = 0; + emit Diss(usr); + } + + function file(bytes32 what, uint256 data) external auth { + if (what == "hop") { + hop = data; + } else if (what == "maxChange") { + require(data >= WAD, "Configurator/maxChange-below-1x"); + maxChange = data; + } else { + revert("Configurator/file-unrecognized-param"); + } + emit File(what, data); + } + + // GovOps functions + + function setRateLimit(address pau, bytes32 key, uint256 maxAmount, uint256 slope) external govOps(pau) { + (uint256 defMaxAmount, uint256 defSlope) = atwlState.getInitRateLimits(key, pau); + if (defMaxAmount == type(uint256).max && defMaxAmount == type(uint256).max) { + RateLimitsLike(pau).setUnlimitedRateLimitData(key); + emit SetRateLimit(pau, key, maxAmount, slope); + } else { + RateLimitsLike.RateLimitData memory current = RateLimitsLike(pau).getRateLimitData(key); + bool safe = maxAmount <= defMaxAmount && slope <= defSlope || maxAmount <= current.maxAmount && slope <= current.slope; + require(safe || block.timestamp >= zzz[pau][key] + hop, "Configurator/increment-too-soon"); + require(safe || maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD + require(safe || slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); + if (maxAmount >= current.maxAmount || slope >= current.slope) { + zzz[pau][key] = block.timestamp; + } + RateLimitsLike(pau).setRateLimitData(key, maxAmount, slope, _min(maxAmount, current.lastAmount), block.timestamp); + emit SetRateLimit(pau, key, maxAmount, slope); + } + } + + function callControllerAction(address pau, bytes calldata data) external govOps(pau) returns (bytes memory ret) { + require(atwlState.isControllerActionEnabled(keccak256(data), pau), "Configurator/not-valid-data"); + bool ok; + (ok, ret) = pau.call(data); + require(ok, "Configurator/call-failed"); + } +} From 9e48b91e98e38a33d6b1eb60c797155efe66cbcb Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 17 Dec 2025 20:04:48 -0300 Subject: [PATCH 003/150] Rename directories --- src/{Option 1 => Option_1}/ATWLState.sol | 0 src/{Option 1 => Option_1}/Configurator.sol | 0 src/{Option 2 => Option_2}/Configurator.sol | 0 src/{Option 3 => Option_3}/Configurator.sol | 0 src/{Option 3 => Option_3}/Role.sol | 0 src/{Option 4 => Option_4}/ATWLState.sol | 0 src/{Option 4 => Option_4}/Configurator.sol | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename src/{Option 1 => Option_1}/ATWLState.sol (100%) rename src/{Option 1 => Option_1}/Configurator.sol (100%) rename src/{Option 2 => Option_2}/Configurator.sol (100%) rename src/{Option 3 => Option_3}/Configurator.sol (100%) rename src/{Option 3 => Option_3}/Role.sol (100%) rename src/{Option 4 => Option_4}/ATWLState.sol (100%) rename src/{Option 4 => Option_4}/Configurator.sol (100%) diff --git a/src/Option 1/ATWLState.sol b/src/Option_1/ATWLState.sol similarity index 100% rename from src/Option 1/ATWLState.sol rename to src/Option_1/ATWLState.sol diff --git a/src/Option 1/Configurator.sol b/src/Option_1/Configurator.sol similarity index 100% rename from src/Option 1/Configurator.sol rename to src/Option_1/Configurator.sol diff --git a/src/Option 2/Configurator.sol b/src/Option_2/Configurator.sol similarity index 100% rename from src/Option 2/Configurator.sol rename to src/Option_2/Configurator.sol diff --git a/src/Option 3/Configurator.sol b/src/Option_3/Configurator.sol similarity index 100% rename from src/Option 3/Configurator.sol rename to src/Option_3/Configurator.sol diff --git a/src/Option 3/Role.sol b/src/Option_3/Role.sol similarity index 100% rename from src/Option 3/Role.sol rename to src/Option_3/Role.sol diff --git a/src/Option 4/ATWLState.sol b/src/Option_4/ATWLState.sol similarity index 100% rename from src/Option 4/ATWLState.sol rename to src/Option_4/ATWLState.sol diff --git a/src/Option 4/Configurator.sol b/src/Option_4/Configurator.sol similarity index 100% rename from src/Option 4/Configurator.sol rename to src/Option_4/Configurator.sol From 599a3f0d6d80b6dd711fb9ac438ea6510692baad Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 18 Dec 2025 10:35:18 -0300 Subject: [PATCH 004/150] Fixes --- src/Option_4/Configurator.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Option_4/Configurator.sol b/src/Option_4/Configurator.sol index cf53e3e..45b84d0 100644 --- a/src/Option_4/Configurator.sol +++ b/src/Option_4/Configurator.sol @@ -10,6 +10,7 @@ interface RateLimitsLike { } function getRateLimitData(bytes32) external view returns (RateLimitData memory); + function getCurrentRateLimit(bytes32) external view returns (uint256); function setRateLimitData(bytes32, uint256, uint256, uint256, uint256) external; function setUnlimitedRateLimitData(bytes32) external; } @@ -112,9 +113,9 @@ contract Configurator { function setRateLimit(address pau, bytes32 key, uint256 maxAmount, uint256 slope) external govOps(pau) { (uint256 defMaxAmount, uint256 defSlope) = atwlState.getInitRateLimits(key, pau); - if (defMaxAmount == type(uint256).max && defMaxAmount == type(uint256).max) { + if (defMaxAmount == type(uint256).max && defSlope == 0) { RateLimitsLike(pau).setUnlimitedRateLimitData(key); - emit SetRateLimit(pau, key, maxAmount, slope); + emit SetRateLimit(pau, key, type(uint256).max, 0); } else { RateLimitsLike.RateLimitData memory current = RateLimitsLike(pau).getRateLimitData(key); bool safe = maxAmount <= defMaxAmount && slope <= defSlope || maxAmount <= current.maxAmount && slope <= current.slope; @@ -124,7 +125,8 @@ contract Configurator { if (maxAmount >= current.maxAmount || slope >= current.slope) { zzz[pau][key] = block.timestamp; } - RateLimitsLike(pau).setRateLimitData(key, maxAmount, slope, _min(maxAmount, current.lastAmount), block.timestamp); + uint256 lastAmount = RateLimitsLike(pau).getCurrentRateLimit(key); + RateLimitsLike(pau).setRateLimitData(key, maxAmount, slope, _min(maxAmount, lastAmount), block.timestamp); emit SetRateLimit(pau, key, maxAmount, slope); } } From a3e4b519c238d32c08c82145f04341144361e943 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 18 Dec 2025 19:46:34 -0300 Subject: [PATCH 005/150] Role for option 4 --- src/Option_4/ATWLRole.sol | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/Option_4/ATWLRole.sol diff --git a/src/Option_4/ATWLRole.sol b/src/Option_4/ATWLRole.sol new file mode 100644 index 0000000..7e0a7a5 --- /dev/null +++ b/src/Option_4/ATWLRole.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +interface AtwlState { + function addGovOps(address pau, address usr) external; + function delGovOps(address pau, address usr) external; + function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external; + function addInitControllerActions(bytes calldata data, address pau) external returns (bytes32 key); + function delInitRateLimits(bytes32 key, address pau) external; + function addInitControllerActions(bytes32 key, address pau) external; + function delInitControllerActions(bytes32 key, address pau) external; +} + +abstract contract ATWLRole { + address immutable public signer; + AtwlState immutable public atwlState; + + modifier onlySigner() { + require(msg.sender == signer, "ATWLRole/not-signer"); + _; + } + + constructor(address signer_, address atwlState_) { + signer = signer_; + atwlState = AtwlState(atwlState_); + } +} + +contract ATWLRoleTimeLock is ATWLRole { + constructor(address signer_, address atwlState_) ATWLRole(signer_, atwlState_) { + } + + function addGovOps(address pau, address usr) external onlySigner { + atwlState.addGovOps(pau, usr); + } + + function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external onlySigner { + atwlState.addInitRateLimits(key, pau, maxAmount, slope); + } + + function addInitControllerActions(bytes calldata data, address pau) external onlySigner returns (bytes32 key) { + key = atwlState.addInitControllerActions(data, pau); + } + + function addInitControllerActions(bytes32 key, address pau) external onlySigner { + atwlState.addInitControllerActions(key, pau); + } +} + +contract ATWLRoleDirect is ATWLRole { + constructor(address signer_, address atwlState) ATWLRole(signer_, atwlState) { + } + + function delGovOps(address pau, address usr) external onlySigner { + atwlState.delGovOps(pau, usr); + } + + function delInitRateLimits(bytes32 key, address pau) external onlySigner { + atwlState.delInitRateLimits(key, pau); + } + + function delInitControllerActions(bytes32 key, address pau) external onlySigner { + atwlState.delInitControllerActions(key, pau); + } +} From acaedc05606203130596f178734f1efdd58017f8 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:06:57 +0700 Subject: [PATCH 006/150] Add timelock layer - wip --- .gitmodules | 3 + lib/openzeppelin-contracts | 1 + src/timelock/ATWLTimeLockedWrapper.sol | 331 ++++++++++ src/timelock/SkyTimelock.sol | 187 ++++++ src/timelock/TimelockKeeperJob.sol | 89 +++ test/timelock/SkyTimelock.t.sol | 839 +++++++++++++++++++++++++ 6 files changed, 1450 insertions(+) create mode 160000 lib/openzeppelin-contracts create mode 100644 src/timelock/ATWLTimeLockedWrapper.sol create mode 100644 src/timelock/SkyTimelock.sol create mode 100644 src/timelock/TimelockKeeperJob.sol create mode 100644 test/timelock/SkyTimelock.t.sol diff --git a/.gitmodules b/.gitmodules index 888d42d..690924b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..a83d9aa --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit a83d9aabbca1ad4be17acba3e1caeca90539d3cc diff --git a/src/timelock/ATWLTimeLockedWrapper.sol b/src/timelock/ATWLTimeLockedWrapper.sol new file mode 100644 index 0000000..a51cead --- /dev/null +++ b/src/timelock/ATWLTimeLockedWrapper.sol @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +// Code aligned to: https://github.com/sunbreak1211/pau/blob/a3e4b519c238d32c08c82145f04341144361e943/src/Option_4/Configurator.sol +// TODO: Rename `pau` parameter to `almController` and `rateLimiter` - current name is ambiguous (depends on external contract changes) + +import {SkyTimelock} from "./SkyTimelock.sol"; + +interface ATWLRoleTimeLockLike { + function addGovOps(address pau, address usr) external; + function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external; + function addInitControllerActions(bytes calldata data, address pau) external returns (bytes32 key); + // Note: The overloaded function addInitControllerActions(bytes32 key, address pau) from ATWLRole.sol (lines 45-46) is not supported +} + +interface MainnetControllerLike { + function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; + function setLayerZeroRecipient(uint32 destinationEndpointId, bytes32 layerZeroRecipient) external; + function setMaxSlippage(address pool, uint256 maxSlippage) external; + function setOTCBuffer(address exchange, address otcBuffer) external; + function setOTCRechargeRate(address exchange, uint256 rechargeRate18) external; + function setOTCWhitelistedAsset(address exchange, address asset, bool isWhitelisted) external; + function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; + function setUniswapV4TickLimits(bytes32 poolId, int24 tickLowerMin, int24 tickUpperMax, uint24 maxTickSpacing) external; +} + +struct RateLimitConfig { + bytes32 key; + address pau; + uint256 maxAmount; + uint256 slope; +} + +contract ATWLTimeLockedWrapper { + // --- Auth --- + mapping(address => uint256) public wards; + mapping(address => uint256) public buds; + + modifier auth() { + require(wards[msg.sender] == 1, "ATWLTimeLockedWrapper/not-authorized"); + _; + } + + modifier toll() { + require(buds[msg.sender] == 1, "ATWLTimeLockedWrapper/not-whitelisted"); + _; + } + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + function kiss(address usr) external auth { + buds[usr] = 1; + emit Kiss(usr); + } + + function diss(address usr) external auth { + buds[usr] = 0; + emit Diss(usr); + } + + // --- Events --- + event Rely(address indexed usr); + event Deny(address indexed usr); + event Kiss(address indexed usr); + event Diss(address indexed usr); + event ProposalSubmitted(bytes32 indexed operationId, string functionName); + + SkyTimelock public immutable timelock; + ATWLRoleTimeLockLike public immutable atwlRoleTimeLock; // TODO: consider changing to atwlState + address public immutable mainnetController; // TODO: unused for now + + constructor(address timelock_, address atwlRoleTimeLock_, address mainnetController_) { + timelock = SkyTimelock(payable(timelock_)); + atwlRoleTimeLock = ATWLRoleTimeLockLike(atwlRoleTimeLock_); + mainnetController = mainnetController_; + + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + function _submitProposal(bytes memory payload, bytes32 predecessor, bytes32 salt, uint256 delay) internal returns (bytes32 operationId) { + address[] memory targets = new address[](1); + targets[0] = address(atwlRoleTimeLock); + + uint256[] memory values = new uint256[](1); + values[0] = 0; + + bytes[] memory payloads = new bytes[](1); + payloads[0] = payload; + + operationId = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + } + + function _submitControllerAction(bytes memory controllerData, address pau, bytes32 predecessor, bytes32 salt, uint256 delay) internal returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + ATWLRoleTimeLockLike.addInitControllerActions.selector, + controllerData, + pau + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + } + + // --- Governance Operations --- + + function addGovOps(address pau, address usr, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + ATWLRoleTimeLockLike.addGovOps.selector, + pau, + usr + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "addGovOps"); + } + + // --- Rate Limits --- + + function addInitRateLimits( + RateLimitConfig calldata config, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + ATWLRoleTimeLockLike.addInitRateLimits.selector, + config.key, + config.pau, + config.maxAmount, + config.slope + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "addInitRateLimits"); + } + + function batchAddInitRateLimits( + RateLimitConfig[] calldata configs, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + uint256 len = configs.length; + require(len > 0, "ATWLTimeLockedWrapper/empty-configs"); + + address[] memory targets = new address[](len); + uint256[] memory values = new uint256[](len); + bytes[] memory payloads = new bytes[](len); + address target = address(atwlRoleTimeLock); + + for (uint256 i = 0; i < len; ++i) { + targets[i] = target; + payloads[i] = abi.encodeWithSelector( + ATWLRoleTimeLockLike.addInitRateLimits.selector, + configs[i].key, + configs[i].pau, + configs[i].maxAmount, + configs[i].slope + ); + } + + operationId = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + + emit ProposalSubmitted(operationId, "batchAddInitRateLimits"); + } + + // --- Controller Actions --- + + function setMintRecipient( + uint32 destinationDomain, + bytes32 mintRecipient, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setMintRecipient.selector, + destinationDomain, + mintRecipient + ); + + operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMintRecipient"); + } + + function setLayerZeroRecipient( + uint32 destinationEndpointId, + bytes32 layerZeroRecipient, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setLayerZeroRecipient.selector, + destinationEndpointId, + layerZeroRecipient + ); + + operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setLayerZeroRecipient"); + } + + function setMaxSlippage( + address pool, + uint256 maxSlippage, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setMaxSlippage.selector, + pool, + maxSlippage + ); + + operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMaxSlippage"); + } + + function setOTCBuffer( + address exchange, + address otcBuffer, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setOTCBuffer.selector, + exchange, + otcBuffer + ); + + operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setOTCBuffer"); + } + + function setOTCRechargeRate( + address exchange, + uint256 rechargeRate18, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setOTCRechargeRate.selector, + exchange, + rechargeRate18 + ); + + operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setOTCRechargeRate"); + } + + function setOTCWhitelistedAsset( + address exchange, + address asset, + bool isWhitelisted, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setOTCWhitelistedAsset.selector, + exchange, + asset, + isWhitelisted + ); + + operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setOTCWhitelistedAsset"); + } + + function setUniswapV4TickLimits( + bytes32 poolId, + int24 tickLowerMin, + int24 tickUpperMax, + uint24 maxTickSpacing, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setUniswapV4TickLimits.selector, + poolId, + tickLowerMin, + tickUpperMax, + maxTickSpacing + ); + + operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV4TickLimits"); + } + + function setMaxExchangeRate( + address token, + uint256 shares, + uint256 maxExpectedAssets, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setMaxExchangeRate.selector, + token, + shares, + maxExpectedAssets + ); + + operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMaxExchangeRate"); + } +} + diff --git a/src/timelock/SkyTimelock.sol b/src/timelock/SkyTimelock.sol new file mode 100644 index 0000000..a09b2a7 --- /dev/null +++ b/src/timelock/SkyTimelock.sol @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +contract SkyTimelock is TimelockController, Pausable { + using EnumerableSet for EnumerableSet.Bytes32Set; + + bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); + + // Operation tracking for keeper jobs - store parameters for execution + struct Operation { + address[] targets; + uint256[] values; + bytes[] payloads; + bytes32 predecessor; + bytes32 salt; + } + + EnumerableSet.Bytes32Set private _operationIds; + mapping(bytes32 id => Operation) public operations; // Public for keeper access + + // Changes from original timelock: + // - Do not allow proposers to change admin-only configurations + // - Support adding cancellers which are not proposers + // - Make execution permissionless + // - Add pausing logic + // - Allow admin to change the delay immediately + // - Do not allow proposals to change the delay + + // Notes: + // - By default all proposers can also cancel any proposal, which is considered OK since they can already create malicious proposals, + // and in the unlikely event they are malicious pausing can be used. + // - Cancellers can cancel any proposal, not only ones they created. They are considered trusted as well. + + constructor( + uint256 minDelay, + address admin, + address[] memory proposers, + address[] memory cancellers, // by default all proposers are added as cancellers, so no need to include them here + address[] memory pausers + ) TimelockController(minDelay, proposers, new address[](0), admin) { + + _revokeRole(DEFAULT_ADMIN_ROLE, address(this)); // do not allow proposers to change admin-only configurations + require(admin != address(0), "SkyTimelock: admin cannot be zero address"); + + // add cancellers which are not necessarily proposers + for (uint256 i = 0; i < cancellers.length; ++i) { + _grantRole(CANCELLER_ROLE, cancellers[i]); + } + + for (uint256 i = 0; i < pausers.length; ++i) { + _grantRole(PAUSER_ROLE, pausers[i]); + } + + _grantRole(EXECUTOR_ROLE, address(0)); // allow anyone to execute + } + + // ------------------------------------------------------------------------ + // Pausing logic + // ------------------------------------------------------------------------ + + function pause() external onlyRole(PAUSER_ROLE) { + _pause(); + } + + function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { + _unpause(); + } + + // ------------------------------------------------------------------------ + // Delay management + // ------------------------------------------------------------------------ + + /** + * @dev Change delay without timelock. + * Can not override updateDelay as we need an external call to the inherited function to change msg.sender. + */ + function updateDelayImmediately(uint256 newDelay) external onlyRole(DEFAULT_ADMIN_ROLE) { + this.updateDelay(newDelay); + } + + // ------------------------------------------------------------------------ + // Scheduling (blocked while paused) + // ------------------------------------------------------------------------ + + function schedule(address, uint256, bytes calldata, bytes32, bytes32, uint256) public pure override { + revert("SkyTimelock: use scheduleBatch() instead"); + } + + function scheduleBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public virtual override whenNotPaused { + uint256 len = targets.length; + for (uint256 i = 0; i < len; ++i) { + require(targets[i] != address(this), "SkyTimelock: self calls disabled"); + } + + bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); + super.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + + // Track operation for keeper jobs + _operationIds.add(id); + operations[id] = Operation(targets, values, payloads, predecessor, salt); + } + + function cancel(bytes32 id) public virtual override { + super.cancel(id); + _operationIds.remove(id); + delete operations[id]; + } + + // ------------------------------------------------------------------------ + // Execution (blocked while paused) + // ------------------------------------------------------------------------ + + function execute(address, uint256, bytes calldata, bytes32, bytes32) public payable override { + revert("SkyTimelock: use executeBatch() instead"); + } + + function executeBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) public payable virtual override whenNotPaused { + bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); + super.executeBatch(targets, values, payloads, predecessor, salt); + _operationIds.remove(id); + delete operations[id]; + } + + // ------------------------------------------------------------------------ + // Keeper job helpers + // ------------------------------------------------------------------------ + + function getNextExecutableOperation() public view returns (bytes32 id) { + uint256 length = _operationIds.length(); + for (uint256 i = 0; i < length; ++i) { + bytes32 operationId = _operationIds.at(i); + + // Check if operation is ready + if (!isOperationReady(operationId)) continue; + + // Check if predecessor is done, if any + Operation memory op = operations[operationId]; + if (op.predecessor != bytes32(0) && !isOperationDone(op.predecessor)) continue; + + return operationId; + } + + return bytes32(0); + } + + + function getOperationCount() public view returns (uint256) { + return _operationIds.length(); + } + + function getOperation(bytes32 id) public view returns (Operation memory op) { + return operations[id]; + } +} + diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol new file mode 100644 index 0000000..0a95ef7 --- /dev/null +++ b/src/timelock/TimelockKeeperJob.sol @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import {SkyTimelock} from "./SkyTimelock.sol"; + +interface IJob { + function workable(bytes32 network) external returns (bool, bytes memory); +} + +/** + * @title TimelockKeeperJob + * @dev Keeper job contract for executing ready timelock operations. + * + * This contract implements the dss-cron IJob interface to allow automated + * execution of ready timelock operations. It iterates through executable + * operations and executes them one at a time. + * + * Reference: https://github.com/sky-ecosystem/dss-cron/blob/master/src/FlapJob.sol + */ +contract TimelockKeeperJob is IJob { + SkyTimelock public immutable timelock; + + // --- Events --- + event Work(bytes32 indexed network, bytes32 indexed operationId); + + /** + * @param _timelock The SkyTimelock contract to execute operations from. + */ + constructor(address _timelock) { + require(_timelock != address(0), "TimelockKeeperJob: zero address"); + timelock = SkyTimelock(payable(_timelock)); + } + + /** + * @dev Executes the next executable operation. + * @param network The network identifier (unused, required by IJob interface) + */ + function work(bytes32 network, bytes calldata) public { + bytes32 id = timelock.getNextExecutableOperation(); + require(id != bytes32(0), "TimelockKeeperJob: no executable operation"); + + // Get operation parameters using the getter function + // Note: Public mapping getters don't work well with structs containing arrays, + // so we use the explicit getOperation() function + SkyTimelock.Operation memory op = timelock.getOperation(id); + + // Execute using executeBatch (works for both single and batch operations) + // Single operations are stored as arrays of length 1 + timelock.executeBatch{value: 0}(op.targets, op.values, op.payloads, op.predecessor, op.salt); + + emit Work(network, id); + } + + /** + * @dev Returns whether there is work to be done (an executable operation exists). + * Uses try/catch to test if work() would succeed. + * @param network The network identifier (unused, required by IJob interface) + * @return canWork true if work can be executed, false otherwise + * @return args The arguments to pass to work() (empty bytes) + */ + function workable(bytes32 network) external override returns (bool canWork, bytes memory args) { + bytes memory emptyArgs = ""; + + try this.work(network, emptyArgs) { + // Work succeeds + return (true, emptyArgs); + } catch { + // Can not work -- carry on + } + + return (false, bytes("No executable operation")); + } +} + diff --git a/test/timelock/SkyTimelock.t.sol b/test/timelock/SkyTimelock.t.sol new file mode 100644 index 0000000..43445c5 --- /dev/null +++ b/test/timelock/SkyTimelock.t.sol @@ -0,0 +1,839 @@ +// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.21; + +import {Test} from "forge-std/Test.sol"; +import {SkyTimelock} from "../../src/timelock/SkyTimelock.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +// Mock contract for testing timelock operations +contract MockTarget { + uint256 public value; + bool public called; + bytes public lastData; + uint256 public lastValue; + + function setValue(uint256 _value) external { + value = _value; + called = true; + } + + function setValueWithData(uint256 _value, bytes calldata _data) external { + value = _value; + lastData = _data; + called = true; + } + + function receiveEther() external payable { + lastValue = msg.value; + called = true; + } + + function revertCall() external pure { + revert("MockTarget: intentional revert"); + } +} + +contract SkyTimelockTest is Test { + SkyTimelock public timelock; + MockTarget public mockTarget; + + address public admin; + address public proposer; + address public canceller; + address public pauser; + address public executor; + address public other; + + uint256 public constant MIN_DELAY = 1 days; + bytes32 public constant SALT = keccak256("test-salt"); + + // Events for easier testing + event CallScheduled( + bytes32 indexed id, + uint256 indexed index, + address target, + uint256 value, + bytes data, + bytes32 predecessor, + uint256 delay + ); + + event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data); + + event Cancelled(bytes32 indexed id); + event MinDelayChange(uint256 oldDuration, uint256 newDuration); + event Paused(address account); + event Unpaused(address account); + + // Helper functions to convert single operations to batch format + function _schedule( + address target, + uint256 value, + bytes memory data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) internal { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory values = new uint256[](1); + values[0] = value; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + } + + function _execute( + address target, + uint256 value, + bytes memory data, + bytes32 predecessor, + bytes32 salt + ) internal { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory values = new uint256[](1); + values[0] = value; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + timelock.executeBatch{value: value}(targets, values, payloads, predecessor, salt); + } + + function setUp() public { + admin = address(0x1); + proposer = address(0x2); + canceller = address(0x3); + pauser = address(0x4); + executor = address(0x5); + other = address(0x6); + + address[] memory proposers = new address[](1); + proposers[0] = proposer; + + address[] memory cancellers = new address[](1); + cancellers[0] = canceller; + + address[] memory pausers = new address[](1); + pausers[0] = pauser; + + timelock = new SkyTimelock(MIN_DELAY, admin, proposers, cancellers, pausers); + mockTarget = new MockTarget(); + } + + // ============================================================================ + // Constructor and Initialization Tests + // ============================================================================ + + function testConstructorInitialization() public { + // Check min delay + assertEq(timelock.getMinDelay(), MIN_DELAY); + + // Check roles are set correctly + assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), admin)); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), proposer)); + // Proposers are automatically given CANCELLER_ROLE by parent constructor + assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), proposer)); + assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller)); + assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser)); + // address(0) means anyone can execute + assertTrue(timelock.hasRole(timelock.EXECUTOR_ROLE(), address(0))); + + // Key security feature: contract itself should not have admin role + assertFalse(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), address(timelock))); + } + + function testConstructorRevertsIfAdminIsZero() public { + address[] memory proposers = new address[](1); + proposers[0] = proposer; + address[] memory cancellers = new address[](0); + address[] memory pausers = new address[](0); + + vm.expectRevert("SkyTimelock: admin cannot be zero address"); + new SkyTimelock(MIN_DELAY, address(0), proposers, cancellers, pausers); + } + + function testConstructorMultipleProposers() public { + address[] memory proposers = new address[](2); + proposers[0] = address(0x10); + proposers[1] = address(0x11); + address[] memory cancellers = new address[](0); + address[] memory pausers = new address[](0); + + SkyTimelock newTimelock = new SkyTimelock(MIN_DELAY, admin, proposers, cancellers, pausers); + assertTrue(newTimelock.hasRole(newTimelock.PROPOSER_ROLE(), address(0x10))); + assertTrue(newTimelock.hasRole(newTimelock.PROPOSER_ROLE(), address(0x11))); + } + + // ============================================================================ + // Scheduling Tests + // ============================================================================ + + function testScheduleReverts() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + vm.prank(proposer); + vm.expectRevert("SkyTimelock: use scheduleBatch() instead"); + timelock.schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testScheduleSingleOperation() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + vm.expectEmit(true, true, true, true); + emit CallScheduled(id, 0, address(mockTarget), 0, data, bytes32(0), MIN_DELAY); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + assertEq(uint256(timelock.getTimestamp(id)), block.timestamp + MIN_DELAY); + } + + function testScheduleRevertsIfNotProposer() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(other); + vm.expectRevert(); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testScheduleRevertsIfDelayTooShort() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + vm.expectRevert(); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY - 1); + } + + function testScheduleRevertsIfPaused() public { + vm.prank(pauser); + timelock.pause(); + + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + vm.expectRevert(); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testScheduleRevertsOnSelfCall() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + vm.expectRevert("SkyTimelock: self calls disabled"); + _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testScheduleWithValue() public { + bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); + bytes32 id = _hashOperation(address(mockTarget), 1 ether, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); + + assertEq(uint256(timelock.getTimestamp(id)), block.timestamp + MIN_DELAY); + } + + function testScheduleWithPredecessor() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); + + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("salt2")); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data2, id1, keccak256("salt2"), MIN_DELAY); + + assertEq(uint256(timelock.getTimestamp(id2)), block.timestamp + MIN_DELAY); + } + + function testScheduleBatchMultipleOperations() public { + address[] memory targets = new address[](2); + targets[0] = address(mockTarget); + targets[1] = address(mockTarget); + + uint256[] memory values = new uint256[](2); + values[0] = 0; + values[1] = 0; + + bytes[] memory payloads = new bytes[](2); + payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); + payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); + + bytes32 id = _hashOperationBatch(targets, values, payloads, bytes32(0), SALT); + + vm.prank(proposer); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + + assertEq(uint256(timelock.getTimestamp(id)), block.timestamp + MIN_DELAY); + } + + function testScheduleBatchRevertsOnSelfCall() public { + address[] memory targets = new address[](2); + targets[0] = address(mockTarget); + targets[1] = address(timelock); // Self call + + uint256[] memory values = new uint256[](2); + values[0] = 0; + values[1] = 0; + + bytes[] memory payloads = new bytes[](2); + payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); + payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); + + vm.prank(proposer); + vm.expectRevert("SkyTimelock: self calls disabled"); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + } + + // ============================================================================ + // Execution Tests + // ============================================================================ + + function testExecuteReverts() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectRevert("SkyTimelock: use executeBatch() instead"); + timelock.execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testExecuteSingleOperation() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectEmit(true, true, true, true); + emit CallExecuted(id, 0, address(mockTarget), 0, data); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + + assertTrue(mockTarget.called()); + assertEq(mockTarget.value(), 42); + } + + function testExecutePermissionless() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Anyone can execute + vm.prank(other); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + + assertTrue(mockTarget.called()); + } + + function testExecuteRevertsIfNotReady() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + // Try to execute before delay + vm.expectRevert(); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testExecuteRevertsIfPaused() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.prank(pauser); + timelock.pause(); + + vm.expectRevert(); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testExecuteWithValue() public { + bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); + bytes32 id = _hashOperation(address(mockTarget), 1 ether, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.deal(address(timelock), 1 ether); + _execute(address(mockTarget), 1 ether, data, bytes32(0), SALT); + + assertTrue(mockTarget.called()); + assertEq(mockTarget.lastValue(), 1 ether); + } + + function testExecuteRevertsIfTargetReverts() public { + bytes memory data = abi.encodeWithSelector(MockTarget.revertCall.selector); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectRevert("MockTarget: intentional revert"); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testExecuteWithPredecessor() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); + + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("salt2")); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data2, id1, keccak256("salt2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Execute first operation + _execute(address(mockTarget), 0, data1, bytes32(0), SALT); + assertEq(mockTarget.value(), 1); + + // Execute second operation (requires predecessor to be done) + _execute(address(mockTarget), 0, data2, id1, keccak256("salt2")); + assertEq(mockTarget.value(), 2); + } + + function testExecuteRevertsIfPredecessorNotDone() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); + + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("salt2")); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data2, id1, keccak256("salt2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Try to execute second operation before first + vm.expectRevert(); + _execute(address(mockTarget), 0, data2, id1, keccak256("salt2")); + } + + function testExecuteBatchMultipleOperations() public { + address[] memory targets = new address[](2); + targets[0] = address(mockTarget); + targets[1] = address(mockTarget); + + uint256[] memory values = new uint256[](2); + values[0] = 0; + values[1] = 0; + + bytes[] memory payloads = new bytes[](2); + payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); + payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); + + bytes32 id = _hashOperationBatch(targets, values, payloads, bytes32(0), SALT); + + vm.prank(proposer); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + timelock.executeBatch(targets, values, payloads, bytes32(0), SALT); + + // Last operation sets value to 20 + assertEq(mockTarget.value(), 20); + } + + // ============================================================================ + // Cancellation Tests + // ============================================================================ + + function testCancelProposerCanCancel() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(proposer); + vm.expectEmit(true, false, false, false); + emit Cancelled(id); + timelock.cancel(id); + + assertEq(uint256(timelock.getTimestamp(id)), 0); + } + + function testCancelCancellerCanCancel() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(canceller); + timelock.cancel(id); + + assertEq(uint256(timelock.getTimestamp(id)), 0); + } + + function testCancelRevertsIfNotAuthorized() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(other); + vm.expectRevert(); + timelock.cancel(id); + } + + function testCancelCannotCancelAfterExecution() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + vm.expectRevert(); + timelock.cancel(id); + } + + // ============================================================================ + // Pausing Tests + // ============================================================================ + + function testPausePauserCanPause() public { + vm.prank(pauser); + vm.expectEmit(true, false, false, false); + emit Paused(pauser); + timelock.pause(); + + assertTrue(timelock.paused()); + } + + function testPauseRevertsIfNotPauser() public { + vm.prank(other); + vm.expectRevert(); + timelock.pause(); + } + + function testUnpauseAdminCanUnpause() public { + vm.prank(pauser); + timelock.pause(); + + vm.prank(admin); + vm.expectEmit(true, false, false, false); + emit Unpaused(admin); + timelock.unpause(); + + assertFalse(timelock.paused()); + } + + function testUnpauseRevertsIfNotAdmin() public { + vm.prank(pauser); + timelock.pause(); + + vm.prank(other); + vm.expectRevert(); + timelock.unpause(); + } + + function testPauseBlocksScheduling() public { + vm.prank(pauser); + timelock.pause(); + + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + vm.expectRevert(); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testPauseBlocksExecution() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.prank(pauser); + timelock.pause(); + + vm.expectRevert(); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testPauseBlocksScheduleBatch() public { + vm.prank(pauser); + timelock.pause(); + + address[] memory targets = new address[](1); + targets[0] = address(mockTarget); + uint256[] memory values = new uint256[](1); + values[0] = 0; + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); + + vm.prank(proposer); + vm.expectRevert(); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + } + + function testPauseBlocksExecuteBatch() public { + address[] memory targets = new address[](1); + targets[0] = address(mockTarget); + uint256[] memory values = new uint256[](1); + values[0] = 0; + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); + + vm.prank(proposer); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.prank(pauser); + timelock.pause(); + + vm.expectRevert(); + timelock.executeBatch(targets, values, payloads, bytes32(0), SALT); + } + + // ============================================================================ + // Delay Management Tests + // ============================================================================ + + function testUpdateDelayImmediatelyAdminCanUpdate() public { + uint256 newDelay = 2 days; + + vm.prank(admin); + vm.expectEmit(true, false, false, false); + emit MinDelayChange(MIN_DELAY, newDelay); + timelock.updateDelayImmediately(newDelay); + + assertEq(timelock.getMinDelay(), newDelay); + } + + function testUpdateDelayImmediatelyRevertsIfNotAdmin() public { + vm.prank(other); + vm.expectRevert(); + timelock.updateDelayImmediately(2 days); + } + + function testUpdateDelayImmediatelyProposerCannotChangeDelay() public { + bytes memory data = abi.encodeWithSelector( + SkyTimelock.updateDelayImmediately.selector, + 2 days + ); + + // Try to schedule a call to updateDelayImmediately + vm.prank(proposer); + vm.expectRevert("SkyTimelock: self calls disabled"); + _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testUpdateDelayImmediatelyNewDelayAppliesToFutureOperations() public { + uint256 newDelay = 2 days; + + vm.prank(admin); + timelock.updateDelayImmediately(newDelay); + + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + // Old delay should fail + vm.expectRevert(); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + // New delay should work + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, newDelay); + } + + // ============================================================================ + // Security Tests - Self Call Prevention + // ============================================================================ + + function testSelfCallPreventionSchedule() public { + bytes memory data = abi.encodeWithSelector( + TimelockController.updateDelay.selector, + 2 days + ); + + vm.prank(proposer); + vm.expectRevert("SkyTimelock: self calls disabled"); + _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testSelfCallPreventionScheduleBatch() public { + address[] memory targets = new address[](1); + targets[0] = address(timelock); + + uint256[] memory values = new uint256[](1); + values[0] = 0; + + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + TimelockController.updateDelay.selector, + 2 days + ); + + vm.prank(proposer); + vm.expectRevert("SkyTimelock: self calls disabled"); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + } + + function testSelfCallPreventionCannotGrantAdminRole() public { + bytes memory data = abi.encodeWithSelector( + AccessControl.grantRole.selector, + timelock.DEFAULT_ADMIN_ROLE(), + proposer + ); + + vm.prank(proposer); + vm.expectRevert("SkyTimelock: self calls disabled"); + _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + // ============================================================================ + // Edge Cases and Integration Tests + // ============================================================================ + + function testOperationStateUnsetToWaitingToReady() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + // Initially unset + assertEq(uint256(timelock.getTimestamp(id)), 0); + + // Schedule - now waiting + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + assertGt(uint256(timelock.getTimestamp(id)), 0); + + // After delay - ready + vm.warp(block.timestamp + MIN_DELAY); + assertTrue(timelock.isOperationReady(id)); + + // Execute - done + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + assertTrue(timelock.isOperationDone(id)); + } + + function testMultipleOperationsSameTarget() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); + + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, bytes32(0), keccak256("salt2")); + + vm.startPrank(proposer); + _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); + _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("salt2"), MIN_DELAY); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + _execute(address(mockTarget), 0, data1, bytes32(0), SALT); + assertEq(mockTarget.value(), 1); + + _execute(address(mockTarget), 0, data2, bytes32(0), keccak256("salt2")); + assertEq(mockTarget.value(), 2); + } + + function testFullWorkflowScheduleExecuteCancel() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + // Schedule + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + assertGt(uint256(timelock.getTimestamp(id)), 0); + + // Cancel + vm.prank(canceller); + timelock.cancel(id); + assertEq(uint256(timelock.getTimestamp(id)), 0); + + // Try to execute (should fail) + vm.warp(block.timestamp + MIN_DELAY); + vm.expectRevert(); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testPauseUnpauseResumeOperations() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + // Schedule + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + // Pause + vm.prank(pauser); + timelock.pause(); + + // Unpause + vm.prank(admin); + timelock.unpause(); + + // Execute should work now + vm.warp(block.timestamp + MIN_DELAY); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + assertTrue(mockTarget.called()); + } + + // ============================================================================ + // Helper Functions + // ============================================================================ + + function _hashOperation( + address target, + uint256 value, + bytes memory data, + bytes32 predecessor, + bytes32 salt + ) internal view returns (bytes32) { + // Convert to batch format for hashing + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory values = new uint256[](1); + values[0] = value; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + } + + function _hashOperationBatch( + address[] memory targets, + uint256[] memory values, + bytes[] memory payloads, + bytes32 predecessor, + bytes32 salt + ) internal pure returns (bytes32) { + return keccak256(abi.encode(targets, values, payloads, predecessor, salt)); + } +} + From 9e9dd0a8867adb2fbf8ecae8f80f5805b1c7037c Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:03:01 +0700 Subject: [PATCH 007/150] Added Timelock Intended Roles and Actors in README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e69de29..7f68416 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,6 @@ +## Timelock Intended Roles and Actors: + +- admin - Pause Proxy +- proposers (can also cancel by default) - Core Council +- (additional) cancellers - empty, or possibly add Mom and/or Emergency-Wallet +- pauser - Core Council, Mom/Emergency-Wallet \ No newline at end of file From a263a3216b531e764d092fa25e0fca31e9f44391 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:29:27 +0700 Subject: [PATCH 008/150] Fix comments and error strings --- src/timelock/SkyTimelock.sol | 13 ++++++------- src/timelock/TimelockKeeperJob.sol | 4 ++-- test/timelock/SkyTimelock.t.sol | 18 +++++++++--------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/timelock/SkyTimelock.sol b/src/timelock/SkyTimelock.sol index a09b2a7..b320420 100644 --- a/src/timelock/SkyTimelock.sol +++ b/src/timelock/SkyTimelock.sol @@ -46,9 +46,8 @@ contract SkyTimelock is TimelockController, Pausable { // - Do not allow proposals to change the delay // Notes: - // - By default all proposers can also cancel any proposal, which is considered OK since they can already create malicious proposals, - // and in the unlikely event they are malicious pausing can be used. - // - Cancellers can cancel any proposal, not only ones they created. They are considered trusted as well. + // - By default all proposers can also cancel any proposal, this should be taken into account to make sure that they are trusted and that specific cancellations do not cause big harm. + // - Cancellers can cancel any proposal, not only ones they created. Same assumptions as above apply. constructor( uint256 minDelay, @@ -59,7 +58,7 @@ contract SkyTimelock is TimelockController, Pausable { ) TimelockController(minDelay, proposers, new address[](0), admin) { _revokeRole(DEFAULT_ADMIN_ROLE, address(this)); // do not allow proposers to change admin-only configurations - require(admin != address(0), "SkyTimelock: admin cannot be zero address"); + require(admin != address(0), "SkyTimelock/admin-zero-address"); // add cancellers which are not necessarily proposers for (uint256 i = 0; i < cancellers.length; ++i) { @@ -102,7 +101,7 @@ contract SkyTimelock is TimelockController, Pausable { // ------------------------------------------------------------------------ function schedule(address, uint256, bytes calldata, bytes32, bytes32, uint256) public pure override { - revert("SkyTimelock: use scheduleBatch() instead"); + revert("SkyTimelock/use-scheduleBatch"); } function scheduleBatch( @@ -115,7 +114,7 @@ contract SkyTimelock is TimelockController, Pausable { ) public virtual override whenNotPaused { uint256 len = targets.length; for (uint256 i = 0; i < len; ++i) { - require(targets[i] != address(this), "SkyTimelock: self calls disabled"); + require(targets[i] != address(this), "SkyTimelock/self-calls-disabled"); } bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); @@ -137,7 +136,7 @@ contract SkyTimelock is TimelockController, Pausable { // ------------------------------------------------------------------------ function execute(address, uint256, bytes calldata, bytes32, bytes32) public payable override { - revert("SkyTimelock: use executeBatch() instead"); + revert("SkyTimelock/use-executeBatch"); } function executeBatch( diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol index 0a95ef7..13199a0 100644 --- a/src/timelock/TimelockKeeperJob.sol +++ b/src/timelock/TimelockKeeperJob.sol @@ -42,7 +42,7 @@ contract TimelockKeeperJob is IJob { * @param _timelock The SkyTimelock contract to execute operations from. */ constructor(address _timelock) { - require(_timelock != address(0), "TimelockKeeperJob: zero address"); + require(_timelock != address(0), "TimelockKeeperJob/zero-address"); timelock = SkyTimelock(payable(_timelock)); } @@ -52,7 +52,7 @@ contract TimelockKeeperJob is IJob { */ function work(bytes32 network, bytes calldata) public { bytes32 id = timelock.getNextExecutableOperation(); - require(id != bytes32(0), "TimelockKeeperJob: no executable operation"); + require(id != bytes32(0), "TimelockKeeperJob/no-executable-operation"); // Get operation parameters using the getter function // Note: Public mapping getters don't work well with structs containing arrays, diff --git a/test/timelock/SkyTimelock.t.sol b/test/timelock/SkyTimelock.t.sol index 43445c5..1d88821 100644 --- a/test/timelock/SkyTimelock.t.sol +++ b/test/timelock/SkyTimelock.t.sol @@ -151,7 +151,7 @@ contract SkyTimelockTest is Test { address[] memory cancellers = new address[](0); address[] memory pausers = new address[](0); - vm.expectRevert("SkyTimelock: admin cannot be zero address"); + vm.expectRevert("SkyTimelock/admin-zero-address"); new SkyTimelock(MIN_DELAY, address(0), proposers, cancellers, pausers); } @@ -174,7 +174,7 @@ contract SkyTimelockTest is Test { function testScheduleReverts() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); vm.prank(proposer); - vm.expectRevert("SkyTimelock: use scheduleBatch() instead"); + vm.expectRevert("SkyTimelock/use-scheduleBatch"); timelock.schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); } @@ -221,7 +221,7 @@ contract SkyTimelockTest is Test { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); vm.prank(proposer); - vm.expectRevert("SkyTimelock: self calls disabled"); + vm.expectRevert("SkyTimelock/self-calls-disabled"); _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); } @@ -286,7 +286,7 @@ contract SkyTimelockTest is Test { payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); vm.prank(proposer); - vm.expectRevert("SkyTimelock: self calls disabled"); + vm.expectRevert("SkyTimelock/self-calls-disabled"); timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); } @@ -300,7 +300,7 @@ contract SkyTimelockTest is Test { _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); - vm.expectRevert("SkyTimelock: use executeBatch() instead"); + vm.expectRevert("SkyTimelock/use-executeBatch"); timelock.execute(address(mockTarget), 0, data, bytes32(0), SALT); } @@ -651,7 +651,7 @@ contract SkyTimelockTest is Test { // Try to schedule a call to updateDelayImmediately vm.prank(proposer); - vm.expectRevert("SkyTimelock: self calls disabled"); + vm.expectRevert("SkyTimelock/self-calls-disabled"); _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); } @@ -684,7 +684,7 @@ contract SkyTimelockTest is Test { ); vm.prank(proposer); - vm.expectRevert("SkyTimelock: self calls disabled"); + vm.expectRevert("SkyTimelock/self-calls-disabled"); _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); } @@ -702,7 +702,7 @@ contract SkyTimelockTest is Test { ); vm.prank(proposer); - vm.expectRevert("SkyTimelock: self calls disabled"); + vm.expectRevert("SkyTimelock/self-calls-disabled"); timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); } @@ -714,7 +714,7 @@ contract SkyTimelockTest is Test { ); vm.prank(proposer); - vm.expectRevert("SkyTimelock: self calls disabled"); + vm.expectRevert("SkyTimelock/self-calls-disabled"); _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); } From 8082adc8b856d9a699c586f5f9c2f0e79bd12bc6 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:47:53 +0700 Subject: [PATCH 009/150] Add comment, move id calc --- src/timelock/SkyTimelock.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/timelock/SkyTimelock.sol b/src/timelock/SkyTimelock.sol index b320420..6489a1c 100644 --- a/src/timelock/SkyTimelock.sol +++ b/src/timelock/SkyTimelock.sol @@ -117,10 +117,10 @@ contract SkyTimelock is TimelockController, Pausable { require(targets[i] != address(this), "SkyTimelock/self-calls-disabled"); } - bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); super.scheduleBatch(targets, values, payloads, predecessor, salt, delay); // Track operation for keeper jobs + bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); _operationIds.add(id); operations[id] = Operation(targets, values, payloads, predecessor, salt); } @@ -146,8 +146,8 @@ contract SkyTimelock is TimelockController, Pausable { bytes32 predecessor, bytes32 salt ) public payable virtual override whenNotPaused { - bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); super.executeBatch(targets, values, payloads, predecessor, salt); + bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); _operationIds.remove(id); delete operations[id]; } @@ -156,6 +156,9 @@ contract SkyTimelock is TimelockController, Pausable { // Keeper job helpers // ------------------------------------------------------------------------ + // Operations may still not be executable due to various downstream conditions. + // It is assumed that this is not a perfect fetching mechanism and that if needed proposals + // can be executed without cron keepers, or canceled in case they are jamming this mechanism. function getNextExecutableOperation() public view returns (bytes32 id) { uint256 length = _operationIds.length(); for (uint256 i = 0; i < length; ++i) { From ada56354fe70c6846098eaac2b2875b08bc2f52b Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:11:27 +0700 Subject: [PATCH 010/150] Keeper Job Changes --- src/timelock/TimelockKeeperJob.sol | 91 +++++++++++++++--------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol index 13199a0..731daa3 100644 --- a/src/timelock/TimelockKeeperJob.sol +++ b/src/timelock/TimelockKeeperJob.sol @@ -16,73 +16,76 @@ pragma solidity ^0.8.21; -import {SkyTimelock} from "./SkyTimelock.sol"; - interface IJob { function workable(bytes32 network) external returns (bool, bytes memory); } -/** - * @title TimelockKeeperJob - * @dev Keeper job contract for executing ready timelock operations. - * - * This contract implements the dss-cron IJob interface to allow automated - * execution of ready timelock operations. It iterates through executable - * operations and executes them one at a time. - * - * Reference: https://github.com/sky-ecosystem/dss-cron/blob/master/src/FlapJob.sol - */ +interface SequencerLike { + function isMaster(bytes32 network) external view returns (bool); +} + +interface SkyTimelockLike { + struct Operation { + address[] targets; + uint256[] values; + bytes[] payloads; + bytes32 predecessor; + bytes32 salt; + } + + function getNextExecutableOperation() external view returns (bytes32 id); + function getOperation(bytes32 id) external view returns (Operation memory op); + function executeBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) external payable; +} + contract TimelockKeeperJob is IJob { - SkyTimelock public immutable timelock; + + SequencerLike public immutable sequencer; + SkyTimelockLike public immutable timelock; + + // --- Errors --- + error NotMaster(bytes32 network); + error NoExecutableOperation(); // --- Events --- event Work(bytes32 indexed network, bytes32 indexed operationId); - /** - * @param _timelock The SkyTimelock contract to execute operations from. - */ - constructor(address _timelock) { - require(_timelock != address(0), "TimelockKeeperJob/zero-address"); - timelock = SkyTimelock(payable(_timelock)); + constructor(address _sequencer, address _timelock) { + sequencer = SequencerLike(_sequencer); + timelock = SkyTimelockLike(_timelock); } - /** - * @dev Executes the next executable operation. - * @param network The network identifier (unused, required by IJob interface) - */ - function work(bytes32 network, bytes calldata) public { + function work(bytes32 network, bytes calldata) external { + if (!sequencer.isMaster(network)) revert NotMaster(network); + bytes32 id = timelock.getNextExecutableOperation(); - require(id != bytes32(0), "TimelockKeeperJob/no-executable-operation"); + if (id == bytes32(0)) revert NoExecutableOperation(); - // Get operation parameters using the getter function - // Note: Public mapping getters don't work well with structs containing arrays, - // so we use the explicit getOperation() function - SkyTimelock.Operation memory op = timelock.getOperation(id); + SkyTimelockLike.Operation memory op = timelock.getOperation(id); - // Execute using executeBatch (works for both single and batch operations) - // Single operations are stored as arrays of length 1 + // Assume that in case a proposal needs eth the timelock is pre-funded, or alternatively it is executed manually timelock.executeBatch{value: 0}(op.targets, op.values, op.payloads, op.predecessor, op.salt); emit Work(network, id); } - /** - * @dev Returns whether there is work to be done (an executable operation exists). - * Uses try/catch to test if work() would succeed. - * @param network The network identifier (unused, required by IJob interface) - * @return canWork true if work can be executed, false otherwise - * @return args The arguments to pass to work() (empty bytes) - */ - function workable(bytes32 network) external override returns (bool canWork, bytes memory args) { - bytes memory emptyArgs = ""; - - try this.work(network, emptyArgs) { + function workable(bytes32 network) external override returns (bool, bytes memory) { + if (!sequencer.isMaster(network)) return (false, bytes("Network is not master")); + + bytes memory args = ""; + try this.work(network, args) { // Work succeeds - return (true, emptyArgs); + return (true, args); } catch { // Can not work -- carry on } - + return (false, bytes("No executable operation")); } } From 17d2e4b490d02de96cc165d90ad147749e8fc68e Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:56:20 +0700 Subject: [PATCH 011/150] Refactors --- src/timelock/ATWLTimeLockedWrapper.sol | 75 +++++++++++++++----------- src/timelock/SkyTimelock.sol | 5 +- test/timelock/SkyTimelock.t.sol | 13 +++++ 3 files changed, 58 insertions(+), 35 deletions(-) diff --git a/src/timelock/ATWLTimeLockedWrapper.sol b/src/timelock/ATWLTimeLockedWrapper.sol index a51cead..99a140c 100644 --- a/src/timelock/ATWLTimeLockedWrapper.sol +++ b/src/timelock/ATWLTimeLockedWrapper.sol @@ -1,10 +1,25 @@ +// SPDX-FileCopyrightText: © 2025 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + pragma solidity ^0.8.21; // Code aligned to: https://github.com/sunbreak1211/pau/blob/a3e4b519c238d32c08c82145f04341144361e943/src/Option_4/Configurator.sol // TODO: Rename `pau` parameter to `almController` and `rateLimiter` - current name is ambiguous (depends on external contract changes) -import {SkyTimelock} from "./SkyTimelock.sol"; +import {SkyTimelock} from "src/timelock/SkyTimelock.sol"; interface ATWLRoleTimeLockLike { function addGovOps(address pau, address usr) external; @@ -73,13 +88,13 @@ contract ATWLTimeLockedWrapper { event Diss(address indexed usr); event ProposalSubmitted(bytes32 indexed operationId, string functionName); - SkyTimelock public immutable timelock; + SkyTimelock public immutable timelock; ATWLRoleTimeLockLike public immutable atwlRoleTimeLock; // TODO: consider changing to atwlState - address public immutable mainnetController; // TODO: unused for now + address public immutable mainnetController; // TODO: unused for now constructor(address timelock_, address atwlRoleTimeLock_, address mainnetController_) { - timelock = SkyTimelock(payable(timelock_)); - atwlRoleTimeLock = ATWLRoleTimeLockLike(atwlRoleTimeLock_); + timelock = SkyTimelock(payable(timelock_)); + atwlRoleTimeLock = ATWLRoleTimeLockLike(atwlRoleTimeLock_); mainnetController = mainnetController_; wards[msg.sender] = 1; @@ -100,16 +115,6 @@ contract ATWLTimeLockedWrapper { timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); } - function _submitControllerAction(bytes memory controllerData, address pau, bytes32 predecessor, bytes32 salt, uint256 delay) internal returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector( - ATWLRoleTimeLockLike.addInitControllerActions.selector, - controllerData, - pau - ); - - operationId = _submitProposal(payload, predecessor, salt, delay); - } - // --- Governance Operations --- function addGovOps(address pau, address usr, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { @@ -189,8 +194,9 @@ contract ATWLTimeLockedWrapper { destinationDomain, mintRecipient ); - - operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMintRecipient"); } @@ -207,8 +213,9 @@ contract ATWLTimeLockedWrapper { destinationEndpointId, layerZeroRecipient ); - - operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setLayerZeroRecipient"); } @@ -225,8 +232,9 @@ contract ATWLTimeLockedWrapper { pool, maxSlippage ); - - operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxSlippage"); } @@ -243,8 +251,9 @@ contract ATWLTimeLockedWrapper { exchange, otcBuffer ); - - operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCBuffer"); } @@ -261,8 +270,9 @@ contract ATWLTimeLockedWrapper { exchange, rechargeRate18 ); - - operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCRechargeRate"); } @@ -281,8 +291,9 @@ contract ATWLTimeLockedWrapper { asset, isWhitelisted ); - - operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCWhitelistedAsset"); } @@ -303,8 +314,9 @@ contract ATWLTimeLockedWrapper { tickUpperMax, maxTickSpacing ); - - operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV4TickLimits"); } @@ -323,8 +335,9 @@ contract ATWLTimeLockedWrapper { shares, maxExpectedAssets ); - - operationId = _submitControllerAction(controllerData, pau, predecessor, salt, delay); + bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxExchangeRate"); } } diff --git a/src/timelock/SkyTimelock.sol b/src/timelock/SkyTimelock.sol index 6489a1c..acbc192 100644 --- a/src/timelock/SkyTimelock.sol +++ b/src/timelock/SkyTimelock.sol @@ -88,10 +88,7 @@ contract SkyTimelock is TimelockController, Pausable { // Delay management // ------------------------------------------------------------------------ - /** - * @dev Change delay without timelock. - * Can not override updateDelay as we need an external call to the inherited function to change msg.sender. - */ + // Can not override updateDelay as we need an external call to the inherited function to change msg.sender. function updateDelayImmediately(uint256 newDelay) external onlyRole(DEFAULT_ADMIN_ROLE) { this.updateDelay(newDelay); } diff --git a/test/timelock/SkyTimelock.t.sol b/test/timelock/SkyTimelock.t.sol index 1d88821..e52b5d6 100644 --- a/test/timelock/SkyTimelock.t.sol +++ b/test/timelock/SkyTimelock.t.sol @@ -1,5 +1,18 @@ // SPDX-FileCopyrightText: © 2025 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . pragma solidity ^0.8.21; From 6ee9458435579032b98e3361fac2baf154cf6bab Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 6 Jan 2026 13:32:41 -0300 Subject: [PATCH 012/150] Changes --- src/Option_4/ATWLRole.sol | 65 --------------- src/Option_4/BeamRole.sol | 75 +++++++++++++++++ src/Option_4/{ATWLState.sol => BeamState.sol} | 44 ++++++---- src/Option_4/Configurator.sol | 80 ++++++++++--------- 4 files changed, 145 insertions(+), 119 deletions(-) delete mode 100644 src/Option_4/ATWLRole.sol create mode 100644 src/Option_4/BeamRole.sol rename src/Option_4/{ATWLState.sol => BeamState.sol} (71%) diff --git a/src/Option_4/ATWLRole.sol b/src/Option_4/ATWLRole.sol deleted file mode 100644 index 7e0a7a5..0000000 --- a/src/Option_4/ATWLRole.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -interface AtwlState { - function addGovOps(address pau, address usr) external; - function delGovOps(address pau, address usr) external; - function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external; - function addInitControllerActions(bytes calldata data, address pau) external returns (bytes32 key); - function delInitRateLimits(bytes32 key, address pau) external; - function addInitControllerActions(bytes32 key, address pau) external; - function delInitControllerActions(bytes32 key, address pau) external; -} - -abstract contract ATWLRole { - address immutable public signer; - AtwlState immutable public atwlState; - - modifier onlySigner() { - require(msg.sender == signer, "ATWLRole/not-signer"); - _; - } - - constructor(address signer_, address atwlState_) { - signer = signer_; - atwlState = AtwlState(atwlState_); - } -} - -contract ATWLRoleTimeLock is ATWLRole { - constructor(address signer_, address atwlState_) ATWLRole(signer_, atwlState_) { - } - - function addGovOps(address pau, address usr) external onlySigner { - atwlState.addGovOps(pau, usr); - } - - function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external onlySigner { - atwlState.addInitRateLimits(key, pau, maxAmount, slope); - } - - function addInitControllerActions(bytes calldata data, address pau) external onlySigner returns (bytes32 key) { - key = atwlState.addInitControllerActions(data, pau); - } - - function addInitControllerActions(bytes32 key, address pau) external onlySigner { - atwlState.addInitControllerActions(key, pau); - } -} - -contract ATWLRoleDirect is ATWLRole { - constructor(address signer_, address atwlState) ATWLRole(signer_, atwlState) { - } - - function delGovOps(address pau, address usr) external onlySigner { - atwlState.delGovOps(pau, usr); - } - - function delInitRateLimits(bytes32 key, address pau) external onlySigner { - atwlState.delInitRateLimits(key, pau); - } - - function delInitControllerActions(bytes32 key, address pau) external onlySigner { - atwlState.delInitControllerActions(key, pau); - } -} diff --git a/src/Option_4/BeamRole.sol b/src/Option_4/BeamRole.sol new file mode 100644 index 0000000..c1e64a4 --- /dev/null +++ b/src/Option_4/BeamRole.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +interface BeamState { + function addCBeam(address cBeam) external; + function delCBeam(address cBeam) external; + function addCBeamForRBeam(address rBeam, address cBeam) external; + function delCBeamForRBeam(address rBeam, address cBeam) external; + function addInitRateLimits(bytes32 key, address rBeam, uint256 maxAmount, uint256 slope) external; + function addInitControllerActions(bytes calldata data, address rBeam) external returns (bytes32 key); + function delInitRateLimits(bytes32 key, address rBeam) external; + function addInitControllerActions(bytes32 key, address rBeam) external; + function delInitControllerActions(bytes32 key, address rBeam) external; +} + +abstract contract BeamRole { + address immutable public signer; + BeamState immutable public beamState; + + modifier onlySigner() { + require(msg.sender == signer, "BeamRole/not-signer"); + _; + } + + constructor(address signer_, address beamState_) { + signer = signer_; + beamState = BeamState(beamState_); + } +} + +contract BeamRoleTimeLock is BeamRole { + constructor(address signer_, address beamState_) BeamRole(signer_, beamState_) { + } + + function addCBeam(address cBeam) external onlySigner { + beamState.addCBeam(cBeam); + } + + function addInitRateLimits(bytes32 key, address rBeam, uint256 maxAmount, uint256 slope) external onlySigner { + beamState.addInitRateLimits(key, rBeam, maxAmount, slope); + } + + function addInitControllerActions(bytes calldata data, address rBeam) external onlySigner returns (bytes32 key) { + key = beamState.addInitControllerActions(data, rBeam); + } + + function addInitControllerActions(bytes32 key, address rBeam) external onlySigner { + beamState.addInitControllerActions(key, rBeam); + } +} + +contract BeamRoleDirect is BeamRole { + constructor(address signer_, address beamState) BeamRole(signer_, beamState) { + } + + function delCBeam(address cBeam) external onlySigner { + beamState.delCBeam(cBeam); + } + + function addCBeamForRBeam(address rBeam, address cBeam) external onlySigner { + beamState.addCBeamForRBeam(rBeam, cBeam); + } + + function delCBeamToRBeam(address rBeam, address cBeam) external onlySigner { + beamState.delCBeamForRBeam(rBeam, cBeam); + } + + function delInitRateLimits(bytes32 key, address rBeam) external onlySigner { + beamState.delInitRateLimits(key, rBeam); + } + + function delInitControllerActions(bytes32 key, address rBeam) external onlySigner { + beamState.delInitControllerActions(key, rBeam); + } +} diff --git a/src/Option_4/ATWLState.sol b/src/Option_4/BeamState.sol similarity index 71% rename from src/Option_4/ATWLState.sol rename to src/Option_4/BeamState.sol index 32d2f5b..0db35f5 100644 --- a/src/Option_4/ATWLState.sol +++ b/src/Option_4/BeamState.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -contract ATWLState { +contract BeamState { // --- Storage variables --- mapping(address usr => uint256 allowed) public wards; - mapping(address pau => mapping(address usr => uint256 allowed)) public govOps; + mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => + mapping(address rBeam => mapping(address cBeam => uint256 allowed)) public cBeamsForRBeams; // allowed == 0 => false, allowed == 1 => true mapping(bytes32 key => mapping(address pau => DefaultRateLimits limit)) public initRateLimits; // pau == address(0) every pau allowed mapping(bytes32 key => mapping(address pau => bool allowed)) public initControllerActions; // pau == address(0) every pau allowed @@ -19,17 +20,19 @@ contract ATWLState { event Rely(address indexed usr); event Deny(address indexed usr); - event AddGovOps(address indexed pau, address indexed usr); - event DelGovOps(address indexed pau, address indexed usr); - event AddInitRateLimits(bytes32 indexed key, address indexed pau, uint256 maxAmount, uint256 slope); - event DelInitRateLimits(bytes32 indexed key, address indexed pau); - event AddInitControllerActions(bytes32 indexed key, address indexed pau); - event DelInitControllerActions(bytes32 indexed key, address indexed pau); + event AddCBeam(address indexed cBeam); + event DelCBeam(address indexed cBeam); + event AddCBeamForRBeam(address indexed rBeam, address indexed cBeam); + event DelCBeamForRBeam(address indexed rBeam, address indexed cBeam); + event AddInitRateLimits(bytes32 indexed key, address indexed rBeam, uint256 maxAmount, uint256 slope); + event DelInitRateLimits(bytes32 indexed key, address indexed rBeam); + event AddInitControllerActions(bytes32 indexed key, address indexed rBeam); + event DelInitControllerActions(bytes32 indexed key, address indexed rBeam); // --- Modifiers --- modifier auth() { - require(wards[msg.sender] == 1, "Configurator/not-authorized"); + require(wards[msg.sender] == 1, "BeamState/not-authorized"); _; } @@ -69,14 +72,25 @@ contract ATWLState { // TODO: for now roles system is outsourced to an external contract for the following functions: - function addGovOps(address pau, address usr) external auth { - govOps[pau][usr] = 1; - emit AddGovOps(pau, usr); + function addCBeam(address cBeam) external auth { + cBeams[cBeam] = 1; + emit AddCBeam(cBeam); } - function delGovOps(address pau, address usr) external auth { - govOps[pau][usr] = 0; - emit DelGovOps(pau, usr); + function delCBeam(address cBeam) external auth { + cBeams[cBeam] = 0; + emit DelCBeam(cBeam); + } + + function addCBeamForRBeam(address rBeam, address cBeam) external auth { + require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); + cBeamsForRBeams[rBeam][cBeam] = 1; + emit AddCBeamForRBeam(rBeam, cBeam); + } + + function delCBeamForRBeam(address rBeam, address cBeam) external auth { + cBeamsForRBeams[rBeam][cBeam] = 0; + emit DelCBeamForRBeam(rBeam, cBeam); } function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external auth { diff --git a/src/Option_4/Configurator.sol b/src/Option_4/Configurator.sol index 45b84d0..6cbbb11 100644 --- a/src/Option_4/Configurator.sol +++ b/src/Option_4/Configurator.sol @@ -15,8 +15,8 @@ interface RateLimitsLike { function setUnlimitedRateLimitData(bytes32) external; } -interface ATWLStateLike { - function govOps(address, address) external view returns (uint256); +interface BeamStateLike { + function cBeamsForRBeams(address, address) external view returns (uint256); function getInitRateLimits(bytes32, address) external view returns (uint256, uint256); function isControllerActionEnabled(bytes32, address) external view returns (bool); } @@ -27,13 +27,13 @@ contract Configurator { mapping(address usr => uint256 allowed) public wards; mapping(address usr => uint256 allowed) public bud; - mapping(address pau => mapping(bytes32 key => uint256 timestamp)) public zzz; - uint256 public hop; - uint256 public maxChange; + mapping(address rBeam => mapping(bytes32 key => uint256 timestamp)) public zzz; + mapping(address rBeam => uint256 value) public hop; // rBeam == address(0) => general backup configuration + mapping(address rBeam => uint256 value) public maxChange; // rBeam == address(0) => general backup configuration // --- Immutables --- - ATWLStateLike public immutable atwlState; + BeamStateLike public immutable beamState; // --- Constants --- @@ -45,8 +45,9 @@ contract Configurator { event Deny(address indexed usr); event Kiss(address indexed usr); event Diss(address indexed usr); - event File(bytes32 indexed what, uint256 data); - event SetRateLimit(address indexed pau, bytes32 indexed key, uint256 maxAmount, uint256 slope); + event SetHop(address indexed rBeam, uint256 value); + event SetMaxChange(address indexed rBeam, uint256 value); + event SetRateLimit(address indexed rBeam, bytes32 indexed key, uint256 maxAmount, uint256 slope); // --- Modifiers --- @@ -55,16 +56,16 @@ contract Configurator { _; } - modifier govOps(address pau) { - require(atwlState.govOps(pau, msg.sender) == 1, "Configurator/not-authorized-govOps"); + modifier cBeamsForRBeams(address rBeam) { + require(beamState.cBeamsForRBeams(rBeam, msg.sender) == 1, "Configurator/not-authorized-cBeam"); _; } // --- Constructor --- - constructor(address atwlState_) { - require(atwlState_ != address(0), "Configurator/null-atwlState"); - atwlState = ATWLStateLike(atwlState_); + constructor(address beamState_) { + require(beamState_ != address(0), "Configurator/null-beamState"); + beamState = BeamStateLike(beamState_); wards[msg.sender] = 1; emit Rely(msg.sender); } @@ -97,44 +98,45 @@ contract Configurator { emit Diss(usr); } - function file(bytes32 what, uint256 data) external auth { - if (what == "hop") { - hop = data; - } else if (what == "maxChange") { - require(data >= WAD, "Configurator/maxChange-below-1x"); - maxChange = data; - } else { - revert("Configurator/file-unrecognized-param"); - } - emit File(what, data); + function setHop(address rBeam, uint256 value) external auth { + hop[rBeam] = value; + emit SetHop(rBeam, value); + } + + function setMaxChange(address rBeam, uint256 value) external auth { + require(value >= WAD, "Configurator/maxChange-below-1x"); + maxChange[rBeam] = value; + emit SetMaxChange(rBeam, value); } - // GovOps functions + // cBeams functions - function setRateLimit(address pau, bytes32 key, uint256 maxAmount, uint256 slope) external govOps(pau) { - (uint256 defMaxAmount, uint256 defSlope) = atwlState.getInitRateLimits(key, pau); + function setRateLimit(address rBeam, bytes32 key, uint256 maxAmount, uint256 slope) external cBeamsForRBeams(rBeam) { + (uint256 defMaxAmount, uint256 defSlope) = beamState.getInitRateLimits(key, rBeam); if (defMaxAmount == type(uint256).max && defSlope == 0) { - RateLimitsLike(pau).setUnlimitedRateLimitData(key); - emit SetRateLimit(pau, key, type(uint256).max, 0); + RateLimitsLike(rBeam).setUnlimitedRateLimitData(key); + emit SetRateLimit(rBeam, key, type(uint256).max, 0); } else { - RateLimitsLike.RateLimitData memory current = RateLimitsLike(pau).getRateLimitData(key); + RateLimitsLike.RateLimitData memory current = RateLimitsLike(rBeam).getRateLimitData(key); bool safe = maxAmount <= defMaxAmount && slope <= defSlope || maxAmount <= current.maxAmount && slope <= current.slope; - require(safe || block.timestamp >= zzz[pau][key] + hop, "Configurator/increment-too-soon"); - require(safe || maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD - require(safe || slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); + uint256 hop_ = hop[rBeam]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; + uint256 maxChange_ = maxChange[rBeam]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; + require(safe || block.timestamp >= zzz[rBeam][key] + hop_, "Configurator/increment-too-soon"); + require(safe || maxAmount <= current.maxAmount * maxChange_ / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD + require(safe || slope <= current.slope * maxChange_ / WAD, "Configurator/maxChange-slope"); if (maxAmount >= current.maxAmount || slope >= current.slope) { - zzz[pau][key] = block.timestamp; + zzz[rBeam][key] = block.timestamp; } - uint256 lastAmount = RateLimitsLike(pau).getCurrentRateLimit(key); - RateLimitsLike(pau).setRateLimitData(key, maxAmount, slope, _min(maxAmount, lastAmount), block.timestamp); - emit SetRateLimit(pau, key, maxAmount, slope); + uint256 lastAmount = RateLimitsLike(rBeam).getCurrentRateLimit(key); + RateLimitsLike(rBeam).setRateLimitData(key, maxAmount, slope, _min(maxAmount, lastAmount), block.timestamp); + emit SetRateLimit(rBeam, key, maxAmount, slope); } } - function callControllerAction(address pau, bytes calldata data) external govOps(pau) returns (bytes memory ret) { - require(atwlState.isControllerActionEnabled(keccak256(data), pau), "Configurator/not-valid-data"); + function callControllerAction(address rBeam, bytes calldata data) external cBeamsForRBeams(rBeam) returns (bytes memory ret) { + require(beamState.isControllerActionEnabled(keccak256(data), rBeam), "Configurator/not-valid-data"); bool ok; - (ok, ret) = pau.call(data); + (ok, ret) = rBeam.call(data); require(ok, "Configurator/call-failed"); } } From 7f2a7501950ed22ad52b05447ef091f309e5e4c5 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 6 Jan 2026 13:43:49 -0300 Subject: [PATCH 013/150] Remove old options --- src/{Option_4 => }/BeamRole.sol | 0 src/{Option_4 => }/BeamState.sol | 0 src/{Option_4 => }/Configurator.sol | 0 src/Option_1/ATWLState.sol | 104 ---------------- src/Option_1/Configurator.sol | 136 --------------------- src/Option_2/Configurator.sol | 178 ---------------------------- src/Option_3/Configurator.sol | 142 ---------------------- src/Option_3/Role.sol | 60 ---------- 8 files changed, 620 deletions(-) rename src/{Option_4 => }/BeamRole.sol (100%) rename src/{Option_4 => }/BeamState.sol (100%) rename src/{Option_4 => }/Configurator.sol (100%) delete mode 100644 src/Option_1/ATWLState.sol delete mode 100644 src/Option_1/Configurator.sol delete mode 100644 src/Option_2/Configurator.sol delete mode 100644 src/Option_3/Configurator.sol delete mode 100644 src/Option_3/Role.sol diff --git a/src/Option_4/BeamRole.sol b/src/BeamRole.sol similarity index 100% rename from src/Option_4/BeamRole.sol rename to src/BeamRole.sol diff --git a/src/Option_4/BeamState.sol b/src/BeamState.sol similarity index 100% rename from src/Option_4/BeamState.sol rename to src/BeamState.sol diff --git a/src/Option_4/Configurator.sol b/src/Configurator.sol similarity index 100% rename from src/Option_4/Configurator.sol rename to src/Configurator.sol diff --git a/src/Option_1/ATWLState.sol b/src/Option_1/ATWLState.sol deleted file mode 100644 index 6978edb..0000000 --- a/src/Option_1/ATWLState.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -contract ATWLState { - - // --- Storage variables --- - - mapping(address usr => uint256 allowed) public wards; - mapping(address usr => uint256 allowed) public bud; - mapping(bytes32 key => DefaultRateLimits) public defaults; - mapping(bytes32 prime => address) public rateLimits; - - struct DefaultRateLimits { - uint256 maxAmount; - uint256 slope; - } - - // --- Events --- - - event Rely(address indexed usr); - event Deny(address indexed usr); - event Kiss(address indexed usr); - event Diss(address indexed usr); - event AddDefault(bytes32 indexed key, uint256 maxAmount, uint256 slope); - event DelDefault(bytes32 indexed key); - event AddPrime(bytes32 indexed prime, address rateLimits_); - event DelPrime(bytes32 indexed prime); - - // --- Modifiers --- - - modifier auth() { - require(wards[msg.sender] == 1, "Configurator/not-authorized"); - _; - } - - modifier toll() { - require(bud[msg.sender] == 1, "Configurator/not-bud"); - _; - } - - // --- Constructor --- - - constructor() { - wards[msg.sender] = 1; - emit Rely(msg.sender); - } - - // --- Admin functions --- - - function rely(address usr) external auth { - wards[usr] = 1; - emit Rely(usr); - } - - function deny(address usr) external auth { - wards[usr] = 0; - emit Deny(usr); - } - - function kiss(address usr) external auth { - bud[usr] = 1; - emit Kiss(usr); - } - - function diss(address usr) external auth { - bud[usr] = 0; - emit Diss(usr); - } - - // Tolled functions - - function addDefault( - bytes32 key, - uint256 maxAmount, - uint256 slope - ) external toll { - defaults[key] = DefaultRateLimits(maxAmount, slope); - - emit AddDefault(key, maxAmount, slope); - } - - function delDefault(bytes32 key) external toll { - delete defaults[key]; - - emit DelDefault(key); - } - - function addPrime( - bytes32 prime, - address rateLimits_ - ) external toll { - rateLimits[prime] = rateLimits_; - - emit AddPrime(prime, rateLimits_); - } - - function delPrime( - bytes32 prime - ) external toll { - rateLimits[prime] = address(0); - - emit DelPrime(prime); - } -} diff --git a/src/Option_1/Configurator.sol b/src/Option_1/Configurator.sol deleted file mode 100644 index 8531ddb..0000000 --- a/src/Option_1/Configurator.sol +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -interface RateLimitsLike { - struct RateLimitData { - uint256 maxAmount; - uint256 slope; - uint256 lastAmount; - uint256 lastUpdated; - } - - function getRateLimitData(bytes32 key) external view returns (RateLimitData memory); - function setRateLimitData(bytes32 key, uint256 maxAmount, uint256 slope) external; -} - -interface ATWLStateLike { - function defaults(bytes32) external view returns (uint256, uint256); - function rateLimits(bytes32) external view returns (RateLimitsLike); -} - -contract Configurator { - - // --- Storage variables --- - - mapping(address usr => uint256 allowed) public wards; - mapping(address usr => uint256 allowed) public bud; - mapping(bytes32 prime => mapping(bytes32 key => uint256 timestamp)) public zzz; - uint256 public hop; - uint256 public maxChange; - - // --- Immutables --- - - ATWLStateLike public immutable atwlState; - - // --- Constants --- - - uint256 internal constant WAD = 10**18; - - // --- Events --- - - event Rely(address indexed usr); - event Deny(address indexed usr); - event Kiss(address indexed usr); - event Diss(address indexed usr); - event File(bytes32 indexed what, uint256 data); - event SetRateLimit(bytes32 indexed prime, bytes32 indexed key, uint256 maxAmount, uint256 slope, bool isIcrement, uint256 timestamp); - - // --- Modifiers --- - - modifier auth() { - require(wards[msg.sender] == 1, "Configurator/not-authorized"); - _; - } - - modifier toll() { - require(bud[msg.sender] == 1, "Configurator/not-bud"); - _; - } - - // --- Constructor --- - - constructor(address atwlState_) { - require(atwlState_ != address(0), "Configurator/null-atwlState"); - - atwlState = ATWLStateLike(atwlState_); - - wards[msg.sender] = 1; - emit Rely(msg.sender); - } - - // --- Admin functions --- - - function rely(address usr) external auth { - wards[usr] = 1; - emit Rely(usr); - } - - function deny(address usr) external auth { - wards[usr] = 0; - emit Deny(usr); - } - - function kiss(address usr) external auth { - bud[usr] = 1; - emit Kiss(usr); - } - - function diss(address usr) external auth { - bud[usr] = 0; - emit Diss(usr); - } - - function file(bytes32 what, uint256 data) external auth { - if (what == "hop") { - hop = data; - } else if (what == "maxChange") { - require(data >= WAD, "Configurator/maxChange-below-1x"); - maxChange = data; - } else { - revert("Configurator/file-unrecognized-param"); - } - emit File(what, data); - } - - // Tolled functions - - function initRateLimit(bytes32 prime, bytes32 key) external toll { - (uint256 maxAmount, uint256 slope) = atwlState.defaults(key); - require(maxAmount > 0 && slope > 0, "Configurator/wrong-defaults"); - RateLimitsLike.RateLimitData memory current = atwlState.rateLimits(prime).getRateLimitData(key); - require(current.maxAmount == 0 || current.slope == 0, "Configurator/already-init"); - - atwlState.rateLimits(prime).setRateLimitData(key, maxAmount, slope); - - zzz[prime][key] = block.timestamp; - - emit SetRateLimit(prime, key, maxAmount, slope, true, block.timestamp); - } - - function setRateLimit(bytes32 prime, bytes32 key, uint256 maxAmount, uint256 slope) external toll { - RateLimitsLike.RateLimitData memory current = atwlState.rateLimits(prime).getRateLimitData(key); - bool isIncrement = maxAmount > current.maxAmount || slope > current.slope; - require(current.maxAmount > 0 && current.slope > 0, "Configurator/needs-init"); - require(!isIncrement || block.timestamp >= zzz[prime][key] + hop, "Configurator/increment-too-soon"); - require(maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD - require(slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); - - if (isIncrement) { - zzz[prime][key] = block.timestamp; - } - - atwlState.rateLimits(prime).setRateLimitData(key, maxAmount, slope); - - emit SetRateLimit(prime, key, maxAmount, slope, isIncrement, block.timestamp); - } -} diff --git a/src/Option_2/Configurator.sol b/src/Option_2/Configurator.sol deleted file mode 100644 index 5550aee..0000000 --- a/src/Option_2/Configurator.sol +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -interface RateLimitsLike { - struct RateLimitData { - uint256 maxAmount; - uint256 slope; - uint256 lastAmount; - uint256 lastUpdated; - } - - function getRateLimitData(bytes32 key) external view returns (RateLimitData memory); - function setRateLimitData(bytes32 key, uint256 maxAmount, uint256 slope) external; -} - -contract Configurator { - - // --- Storage variables --- - - mapping(address usr => uint256 allowed) public wards; - mapping(address usr => uint256 allowed) public can; - mapping(address usr => uint256 allowed) public bud; - mapping(bytes32 key => DefaultRateLimits) public defaults; - mapping(bytes32 prime => RateLimitsLike) public rateLimits; - mapping(bytes32 prime => mapping(bytes32 key => uint256 timestamp)) public zzz; - uint256 public hop; - uint256 public maxChange; - - struct DefaultRateLimits { - uint256 maxAmount; - uint256 slope; - } - - // --- Constants --- - - uint256 internal constant WAD = 10**18; - - // --- Events --- - - event Rely(address indexed usr); - event Deny(address indexed usr); - event Hope(address indexed usr); - event Nope(address indexed usr); - event Kiss(address indexed usr); - event Diss(address indexed usr); - event File(bytes32 indexed what, uint256 data); - event AddDefault(bytes32 indexed key, uint256 maxAmount, uint256 slope); - event DelDefault(bytes32 indexed key); - event AddPrime(bytes32 indexed prime, address rateLimits_); - event DelPrime(bytes32 indexed prime); - event SetRateLimit(bytes32 indexed prime, bytes32 indexed key, uint256 maxAmount, uint256 slope, bool isIcrement, uint256 timestamp); - - // --- Modifiers --- - - modifier auth() { - require(wards[msg.sender] == 1, "Configurator/not-authorized"); - _; - } - - modifier time() { - require(can[msg.sender] == 1, "Configurator/not-can"); - _; - } - - modifier toll() { - require(bud[msg.sender] == 1, "Configurator/not-bud"); - _; - } - - // --- Constructor --- - - constructor() { - wards[msg.sender] = 1; - emit Rely(msg.sender); - } - - // --- Admin functions --- - - function rely(address usr) external auth { - wards[usr] = 1; - emit Rely(usr); - } - - function deny(address usr) external auth { - wards[usr] = 0; - emit Deny(usr); - } - - function hope(address usr) external auth { - can[usr] = 1; - emit Hope(usr); - } - - function nope(address usr) external auth { - can[usr] = 0; - emit Nope(usr); - } - - function kiss(address usr) external auth { - bud[usr] = 1; - emit Kiss(usr); - } - - function diss(address usr) external auth { - bud[usr] = 0; - emit Diss(usr); - } - - function file(bytes32 what, uint256 data) external auth { - if (what == "hop") { - hop = data; - } else if (what == "maxChange") { - require(data >= WAD, "Configurator/maxChange-below-1x"); - maxChange = data; - } else { - revert("Configurator/file-unrecognized-param"); - } - emit File(what, data); - } - - // Timelocked functions - - function addDefault(bytes32 key, uint256 maxAmount, uint256 slope) external time { - defaults[key] = DefaultRateLimits(maxAmount, slope); - - emit AddDefault(key, maxAmount, slope); - } - - function delDefault(bytes32 key) external time { - delete defaults[key]; - - emit DelDefault(key); - } - - function addPrime(bytes32 prime, address rateLimits_) external time { - rateLimits[prime] = RateLimitsLike(rateLimits_); - - emit AddPrime(prime, rateLimits_); - } - - function delPrime(bytes32 prime) external time { - rateLimits[prime] = RateLimitsLike(address(0)); - - emit DelPrime(prime); - } - - // Tolled functions - - function initRateLimit(bytes32 prime, bytes32 key) external toll { - DefaultRateLimits memory d = defaults[key]; - require(d.maxAmount > 0 && d.slope > 0, "Configurator/wrong-defaults"); - RateLimitsLike.RateLimitData memory current = rateLimits[prime].getRateLimitData(key); - require(current.maxAmount == 0 || current.slope == 0, "Configurator/already-init"); - - rateLimits[prime].setRateLimitData(key, d.maxAmount, d.slope); - - zzz[prime][key] = block.timestamp; - - emit SetRateLimit(prime, key, d.maxAmount, d.slope, true, block.timestamp); - } - - function setRateLimit(bytes32 prime, bytes32 key, uint256 maxAmount, uint256 slope) external toll { - RateLimitsLike.RateLimitData memory current = rateLimits[prime].getRateLimitData(key); - bool isIncrement = maxAmount > current.maxAmount || slope > current.slope; - require(current.maxAmount > 0 && current.slope > 0, "Configurator/needs-init"); - require(!isIncrement || block.timestamp >= zzz[prime][key] + hop, "Configurator/increment-too-soon"); - require(maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD - require(slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); - - if (isIncrement) { - zzz[prime][key] = block.timestamp; - } - - rateLimits[prime].setRateLimitData(key, maxAmount, slope); - - emit SetRateLimit(prime, key, maxAmount, slope, isIncrement, block.timestamp); - } -} diff --git a/src/Option_3/Configurator.sol b/src/Option_3/Configurator.sol deleted file mode 100644 index d852945..0000000 --- a/src/Option_3/Configurator.sol +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -interface RateLimitsLike { - struct RateLimitData { - uint256 maxAmount; - uint256 slope; - uint256 lastAmount; - uint256 lastUpdated; - } - - function getRateLimitData(bytes32 key) external view returns (RateLimitData memory); - function setRateLimitData(bytes32 key, uint256 maxAmount, uint256 slope) external; -} - -contract Configurator { - - // --- Storage variables --- - - mapping(address usr => uint256 allowed) public wards; - mapping(bytes32 key => DefaultRateLimits) public defaults; - mapping(bytes32 prime => RateLimitsLike) public rateLimits; - mapping(bytes32 prime => mapping(bytes32 key => uint256 timestamp)) public zzz; - uint256 public hop; - uint256 public maxChange; - - struct DefaultRateLimits { - uint256 maxAmount; - uint256 slope; - } - - // --- Constants --- - - uint256 internal constant WAD = 10**18; - - // --- Events --- - - event Rely(address indexed usr); - event Deny(address indexed usr); - event Hope(address indexed usr); - event Nope(address indexed usr); - event Kiss(address indexed usr); - event Diss(address indexed usr); - event File(bytes32 indexed what, uint256 data); - event AddDefault(bytes32 indexed key, uint256 maxAmount, uint256 slope); - event DelDefault(bytes32 indexed key); - event AddPrime(bytes32 indexed prime, address rateLimits_); - event DelPrime(bytes32 indexed prime); - event SetRateLimit(bytes32 indexed prime, bytes32 indexed key, uint256 maxAmount, uint256 slope, bool isIcrement, uint256 timestamp); - - // --- Modifiers --- - - modifier auth() { - require(wards[msg.sender] == 1, "Configurator/not-authorized"); - _; - } - - // --- Constructor --- - - constructor() { - wards[msg.sender] = 1; - emit Rely(msg.sender); - } - - // --- Admin functions --- - - function rely(address usr) external auth { - wards[usr] = 1; - emit Rely(usr); - } - - function deny(address usr) external auth { - wards[usr] = 0; - emit Deny(usr); - } - - function file(bytes32 what, uint256 data) external auth { - if (what == "hop") { - hop = data; - } else if (what == "maxChange") { - require(data >= WAD, "Configurator/maxChange-below-1x"); - maxChange = data; - } else { - revert("Configurator/file-unrecognized-param"); - } - emit File(what, data); - } - - function addDefault(bytes32 key, uint256 maxAmount, uint256 slope) external auth { - defaults[key] = DefaultRateLimits(maxAmount, slope); - - emit AddDefault(key, maxAmount, slope); - } - - function delDefault(bytes32 key) external auth { - delete defaults[key]; - - emit DelDefault(key); - } - - function addPrime(bytes32 prime, address rateLimits_) external auth { - rateLimits[prime] = RateLimitsLike(rateLimits_); - - emit AddPrime(prime, rateLimits_); - } - - function delPrime(bytes32 prime) external auth { - rateLimits[prime] = RateLimitsLike(address(0)); - - emit DelPrime(prime); - } - - function initRateLimit(bytes32 prime, bytes32 key) external auth { - DefaultRateLimits memory d = defaults[key]; - require(d.maxAmount > 0 && d.slope > 0, "Configurator/wrong-defaults"); - RateLimitsLike.RateLimitData memory current = rateLimits[prime].getRateLimitData(key); - require(current.maxAmount == 0 || current.slope == 0, "Configurator/already-init"); - - rateLimits[prime].setRateLimitData(key, d.maxAmount, d.slope); - - zzz[prime][key] = block.timestamp; - - emit SetRateLimit(prime, key, d.maxAmount, d.slope, true, block.timestamp); - } - - function setRateLimit(bytes32 prime, bytes32 key, uint256 maxAmount, uint256 slope) external auth { - RateLimitsLike.RateLimitData memory current = rateLimits[prime].getRateLimitData(key); - bool isIncrement = maxAmount > current.maxAmount || slope > current.slope; - require(current.maxAmount > 0 && current.slope > 0, "Configurator/needs-init"); - require(!isIncrement || block.timestamp >= zzz[prime][key] + hop, "Configurator/increment-too-soon"); - require(maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD - require(slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); - - if (isIncrement) { - zzz[prime][key] = block.timestamp; - } - - rateLimits[prime].setRateLimitData(key, maxAmount, slope); - - emit SetRateLimit(prime, key, maxAmount, slope, isIncrement, block.timestamp); - } -} diff --git a/src/Option_3/Role.sol b/src/Option_3/Role.sol deleted file mode 100644 index a91f21d..0000000 --- a/src/Option_3/Role.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -interface ConfiguratorLike { - function addDefault(bytes32, uint256, uint256) external; - function delDefault(bytes32) external; - function addPrime(bytes32, address) external; - function delPrime(bytes32) external; - function initRateLimit(bytes32, bytes32) external; - function setRateLimit(bytes32, bytes32, uint256, uint256) external; -} - -abstract contract Role { - address immutable public owner; - ConfiguratorLike immutable public configurator; - - modifier onlyOwner() { - require(msg.sender == owner, "Role/not-owner"); - _; - } - - constructor(address owner_, address configurator_) { - owner = owner_; - configurator = ConfiguratorLike(configurator_); - } -} - -contract TimeLocked is Role { - constructor(address owner_, address configurator_) Role(owner_, configurator_) { - } - - function addDefault(bytes32 key, uint256 maxAmount, uint256 slope) external onlyOwner { - configurator.addDefault(key, maxAmount, slope); - } - - function delDefault(bytes32 key) external onlyOwner { - configurator.delDefault(key); - } - - function addPrime(bytes32 prime, address rateLimits) external onlyOwner { - configurator.addPrime(prime, rateLimits); - } - - function delPrime(bytes32 prime) external onlyOwner { - configurator.delPrime(prime); - } -} - -contract GovOps is Role { - constructor(address owner_, address configurator_) Role(owner_, configurator_) { - } - - function initRateLimit(bytes32 prime, bytes32 key) external onlyOwner { - configurator.initRateLimit(prime, key); - } - - function setRateLimit(bytes32 prime, bytes32 key, uint256 maxAmount, uint256 slope) external onlyOwner { - configurator.setRateLimit(prime, key, maxAmount, slope); - } -} From 8b6278c1e0d4a883c7a13bcceb70d86e7c3d7c4e Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 6 Jan 2026 14:24:24 -0300 Subject: [PATCH 014/150] More renaming --- src/BeamState.sol | 52 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 0db35f5..a6bf59e 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -5,11 +5,11 @@ contract BeamState { // --- Storage variables --- - mapping(address usr => uint256 allowed) public wards; - mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => - mapping(address rBeam => mapping(address cBeam => uint256 allowed)) public cBeamsForRBeams; // allowed == 0 => false, allowed == 1 => true - mapping(bytes32 key => mapping(address pau => DefaultRateLimits limit)) public initRateLimits; // pau == address(0) every pau allowed - mapping(bytes32 key => mapping(address pau => bool allowed)) public initControllerActions; // pau == address(0) every pau allowed + mapping(address usr => uint256 allowed) public wards; + mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => true + mapping(address rBeam => mapping(address cBeam => uint256 allowed)) public cBeamsForRBeams; // allowed == 0 => false, allowed == 1 => true + mapping(bytes32 key => mapping(address rBeam => DefaultRateLimits limit)) public initRateLimits; // rBeam == address(0) every rBeam allowed + mapping(bytes32 key => mapping(address rBeam => bool allowed)) public initControllerActions; // rBeam == address(0) every rBeam allowed struct DefaultRateLimits { uint256 maxAmount; @@ -45,17 +45,17 @@ contract BeamState { // --- External getters --- - function getInitRateLimits(bytes32 key, address pau) external view returns (DefaultRateLimits memory defaultRateLimits) { - defaultRateLimits = initRateLimits[key][pau]; + function getInitRateLimits(bytes32 key, address rBeam) external view returns (DefaultRateLimits memory defaultRateLimits) { + defaultRateLimits = initRateLimits[key][rBeam]; if (defaultRateLimits.maxAmount == 0 || defaultRateLimits.slope == 0) { - // If not set for specific pau, check in general + // If not set for specific rBeam, check in general defaultRateLimits = initRateLimits[key][address(0)]; } } - function isControllerActionEnabled(bytes32 key, address pau) external view returns (bool ok) { - ok = initControllerActions[key][address(0)] || // address(0) enabled for every pau - initControllerActions[key][pau]; + function isControllerActionEnabled(bytes32 key, address rBeam) external view returns (bool ok) { + ok = initControllerActions[key][address(0)] || // address(0) enabled for every rBeam + initControllerActions[key][rBeam]; } // --- Admin functions --- @@ -93,31 +93,31 @@ contract BeamState { emit DelCBeamForRBeam(rBeam, cBeam); } - function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external auth { - initRateLimits[key][pau] = DefaultRateLimits(maxAmount, slope); - emit AddInitRateLimits(key, pau, maxAmount, slope); + function addInitRateLimits(bytes32 key, address rBeam, uint256 maxAmount, uint256 slope) external auth { + initRateLimits[key][rBeam] = DefaultRateLimits(maxAmount, slope); + emit AddInitRateLimits(key, rBeam, maxAmount, slope); } - function delInitRateLimits(bytes32 key, address pau) external auth { - delete initRateLimits[key][pau]; - emit DelInitRateLimits(key, pau); + function delInitRateLimits(bytes32 key, address rBeam) external auth { + delete initRateLimits[key][rBeam]; + emit DelInitRateLimits(key, rBeam); } - function addInitControllerActions(bytes calldata data, address pau) external auth returns (bytes32 key) { + function addInitControllerActions(bytes calldata data, address rBeam) external auth returns (bytes32 key) { key = keccak256(data); - initControllerActions[key][pau] = true; - emit AddInitControllerActions(key, pau); + initControllerActions[key][rBeam] = true; + emit AddInitControllerActions(key, rBeam); } - function addInitControllerActions(bytes32 key, address pau) external auth { + function addInitControllerActions(bytes32 key, address rBeam) external auth { // TODO: We will have to remove this function if finally having to save or log the raw data for enumeration purposes - initControllerActions[key][pau] = true; - emit AddInitControllerActions(key, pau); + initControllerActions[key][rBeam] = true; + emit AddInitControllerActions(key, rBeam); } - function delInitControllerActions(bytes32 key, address pau) external auth { - delete initControllerActions[key][pau]; - emit DelInitControllerActions(key, pau); + function delInitControllerActions(bytes32 key, address rBeam) external auth { + delete initControllerActions[key][rBeam]; + emit DelInitControllerActions(key, rBeam); } } From c3615fa3d9e950e7053f68accb86103d2ed3f56f Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 6 Jan 2026 14:50:35 -0300 Subject: [PATCH 015/150] Move hop and maxChange to state --- src/BeamState.sol | 27 +++++++++++++++++++++++++++ src/Configurator.sol | 26 ++++++-------------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index a6bf59e..b81f606 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -10,16 +10,24 @@ contract BeamState { mapping(address rBeam => mapping(address cBeam => uint256 allowed)) public cBeamsForRBeams; // allowed == 0 => false, allowed == 1 => true mapping(bytes32 key => mapping(address rBeam => DefaultRateLimits limit)) public initRateLimits; // rBeam == address(0) every rBeam allowed mapping(bytes32 key => mapping(address rBeam => bool allowed)) public initControllerActions; // rBeam == address(0) every rBeam allowed + mapping(address rBeam => uint256 value) public hop; // rBeam == address(0) => general backup configuration + mapping(address rBeam => uint256 value) public maxChange; // rBeam == address(0) => general backup configuration struct DefaultRateLimits { uint256 maxAmount; uint256 slope; } + // --- Constants --- + + uint256 internal constant WAD = 10**18; + // --- Events --- event Rely(address indexed usr); event Deny(address indexed usr); + event SetHop(address indexed rBeam, uint256 value); + event SetMaxChange(address indexed rBeam, uint256 value); event AddCBeam(address indexed cBeam); event DelCBeam(address indexed cBeam); event AddCBeamForRBeam(address indexed rBeam, address indexed cBeam); @@ -45,6 +53,14 @@ contract BeamState { // --- External getters --- + function getHop(address rBeam) external view returns (uint256 hop_) { + hop_ = hop[rBeam]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; + } + + function getMaxChange(address rBeam) external view returns (uint256 maxChange_) { + maxChange_ = maxChange[rBeam]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; + } + function getInitRateLimits(bytes32 key, address rBeam) external view returns (DefaultRateLimits memory defaultRateLimits) { defaultRateLimits = initRateLimits[key][rBeam]; if (defaultRateLimits.maxAmount == 0 || defaultRateLimits.slope == 0) { @@ -72,6 +88,17 @@ contract BeamState { // TODO: for now roles system is outsourced to an external contract for the following functions: + function setHop(address rBeam, uint256 value) external auth { + hop[rBeam] = value; + emit SetHop(rBeam, value); + } + + function setMaxChange(address rBeam, uint256 value) external auth { + require(value >= WAD, "Configurator/maxChange-below-1x"); + maxChange[rBeam] = value; + emit SetMaxChange(rBeam, value); + } + function addCBeam(address cBeam) external auth { cBeams[cBeam] = 1; emit AddCBeam(cBeam); diff --git a/src/Configurator.sol b/src/Configurator.sol index 6cbbb11..840ee86 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -16,6 +16,8 @@ interface RateLimitsLike { } interface BeamStateLike { + function getHop(address) external view returns (uint256); + function getMaxChange(address) external view returns (uint256); function cBeamsForRBeams(address, address) external view returns (uint256); function getInitRateLimits(bytes32, address) external view returns (uint256, uint256); function isControllerActionEnabled(bytes32, address) external view returns (bool); @@ -28,8 +30,6 @@ contract Configurator { mapping(address usr => uint256 allowed) public wards; mapping(address usr => uint256 allowed) public bud; mapping(address rBeam => mapping(bytes32 key => uint256 timestamp)) public zzz; - mapping(address rBeam => uint256 value) public hop; // rBeam == address(0) => general backup configuration - mapping(address rBeam => uint256 value) public maxChange; // rBeam == address(0) => general backup configuration // --- Immutables --- @@ -45,8 +45,6 @@ contract Configurator { event Deny(address indexed usr); event Kiss(address indexed usr); event Diss(address indexed usr); - event SetHop(address indexed rBeam, uint256 value); - event SetMaxChange(address indexed rBeam, uint256 value); event SetRateLimit(address indexed rBeam, bytes32 indexed key, uint256 maxAmount, uint256 slope); // --- Modifiers --- @@ -98,17 +96,6 @@ contract Configurator { emit Diss(usr); } - function setHop(address rBeam, uint256 value) external auth { - hop[rBeam] = value; - emit SetHop(rBeam, value); - } - - function setMaxChange(address rBeam, uint256 value) external auth { - require(value >= WAD, "Configurator/maxChange-below-1x"); - maxChange[rBeam] = value; - emit SetMaxChange(rBeam, value); - } - // cBeams functions function setRateLimit(address rBeam, bytes32 key, uint256 maxAmount, uint256 slope) external cBeamsForRBeams(rBeam) { @@ -119,11 +106,10 @@ contract Configurator { } else { RateLimitsLike.RateLimitData memory current = RateLimitsLike(rBeam).getRateLimitData(key); bool safe = maxAmount <= defMaxAmount && slope <= defSlope || maxAmount <= current.maxAmount && slope <= current.slope; - uint256 hop_ = hop[rBeam]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; - uint256 maxChange_ = maxChange[rBeam]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; - require(safe || block.timestamp >= zzz[rBeam][key] + hop_, "Configurator/increment-too-soon"); - require(safe || maxAmount <= current.maxAmount * maxChange_ / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD - require(safe || slope <= current.slope * maxChange_ / WAD, "Configurator/maxChange-slope"); + uint256 maxChange = beamState.getMaxChange(rBeam); + require(safe || block.timestamp >= zzz[rBeam][key] + beamState.getHop(rBeam), "Configurator/increment-too-soon"); + require(safe || maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD + require(safe || slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); if (maxAmount >= current.maxAmount || slope >= current.slope) { zzz[rBeam][key] = block.timestamp; } From 861e520b419f6d78cb3c767bd9254c9e9dbe899c Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 6 Jan 2026 15:11:09 -0300 Subject: [PATCH 016/150] Clean configurator --- src/BeamRole.sol | 28 +++++++++++++++++++--------- src/Configurator.sol | 36 ------------------------------------ 2 files changed, 19 insertions(+), 45 deletions(-) diff --git a/src/BeamRole.sol b/src/BeamRole.sol index c1e64a4..126d96b 100644 --- a/src/BeamRole.sol +++ b/src/BeamRole.sol @@ -2,15 +2,17 @@ pragma solidity ^0.8.21; interface BeamState { - function addCBeam(address cBeam) external; - function delCBeam(address cBeam) external; - function addCBeamForRBeam(address rBeam, address cBeam) external; - function delCBeamForRBeam(address rBeam, address cBeam) external; - function addInitRateLimits(bytes32 key, address rBeam, uint256 maxAmount, uint256 slope) external; - function addInitControllerActions(bytes calldata data, address rBeam) external returns (bytes32 key); - function delInitRateLimits(bytes32 key, address rBeam) external; - function addInitControllerActions(bytes32 key, address rBeam) external; - function delInitControllerActions(bytes32 key, address rBeam) external; + function setHop(address, uint256) external; + function setMaxChange(address, uint256) external; + function addCBeam(address) external; + function delCBeam(address) external; + function addCBeamForRBeam(address, address) external; + function delCBeamForRBeam(address, address) external; + function addInitRateLimits(bytes32, address, uint256, uint256) external; + function addInitControllerActions(bytes calldata, address) external returns (bytes32); + function delInitRateLimits(bytes32, address) external; + function addInitControllerActions(bytes32, address) external; + function delInitControllerActions(bytes32, address) external; } abstract contract BeamRole { @@ -32,6 +34,14 @@ contract BeamRoleTimeLock is BeamRole { constructor(address signer_, address beamState_) BeamRole(signer_, beamState_) { } + function setHop(address rBeam, uint256 value) external onlySigner { + beamState.setHop(rBeam, value); + } + + function setMaxChange(address rBeam, uint256 value) external onlySigner { + beamState.setHop(rBeam, value); + } + function addCBeam(address cBeam) external onlySigner { beamState.addCBeam(cBeam); } diff --git a/src/Configurator.sol b/src/Configurator.sol index 840ee86..0d3556f 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -27,8 +27,6 @@ contract Configurator { // --- Storage variables --- - mapping(address usr => uint256 allowed) public wards; - mapping(address usr => uint256 allowed) public bud; mapping(address rBeam => mapping(bytes32 key => uint256 timestamp)) public zzz; // --- Immutables --- @@ -41,19 +39,10 @@ contract Configurator { // --- Events --- - event Rely(address indexed usr); - event Deny(address indexed usr); - event Kiss(address indexed usr); - event Diss(address indexed usr); event SetRateLimit(address indexed rBeam, bytes32 indexed key, uint256 maxAmount, uint256 slope); // --- Modifiers --- - modifier auth() { - require(wards[msg.sender] == 1, "Configurator/not-authorized"); - _; - } - modifier cBeamsForRBeams(address rBeam) { require(beamState.cBeamsForRBeams(rBeam, msg.sender) == 1, "Configurator/not-authorized-cBeam"); _; @@ -62,10 +51,7 @@ contract Configurator { // --- Constructor --- constructor(address beamState_) { - require(beamState_ != address(0), "Configurator/null-beamState"); beamState = BeamStateLike(beamState_); - wards[msg.sender] = 1; - emit Rely(msg.sender); } // --- Internal functions --- @@ -74,28 +60,6 @@ contract Configurator { z = x < y ? x : y; } - // --- Admin functions --- - - function rely(address usr) external auth { - wards[usr] = 1; - emit Rely(usr); - } - - function deny(address usr) external auth { - wards[usr] = 0; - emit Deny(usr); - } - - function kiss(address usr) external auth { - bud[usr] = 1; - emit Kiss(usr); - } - - function diss(address usr) external auth { - bud[usr] = 0; - emit Diss(usr); - } - // cBeams functions function setRateLimit(address rBeam, bytes32 key, uint256 maxAmount, uint256 slope) external cBeamsForRBeams(rBeam) { From 9fa545371fa121d76ce6f0dca670b3ed0887df30 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 7 Jan 2026 09:19:09 -0300 Subject: [PATCH 017/150] Rolling back some naming --- src/BeamRole.sol | 40 ++++++++--------- src/BeamState.sol | 104 +++++++++++++++++++++---------------------- src/Configurator.sol | 38 ++++++++-------- 3 files changed, 91 insertions(+), 91 deletions(-) diff --git a/src/BeamRole.sol b/src/BeamRole.sol index 126d96b..0f95949 100644 --- a/src/BeamRole.sol +++ b/src/BeamRole.sol @@ -6,8 +6,8 @@ interface BeamState { function setMaxChange(address, uint256) external; function addCBeam(address) external; function delCBeam(address) external; - function addCBeamForRBeam(address, address) external; - function delCBeamForRBeam(address, address) external; + function addCBeamForPau(address, address) external; + function delCBeamForPau(address, address) external; function addInitRateLimits(bytes32, address, uint256, uint256) external; function addInitControllerActions(bytes calldata, address) external returns (bytes32); function delInitRateLimits(bytes32, address) external; @@ -34,28 +34,28 @@ contract BeamRoleTimeLock is BeamRole { constructor(address signer_, address beamState_) BeamRole(signer_, beamState_) { } - function setHop(address rBeam, uint256 value) external onlySigner { - beamState.setHop(rBeam, value); + function setHop(address pau, uint256 value) external onlySigner { + beamState.setHop(pau, value); } - function setMaxChange(address rBeam, uint256 value) external onlySigner { - beamState.setHop(rBeam, value); + function setMaxChange(address pau, uint256 value) external onlySigner { + beamState.setHop(pau, value); } function addCBeam(address cBeam) external onlySigner { beamState.addCBeam(cBeam); } - function addInitRateLimits(bytes32 key, address rBeam, uint256 maxAmount, uint256 slope) external onlySigner { - beamState.addInitRateLimits(key, rBeam, maxAmount, slope); + function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external onlySigner { + beamState.addInitRateLimits(key, pau, maxAmount, slope); } - function addInitControllerActions(bytes calldata data, address rBeam) external onlySigner returns (bytes32 key) { - key = beamState.addInitControllerActions(data, rBeam); + function addInitControllerActions(bytes calldata data, address pau) external onlySigner returns (bytes32 key) { + key = beamState.addInitControllerActions(data, pau); } - function addInitControllerActions(bytes32 key, address rBeam) external onlySigner { - beamState.addInitControllerActions(key, rBeam); + function addInitControllerActions(bytes32 key, address pau) external onlySigner { + beamState.addInitControllerActions(key, pau); } } @@ -67,19 +67,19 @@ contract BeamRoleDirect is BeamRole { beamState.delCBeam(cBeam); } - function addCBeamForRBeam(address rBeam, address cBeam) external onlySigner { - beamState.addCBeamForRBeam(rBeam, cBeam); + function addCBeamForPau(address pau, address cBeam) external onlySigner { + beamState.addCBeamForPau(pau, cBeam); } - function delCBeamToRBeam(address rBeam, address cBeam) external onlySigner { - beamState.delCBeamForRBeam(rBeam, cBeam); + function delCBeamToPau(address pau, address cBeam) external onlySigner { + beamState.delCBeamForPau(pau, cBeam); } - function delInitRateLimits(bytes32 key, address rBeam) external onlySigner { - beamState.delInitRateLimits(key, rBeam); + function delInitRateLimits(bytes32 key, address pau) external onlySigner { + beamState.delInitRateLimits(key, pau); } - function delInitControllerActions(bytes32 key, address rBeam) external onlySigner { - beamState.delInitControllerActions(key, rBeam); + function delInitControllerActions(bytes32 key, address pau) external onlySigner { + beamState.delInitControllerActions(key, pau); } } diff --git a/src/BeamState.sol b/src/BeamState.sol index b81f606..16a7ace 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -5,13 +5,13 @@ contract BeamState { // --- Storage variables --- - mapping(address usr => uint256 allowed) public wards; - mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => true - mapping(address rBeam => mapping(address cBeam => uint256 allowed)) public cBeamsForRBeams; // allowed == 0 => false, allowed == 1 => true - mapping(bytes32 key => mapping(address rBeam => DefaultRateLimits limit)) public initRateLimits; // rBeam == address(0) every rBeam allowed - mapping(bytes32 key => mapping(address rBeam => bool allowed)) public initControllerActions; // rBeam == address(0) every rBeam allowed - mapping(address rBeam => uint256 value) public hop; // rBeam == address(0) => general backup configuration - mapping(address rBeam => uint256 value) public maxChange; // rBeam == address(0) => general backup configuration + mapping(address usr => uint256 allowed) public wards; + mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => true + mapping(address pau => mapping(address cBeam => uint256 allowed)) public pauCBeams; // allowed == 0 => false, allowed == 1 => true + mapping(bytes32 key => mapping(address pau => DefaultRateLimits limit)) public initRateLimits; // pau == address(0) every pau allowed + mapping(bytes32 key => mapping(address pau => bool allowed)) public initControllerActions; // pau == address(0) every pau allowed + mapping(address pau => uint256 value) public hop; // pau == address(0) => general backup configuration + mapping(address pau => uint256 value) public maxChange; // pau == address(0) => general backup configuration struct DefaultRateLimits { uint256 maxAmount; @@ -26,16 +26,16 @@ contract BeamState { event Rely(address indexed usr); event Deny(address indexed usr); - event SetHop(address indexed rBeam, uint256 value); - event SetMaxChange(address indexed rBeam, uint256 value); + event SetHop(address indexed pau, uint256 value); + event SetMaxChange(address indexed pau, uint256 value); event AddCBeam(address indexed cBeam); event DelCBeam(address indexed cBeam); - event AddCBeamForRBeam(address indexed rBeam, address indexed cBeam); - event DelCBeamForRBeam(address indexed rBeam, address indexed cBeam); - event AddInitRateLimits(bytes32 indexed key, address indexed rBeam, uint256 maxAmount, uint256 slope); - event DelInitRateLimits(bytes32 indexed key, address indexed rBeam); - event AddInitControllerActions(bytes32 indexed key, address indexed rBeam); - event DelInitControllerActions(bytes32 indexed key, address indexed rBeam); + event SetCBeamForPau(address indexed pau, address indexed cBeam); + event UnsetCBeamForPau(address indexed pau, address indexed cBeam); + event AddInitRateLimits(bytes32 indexed key, address indexed pau, uint256 maxAmount, uint256 slope); + event DelInitRateLimits(bytes32 indexed key, address indexed pau); + event AddInitControllerActions(bytes32 indexed key, address indexed pau); + event DelInitControllerActions(bytes32 indexed key, address indexed pau); // --- Modifiers --- @@ -53,25 +53,25 @@ contract BeamState { // --- External getters --- - function getHop(address rBeam) external view returns (uint256 hop_) { - hop_ = hop[rBeam]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; + function getHop(address pau) external view returns (uint256 hop_) { + hop_ = hop[pau]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; } - function getMaxChange(address rBeam) external view returns (uint256 maxChange_) { - maxChange_ = maxChange[rBeam]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; + function getMaxChange(address pau) external view returns (uint256 maxChange_) { + maxChange_ = maxChange[pau]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; } - function getInitRateLimits(bytes32 key, address rBeam) external view returns (DefaultRateLimits memory defaultRateLimits) { - defaultRateLimits = initRateLimits[key][rBeam]; + function getInitRateLimits(bytes32 key, address pau) external view returns (DefaultRateLimits memory defaultRateLimits) { + defaultRateLimits = initRateLimits[key][pau]; if (defaultRateLimits.maxAmount == 0 || defaultRateLimits.slope == 0) { - // If not set for specific rBeam, check in general + // If not set for specific pau, check in general defaultRateLimits = initRateLimits[key][address(0)]; } } - function isControllerActionEnabled(bytes32 key, address rBeam) external view returns (bool ok) { - ok = initControllerActions[key][address(0)] || // address(0) enabled for every rBeam - initControllerActions[key][rBeam]; + function isControllerActionEnabled(bytes32 key, address pau) external view returns (bool ok) { + ok = initControllerActions[key][address(0)] || // address(0) enabled for every pau + initControllerActions[key][pau]; } // --- Admin functions --- @@ -88,15 +88,15 @@ contract BeamState { // TODO: for now roles system is outsourced to an external contract for the following functions: - function setHop(address rBeam, uint256 value) external auth { - hop[rBeam] = value; - emit SetHop(rBeam, value); + function setHop(address pau, uint256 value) external auth { + hop[pau] = value; + emit SetHop(pau, value); } - function setMaxChange(address rBeam, uint256 value) external auth { + function setMaxChange(address pau, uint256 value) external auth { require(value >= WAD, "Configurator/maxChange-below-1x"); - maxChange[rBeam] = value; - emit SetMaxChange(rBeam, value); + maxChange[pau] = value; + emit SetMaxChange(pau, value); } function addCBeam(address cBeam) external auth { @@ -109,42 +109,42 @@ contract BeamState { emit DelCBeam(cBeam); } - function addCBeamForRBeam(address rBeam, address cBeam) external auth { + function setCBeamForPau(address pau, address cBeam) external auth { require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); - cBeamsForRBeams[rBeam][cBeam] = 1; - emit AddCBeamForRBeam(rBeam, cBeam); + pauCBeams[pau][cBeam] = 1; + emit SetCBeamForPau(pau, cBeam); } - function delCBeamForRBeam(address rBeam, address cBeam) external auth { - cBeamsForRBeams[rBeam][cBeam] = 0; - emit DelCBeamForRBeam(rBeam, cBeam); + function unsetCBeamForPau(address pau, address cBeam) external auth { + pauCBeams[pau][cBeam] = 0; + emit UnsetCBeamForPau(pau, cBeam); } - function addInitRateLimits(bytes32 key, address rBeam, uint256 maxAmount, uint256 slope) external auth { - initRateLimits[key][rBeam] = DefaultRateLimits(maxAmount, slope); - emit AddInitRateLimits(key, rBeam, maxAmount, slope); + function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external auth { + initRateLimits[key][pau] = DefaultRateLimits(maxAmount, slope); + emit AddInitRateLimits(key, pau, maxAmount, slope); } - function delInitRateLimits(bytes32 key, address rBeam) external auth { - delete initRateLimits[key][rBeam]; - emit DelInitRateLimits(key, rBeam); + function delInitRateLimits(bytes32 key, address pau) external auth { + delete initRateLimits[key][pau]; + emit DelInitRateLimits(key, pau); } - function addInitControllerActions(bytes calldata data, address rBeam) external auth returns (bytes32 key) { + function addInitControllerActions(bytes calldata data, address pau) external auth returns (bytes32 key) { key = keccak256(data); - initControllerActions[key][rBeam] = true; - emit AddInitControllerActions(key, rBeam); + initControllerActions[key][pau] = true; + emit AddInitControllerActions(key, pau); } - function addInitControllerActions(bytes32 key, address rBeam) external auth { + function addInitControllerActions(bytes32 key, address pau) external auth { // TODO: We will have to remove this function if finally having to save or log the raw data for enumeration purposes - initControllerActions[key][rBeam] = true; - emit AddInitControllerActions(key, rBeam); + initControllerActions[key][pau] = true; + emit AddInitControllerActions(key, pau); } - function delInitControllerActions(bytes32 key, address rBeam) external auth { - delete initControllerActions[key][rBeam]; - emit DelInitControllerActions(key, rBeam); + function delInitControllerActions(bytes32 key, address pau) external auth { + delete initControllerActions[key][pau]; + emit DelInitControllerActions(key, pau); } } diff --git a/src/Configurator.sol b/src/Configurator.sol index 0d3556f..e9077ad 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -18,7 +18,7 @@ interface RateLimitsLike { interface BeamStateLike { function getHop(address) external view returns (uint256); function getMaxChange(address) external view returns (uint256); - function cBeamsForRBeams(address, address) external view returns (uint256); + function pauCBeams(address, address) external view returns (uint256); function getInitRateLimits(bytes32, address) external view returns (uint256, uint256); function isControllerActionEnabled(bytes32, address) external view returns (bool); } @@ -27,7 +27,7 @@ contract Configurator { // --- Storage variables --- - mapping(address rBeam => mapping(bytes32 key => uint256 timestamp)) public zzz; + mapping(address pau => mapping(bytes32 key => uint256 timestamp)) public zzz; // --- Immutables --- @@ -39,12 +39,12 @@ contract Configurator { // --- Events --- - event SetRateLimit(address indexed rBeam, bytes32 indexed key, uint256 maxAmount, uint256 slope); + event SetRateLimit(address indexed pau, bytes32 indexed key, uint256 maxAmount, uint256 slope); // --- Modifiers --- - modifier cBeamsForRBeams(address rBeam) { - require(beamState.cBeamsForRBeams(rBeam, msg.sender) == 1, "Configurator/not-authorized-cBeam"); + modifier auth(address pau) { + require(beamState.pauCBeams(pau, msg.sender) == 1, "Configurator/not-authorized-cBeam"); _; } @@ -62,31 +62,31 @@ contract Configurator { // cBeams functions - function setRateLimit(address rBeam, bytes32 key, uint256 maxAmount, uint256 slope) external cBeamsForRBeams(rBeam) { - (uint256 defMaxAmount, uint256 defSlope) = beamState.getInitRateLimits(key, rBeam); + function setRateLimit(address pau, bytes32 key, uint256 maxAmount, uint256 slope) external auth(pau) { + (uint256 defMaxAmount, uint256 defSlope) = beamState.getInitRateLimits(key, pau); if (defMaxAmount == type(uint256).max && defSlope == 0) { - RateLimitsLike(rBeam).setUnlimitedRateLimitData(key); - emit SetRateLimit(rBeam, key, type(uint256).max, 0); + RateLimitsLike(pau).setUnlimitedRateLimitData(key); + emit SetRateLimit(pau, key, type(uint256).max, 0); } else { - RateLimitsLike.RateLimitData memory current = RateLimitsLike(rBeam).getRateLimitData(key); + RateLimitsLike.RateLimitData memory current = RateLimitsLike(pau).getRateLimitData(key); bool safe = maxAmount <= defMaxAmount && slope <= defSlope || maxAmount <= current.maxAmount && slope <= current.slope; - uint256 maxChange = beamState.getMaxChange(rBeam); - require(safe || block.timestamp >= zzz[rBeam][key] + beamState.getHop(rBeam), "Configurator/increment-too-soon"); + uint256 maxChange = beamState.getMaxChange(pau); + require(safe || block.timestamp >= zzz[pau][key] + beamState.getHop(pau), "Configurator/increment-too-soon"); require(safe || maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD require(safe || slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); if (maxAmount >= current.maxAmount || slope >= current.slope) { - zzz[rBeam][key] = block.timestamp; + zzz[pau][key] = block.timestamp; } - uint256 lastAmount = RateLimitsLike(rBeam).getCurrentRateLimit(key); - RateLimitsLike(rBeam).setRateLimitData(key, maxAmount, slope, _min(maxAmount, lastAmount), block.timestamp); - emit SetRateLimit(rBeam, key, maxAmount, slope); + uint256 lastAmount = RateLimitsLike(pau).getCurrentRateLimit(key); + RateLimitsLike(pau).setRateLimitData(key, maxAmount, slope, _min(maxAmount, lastAmount), block.timestamp); + emit SetRateLimit(pau, key, maxAmount, slope); } } - function callControllerAction(address rBeam, bytes calldata data) external cBeamsForRBeams(rBeam) returns (bytes memory ret) { - require(beamState.isControllerActionEnabled(keccak256(data), rBeam), "Configurator/not-valid-data"); + function callControllerAction(address pau, bytes calldata data) external auth(pau) returns (bytes memory ret) { + require(beamState.isControllerActionEnabled(keccak256(data), pau), "Configurator/not-valid-data"); bool ok; - (ok, ret) = rBeam.call(data); + (ok, ret) = pau.call(data); require(ok, "Configurator/call-failed"); } } From ccd18e11ba1b614dcb69b5d97fec0e699ee2f531 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 7 Jan 2026 12:38:09 -0300 Subject: [PATCH 018/150] Roles embedded in State --- src/BeamRole.sol | 85 ----------------------------------------------- src/BeamState.sol | 65 ++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 98 deletions(-) delete mode 100644 src/BeamRole.sol diff --git a/src/BeamRole.sol b/src/BeamRole.sol deleted file mode 100644 index 0f95949..0000000 --- a/src/BeamRole.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -interface BeamState { - function setHop(address, uint256) external; - function setMaxChange(address, uint256) external; - function addCBeam(address) external; - function delCBeam(address) external; - function addCBeamForPau(address, address) external; - function delCBeamForPau(address, address) external; - function addInitRateLimits(bytes32, address, uint256, uint256) external; - function addInitControllerActions(bytes calldata, address) external returns (bytes32); - function delInitRateLimits(bytes32, address) external; - function addInitControllerActions(bytes32, address) external; - function delInitControllerActions(bytes32, address) external; -} - -abstract contract BeamRole { - address immutable public signer; - BeamState immutable public beamState; - - modifier onlySigner() { - require(msg.sender == signer, "BeamRole/not-signer"); - _; - } - - constructor(address signer_, address beamState_) { - signer = signer_; - beamState = BeamState(beamState_); - } -} - -contract BeamRoleTimeLock is BeamRole { - constructor(address signer_, address beamState_) BeamRole(signer_, beamState_) { - } - - function setHop(address pau, uint256 value) external onlySigner { - beamState.setHop(pau, value); - } - - function setMaxChange(address pau, uint256 value) external onlySigner { - beamState.setHop(pau, value); - } - - function addCBeam(address cBeam) external onlySigner { - beamState.addCBeam(cBeam); - } - - function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external onlySigner { - beamState.addInitRateLimits(key, pau, maxAmount, slope); - } - - function addInitControllerActions(bytes calldata data, address pau) external onlySigner returns (bytes32 key) { - key = beamState.addInitControllerActions(data, pau); - } - - function addInitControllerActions(bytes32 key, address pau) external onlySigner { - beamState.addInitControllerActions(key, pau); - } -} - -contract BeamRoleDirect is BeamRole { - constructor(address signer_, address beamState) BeamRole(signer_, beamState) { - } - - function delCBeam(address cBeam) external onlySigner { - beamState.delCBeam(cBeam); - } - - function addCBeamForPau(address pau, address cBeam) external onlySigner { - beamState.addCBeamForPau(pau, cBeam); - } - - function delCBeamToPau(address pau, address cBeam) external onlySigner { - beamState.delCBeamForPau(pau, cBeam); - } - - function delInitRateLimits(bytes32 key, address pau) external onlySigner { - beamState.delInitRateLimits(key, pau); - } - - function delInitControllerActions(bytes32 key, address pau) external onlySigner { - beamState.delInitControllerActions(key, pau); - } -} diff --git a/src/BeamState.sol b/src/BeamState.sol index 16a7ace..5937cbf 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -6,6 +6,8 @@ contract BeamState { // --- Storage variables --- mapping(address usr => uint256 allowed) public wards; + mapping(address usr => bytes32 rolesData) public userRoles; + mapping(bytes4 sig => bytes32 rolesData) public actionsRoles; mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => true mapping(address pau => mapping(address cBeam => uint256 allowed)) public pauCBeams; // allowed == 0 => false, allowed == 1 => true mapping(bytes32 key => mapping(address pau => DefaultRateLimits limit)) public initRateLimits; // pau == address(0) every pau allowed @@ -26,6 +28,8 @@ contract BeamState { event Rely(address indexed usr); event Deny(address indexed usr); + event SetUserRole(address indexed who, uint8 indexed role, bool enabled); + event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); event SetHop(address indexed pau, uint256 value); event SetMaxChange(address indexed pau, uint256 value); event AddCBeam(address indexed cBeam); @@ -44,6 +48,14 @@ contract BeamState { _; } + modifier roleAuth() { + require( + userRoles[msg.sender] & actionsRoles[msg.sig] != bytes32(0) || + wards[msg.sender] == 1, "BeamState/role-not-authorized" + ); + _; + } + // --- Constructor --- constructor() { @@ -53,6 +65,14 @@ contract BeamState { // --- External getters --- + function hasUserRole(address usr, uint8 role) external view returns (bool has) { + has = userRoles[usr] & bytes32(uint256(1) << role) != bytes32(0); + } + + function isActionInRole(bytes4 sig, uint8 role) external view returns (bool has) { + has = actionsRoles[sig] & bytes32(uint256(1) << role) != bytes32(0); + } + function getHop(address pau) external view returns (uint256 hop_) { hop_ = hop[pau]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; } @@ -86,65 +106,84 @@ contract BeamState { emit Deny(usr); } - // TODO: for now roles system is outsourced to an external contract for the following functions: + function setUserRole(address who, uint8 role, bool enabled) public auth { + bytes32 mask = bytes32(uint256(1) << role); + if (enabled) { + userRoles[who] |= mask; + } else { + userRoles[who] &= ~mask; + } + emit SetUserRole(who, role, enabled); + } - function setHop(address pau, uint256 value) external auth { + function setRoleAction(uint8 role, bytes4 sig, bool enabled) external auth { + bytes32 mask = bytes32(uint256(1) << role); + if (enabled) { + actionsRoles[sig] |= mask; + } else { + actionsRoles[sig] &= ~mask; + } + emit SetRoleAction(role, sig, enabled); + } + + // --- Role authed functions --- + + function setHop(address pau, uint256 value) external roleAuth { hop[pau] = value; emit SetHop(pau, value); } - function setMaxChange(address pau, uint256 value) external auth { + function setMaxChange(address pau, uint256 value) external roleAuth { require(value >= WAD, "Configurator/maxChange-below-1x"); maxChange[pau] = value; emit SetMaxChange(pau, value); } - function addCBeam(address cBeam) external auth { + function addCBeam(address cBeam) external roleAuth { cBeams[cBeam] = 1; emit AddCBeam(cBeam); } - function delCBeam(address cBeam) external auth { + function delCBeam(address cBeam) external roleAuth { cBeams[cBeam] = 0; emit DelCBeam(cBeam); } - function setCBeamForPau(address pau, address cBeam) external auth { + function setCBeamForPau(address pau, address cBeam) external roleAuth { require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); pauCBeams[pau][cBeam] = 1; emit SetCBeamForPau(pau, cBeam); } - function unsetCBeamForPau(address pau, address cBeam) external auth { + function unsetCBeamForPau(address pau, address cBeam) external roleAuth { pauCBeams[pau][cBeam] = 0; emit UnsetCBeamForPau(pau, cBeam); } - function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external auth { + function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external roleAuth { initRateLimits[key][pau] = DefaultRateLimits(maxAmount, slope); emit AddInitRateLimits(key, pau, maxAmount, slope); } - function delInitRateLimits(bytes32 key, address pau) external auth { + function delInitRateLimits(bytes32 key, address pau) external roleAuth { delete initRateLimits[key][pau]; emit DelInitRateLimits(key, pau); } - function addInitControllerActions(bytes calldata data, address pau) external auth returns (bytes32 key) { + function addInitControllerActions(bytes calldata data, address pau) external roleAuth returns (bytes32 key) { key = keccak256(data); initControllerActions[key][pau] = true; emit AddInitControllerActions(key, pau); } - function addInitControllerActions(bytes32 key, address pau) external auth { + function addInitControllerActions(bytes32 key, address pau) external roleAuth { // TODO: We will have to remove this function if finally having to save or log the raw data for enumeration purposes initControllerActions[key][pau] = true; emit AddInitControllerActions(key, pau); } - function delInitControllerActions(bytes32 key, address pau) external auth { + function delInitControllerActions(bytes32 key, address pau) external roleAuth { delete initControllerActions[key][pau]; emit DelInitControllerActions(key, pau); } - } From 95f083f2f0b0291122c867d0392f69c5ce57f74c Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:20:36 +0700 Subject: [PATCH 019/150] Minor changes --- src/timelock/ATWLTimeLockedWrapper.sol | 5 +++-- src/timelock/SkyTimelock.sol | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/timelock/ATWLTimeLockedWrapper.sol b/src/timelock/ATWLTimeLockedWrapper.sol index 99a140c..ac6b7ec 100644 --- a/src/timelock/ATWLTimeLockedWrapper.sol +++ b/src/timelock/ATWLTimeLockedWrapper.sol @@ -16,10 +16,11 @@ pragma solidity ^0.8.21; -// Code aligned to: https://github.com/sunbreak1211/pau/blob/a3e4b519c238d32c08c82145f04341144361e943/src/Option_4/Configurator.sol +// PAU code aligned to: https://github.com/sunbreak1211/pau/blob/a3e4b519c238d32c08c82145f04341144361e943/src/Option_4/Configurator.sol +// Mainnet controller aligned to: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol // TODO: Rename `pau` parameter to `almController` and `rateLimiter` - current name is ambiguous (depends on external contract changes) -import {SkyTimelock} from "src/timelock/SkyTimelock.sol"; +import { SkyTimelock } from "src/timelock/SkyTimelock.sol"; interface ATWLRoleTimeLockLike { function addGovOps(address pau, address usr) external; diff --git a/src/timelock/SkyTimelock.sol b/src/timelock/SkyTimelock.sol index acbc192..a73ce92 100644 --- a/src/timelock/SkyTimelock.sol +++ b/src/timelock/SkyTimelock.sol @@ -16,9 +16,9 @@ pragma solidity ^0.8.21; -import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; -import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol"; +import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; +import { EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; contract SkyTimelock is TimelockController, Pausable { using EnumerableSet for EnumerableSet.Bytes32Set; From c73452758bd7d0f6328d43f3d55096ecd3b60980 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 8 Jan 2026 22:24:07 +0700 Subject: [PATCH 020/150] Align to BEAMState --- src/timelock/ATWLTimeLockedWrapper.sol | 134 ++++++++++++++++++++----- 1 file changed, 111 insertions(+), 23 deletions(-) diff --git a/src/timelock/ATWLTimeLockedWrapper.sol b/src/timelock/ATWLTimeLockedWrapper.sol index ac6b7ec..ce4c825 100644 --- a/src/timelock/ATWLTimeLockedWrapper.sol +++ b/src/timelock/ATWLTimeLockedWrapper.sol @@ -19,14 +19,22 @@ pragma solidity ^0.8.21; // PAU code aligned to: https://github.com/sunbreak1211/pau/blob/a3e4b519c238d32c08c82145f04341144361e943/src/Option_4/Configurator.sol // Mainnet controller aligned to: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol // TODO: Rename `pau` parameter to `almController` and `rateLimiter` - current name is ambiguous (depends on external contract changes) +// TODO: Decide if we want to keep all the direct access functions or use a more generic approach import { SkyTimelock } from "src/timelock/SkyTimelock.sol"; -interface ATWLRoleTimeLockLike { - function addGovOps(address pau, address usr) external; +interface BeamStateLike { + function setHop(address pau, uint256 value) external; + function setMaxChange(address pau, uint256 value) external; + function addCBeam(address cBeam) external; + function delCBeam(address cBeam) external; + function setCBeamForPau(address pau, address cBeam) external; + function unsetCBeamForPau(address pau, address cBeam) external; function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external; + function delInitRateLimits(bytes32 key, address pau) external; function addInitControllerActions(bytes calldata data, address pau) external returns (bytes32 key); - // Note: The overloaded function addInitControllerActions(bytes32 key, address pau) from ATWLRole.sol (lines 45-46) is not supported + // Note: The overloaded function addInitControllerActions(bytes32 key, address pau) from BeamState.sol is not supported + function delInitControllerActions(bytes32 key, address pau) external; } interface MainnetControllerLike { @@ -89,13 +97,13 @@ contract ATWLTimeLockedWrapper { event Diss(address indexed usr); event ProposalSubmitted(bytes32 indexed operationId, string functionName); - SkyTimelock public immutable timelock; - ATWLRoleTimeLockLike public immutable atwlRoleTimeLock; // TODO: consider changing to atwlState - address public immutable mainnetController; // TODO: unused for now + SkyTimelock public immutable timelock; + BeamStateLike public immutable beamState; + address public immutable mainnetController; // TODO: unused for now - constructor(address timelock_, address atwlRoleTimeLock_, address mainnetController_) { + constructor(address timelock_, address beamState_, address mainnetController_) { timelock = SkyTimelock(payable(timelock_)); - atwlRoleTimeLock = ATWLRoleTimeLockLike(atwlRoleTimeLock_); + beamState = BeamStateLike(beamState_); mainnetController = mainnetController_; wards[msg.sender] = 1; @@ -104,7 +112,7 @@ contract ATWLTimeLockedWrapper { function _submitProposal(bytes memory payload, bytes32 predecessor, bytes32 salt, uint256 delay) internal returns (bytes32 operationId) { address[] memory targets = new address[](1); - targets[0] = address(atwlRoleTimeLock); + targets[0] = address(beamState); uint256[] memory values = new uint256[](1); values[0] = 0; @@ -116,19 +124,77 @@ contract ATWLTimeLockedWrapper { timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); } + // --- Configuration Parameters --- + + function setHop(address pau, uint256 value, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + BeamStateLike.setHop.selector, + pau, + value + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setHop"); + } + + function setMaxChange(address pau, uint256 value, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + BeamStateLike.setMaxChange.selector, + pau, + value + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMaxChange"); + } + + // --- CBeam Management --- + + function addCBeam(address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + BeamStateLike.addCBeam.selector, + cBeam + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "addCBeam"); + } + + function delCBeam(address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + BeamStateLike.delCBeam.selector, + cBeam + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "delCBeam"); + } + // --- Governance Operations --- - function addGovOps(address pau, address usr, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + function addGovOps(address pau, address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + // Maps to setCBeamForPau in BeamState - requires cBeam to be added first via addCBeam bytes memory payload = abi.encodeWithSelector( - ATWLRoleTimeLockLike.addGovOps.selector, + BeamStateLike.setCBeamForPau.selector, pau, - usr + cBeam ); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "addGovOps"); } + function removeGovOps(address pau, address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + BeamStateLike.unsetCBeamForPau.selector, + pau, + cBeam + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "removeGovOps"); + } + // --- Rate Limits --- function addInitRateLimits( @@ -138,7 +204,7 @@ contract ATWLTimeLockedWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory payload = abi.encodeWithSelector( - ATWLRoleTimeLockLike.addInitRateLimits.selector, + BeamStateLike.addInitRateLimits.selector, config.key, config.pau, config.maxAmount, @@ -161,12 +227,12 @@ contract ATWLTimeLockedWrapper { address[] memory targets = new address[](len); uint256[] memory values = new uint256[](len); bytes[] memory payloads = new bytes[](len); - address target = address(atwlRoleTimeLock); + address target = address(beamState); for (uint256 i = 0; i < len; ++i) { targets[i] = target; payloads[i] = abi.encodeWithSelector( - ATWLRoleTimeLockLike.addInitRateLimits.selector, + BeamStateLike.addInitRateLimits.selector, configs[i].key, configs[i].pau, configs[i].maxAmount, @@ -180,6 +246,17 @@ contract ATWLTimeLockedWrapper { emit ProposalSubmitted(operationId, "batchAddInitRateLimits"); } + function delInitRateLimits(bytes32 key, address pau, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + BeamStateLike.delInitRateLimits.selector, + key, + pau + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "delInitRateLimits"); + } + // --- Controller Actions --- function setMintRecipient( @@ -195,7 +272,7 @@ contract ATWLTimeLockedWrapper { destinationDomain, mintRecipient ); - bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMintRecipient"); @@ -214,7 +291,7 @@ contract ATWLTimeLockedWrapper { destinationEndpointId, layerZeroRecipient ); - bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setLayerZeroRecipient"); @@ -233,7 +310,7 @@ contract ATWLTimeLockedWrapper { pool, maxSlippage ); - bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxSlippage"); @@ -252,7 +329,7 @@ contract ATWLTimeLockedWrapper { exchange, otcBuffer ); - bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCBuffer"); @@ -271,7 +348,7 @@ contract ATWLTimeLockedWrapper { exchange, rechargeRate18 ); - bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCRechargeRate"); @@ -292,7 +369,7 @@ contract ATWLTimeLockedWrapper { asset, isWhitelisted ); - bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCWhitelistedAsset"); @@ -315,7 +392,7 @@ contract ATWLTimeLockedWrapper { tickUpperMax, maxTickSpacing ); - bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV4TickLimits"); @@ -336,10 +413,21 @@ contract ATWLTimeLockedWrapper { shares, maxExpectedAssets ); - bytes memory payload = abi.encodeWithSelector(ATWLRoleTimeLockLike.addInitControllerActions.selector, controllerData, pau); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxExchangeRate"); } + + function delInitControllerActions(bytes32 key, address pau, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + BeamStateLike.delInitControllerActions.selector, + key, + pau + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "delInitControllerActions"); + } } From 2430c4010de78934b9af289b9b61541bb4f75f8c Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 8 Jan 2026 23:54:12 +0700 Subject: [PATCH 021/150] Add grove alm controller functions --- src/timelock/ATWLTimeLockedWrapper.sol | 113 ++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) diff --git a/src/timelock/ATWLTimeLockedWrapper.sol b/src/timelock/ATWLTimeLockedWrapper.sol index ce4c825..0a307e1 100644 --- a/src/timelock/ATWLTimeLockedWrapper.sol +++ b/src/timelock/ATWLTimeLockedWrapper.sol @@ -17,7 +17,8 @@ pragma solidity ^0.8.21; // PAU code aligned to: https://github.com/sunbreak1211/pau/blob/a3e4b519c238d32c08c82145f04341144361e943/src/Option_4/Configurator.sol -// Mainnet controller aligned to: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol +// Spark Mainnet controller aligned to: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol +// Grove Mainnet controller aligned to: https://github.com/grove-labs/grove-alm-controller/blob/548c96fa22bcb13afd25cb592ec8cb4bb98c2d86/src/MainnetController.sol // TODO: Rename `pau` parameter to `almController` and `rateLimiter` - current name is ambiguous (depends on external contract changes) // TODO: Decide if we want to keep all the direct access functions or use a more generic approach @@ -38,14 +39,23 @@ interface BeamStateLike { } interface MainnetControllerLike { + + // Spark functions function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; function setLayerZeroRecipient(uint32 destinationEndpointId, bytes32 layerZeroRecipient) external; function setMaxSlippage(address pool, uint256 maxSlippage) external; + function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; function setOTCBuffer(address exchange, address otcBuffer) external; function setOTCRechargeRate(address exchange, uint256 rechargeRate18) external; function setOTCWhitelistedAsset(address exchange, address asset, bool isWhitelisted) external; - function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; function setUniswapV4TickLimits(bytes32 poolId, int24 tickLowerMin, int24 tickUpperMax, uint24 maxTickSpacing) external; + + // Grove-only functions + function setCentrifugeRecipient(uint16 centrifugeId, bytes32 recipient) external; + function setUniswapV3PoolLowerTick(address pool, int24 lowerTick) external; + function setUniswapV3PoolUpperTick(address pool, int24 upperTick) external; + function setUniswapV3PoolMaxTickDelta(address pool, uint24 maxTickDelta) external; + function setUniswapV3PoolTwapSecondsAgo(address pool, uint32 twapSecondsAgo) external; } struct RateLimitConfig { @@ -418,6 +428,105 @@ contract ATWLTimeLockedWrapper { operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxExchangeRate"); } + + // Grove-only functions + + function setCentrifugeRecipient( + uint16 centrifugeId, + bytes32 recipient, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setCentrifugeRecipient.selector, + centrifugeId, + recipient + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setCentrifugeRecipient"); + } + + function setUniswapV3PoolLowerTick( + address pool, + int24 lowerTick, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setUniswapV3PoolLowerTick.selector, + pool, + lowerTick + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV3PoolLowerTick"); + } + + function setUniswapV3PoolUpperTick( + address pool, + int24 upperTick, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setUniswapV3PoolUpperTick.selector, + pool, + upperTick + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV3PoolUpperTick"); + } + + function setUniswapV3PoolMaxTickDelta( + address pool, + uint24 maxTickDelta, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setUniswapV3PoolMaxTickDelta.selector, + pool, + maxTickDelta + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV3PoolMaxTickDelta"); + } + + function setUniswapV3PoolTwapSecondsAgo( + address pool, + uint32 twapSecondsAgo, + address pau, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.setUniswapV3PoolTwapSecondsAgo.selector, + pool, + twapSecondsAgo + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, pau); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV3PoolTwapSecondsAgo"); + } + + // --- Deletion Functions --- function delInitControllerActions(bytes32 key, address pau, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { bytes memory payload = abi.encodeWithSelector( From 3db9f3033067262659b56dbeca8c001c13f12a5f Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 8 Jan 2026 16:46:00 -0300 Subject: [PATCH 022/150] Add dss-test dependency --- .gitmodules | 3 +++ lib/dss-test | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/dss-test diff --git a/.gitmodules b/.gitmodules index 888d42d..5590a3f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/dss-test"] + path = lib/dss-test + url = https://github.com/sky-ecosystem/dss-test diff --git a/lib/dss-test b/lib/dss-test new file mode 160000 index 0000000..61cf29f --- /dev/null +++ b/lib/dss-test @@ -0,0 +1 @@ +Subproject commit 61cf29fc0cf0c177a3b4072b433c43a7326ccd7b From ff2ec2c9bec4b5e85558469636197421ba65ae7d Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 8 Jan 2026 16:46:26 -0300 Subject: [PATCH 023/150] Remove addInitControllerActions with key --- src/BeamState.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 5937cbf..45b6a8e 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -176,12 +176,6 @@ contract BeamState { emit AddInitControllerActions(key, pau); } - function addInitControllerActions(bytes32 key, address pau) external roleAuth { - // TODO: We will have to remove this function if finally having to save or log the raw data for enumeration purposes - initControllerActions[key][pau] = true; - emit AddInitControllerActions(key, pau); - } - function delInitControllerActions(bytes32 key, address pau) external roleAuth { delete initControllerActions[key][pau]; emit DelInitControllerActions(key, pau); From 705ab2ce7f198607cedc6908ce435dac960d27ce Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 8 Jan 2026 16:50:39 -0300 Subject: [PATCH 024/150] Unit tests for State --- test/BeamState.t.sol | 631 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 631 insertions(+) create mode 100644 test/BeamState.t.sol diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol new file mode 100644 index 0000000..8156d25 --- /dev/null +++ b/test/BeamState.t.sol @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +import "dss-test/DssTest.sol"; +import { BeamState } from "../src/BeamState.sol"; + +contract BeamStateTest is DssTest { + + BeamState beamState; + + address constant USER1 = address(0x1); + address constant USER2 = address(0x2); + address constant CBEAM1 = address(0xC1); + address constant CBEAM2 = address(0xC2); + address constant PAU1 = address(0xA1); + address constant PAU2 = address(0xA2); + + event SetUserRole(address indexed who, uint8 indexed role, bool enabled); + event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); + event SetHop(address indexed pau, uint256 value); + event SetMaxChange(address indexed pau, uint256 value); + event AddCBeam(address indexed cBeam); + event DelCBeam(address indexed cBeam); + event SetCBeamForPau(address indexed pau, address indexed cBeam); + event UnsetCBeamForPau(address indexed pau, address indexed cBeam); + event AddInitRateLimits(bytes32 indexed key, address indexed pau, uint256 maxAmount, uint256 slope); + event DelInitRateLimits(bytes32 indexed key, address indexed pau); + event AddInitControllerActions(bytes32 indexed key, address indexed pau); + event DelInitControllerActions(bytes32 indexed key, address indexed pau); + + function setUp() public { + beamState = new BeamState(); + } + + // --- Constructor & Initialization Tests --- + + function testConstructor() public { + vm.expectEmit(); + emit Rely(address(this)); + BeamState beamState2 = new BeamState(); + assertEq(beamState2.wards(address(this)), 1, "deployer should be ward"); + } + + // --- Authorization Tests --- + + function testAuth() public { + checkAuth(address(beamState), "BeamState"); + } + + function testModifiers() public { + bytes4[] memory authedMethods = new bytes4[](2); + authedMethods[0] = beamState.setUserRole.selector; + authedMethods[1] = beamState.setRoleAction.selector; + + vm.startPrank(address(0xBEEF)); + checkModifier(address(beamState), "BeamState/not-authorized", authedMethods); + vm.stopPrank(); + } + + // --- Role Management Tests --- + + function testSetUserRole() public { + assertFalse(beamState.hasUserRole(USER1, 0), "USER1 should not have role 0 initially"); + + vm.expectEmit(); + emit SetUserRole(USER1, 0, true); + beamState.setUserRole(USER1, 0, true); + + assertTrue(beamState.hasUserRole(USER1, 0), "USER1 should have role 0 after setting"); + } + + function testSetUserRoleMultiple() public { + beamState.setUserRole(USER1, 0, true); + beamState.setUserRole(USER1, 5, true); + beamState.setUserRole(USER1, 255, true); + + assertTrue(beamState.hasUserRole(USER1, 0), "USER1 should have role 0"); + assertTrue(beamState.hasUserRole(USER1, 5), "USER1 should have role 5"); + assertTrue(beamState.hasUserRole(USER1, 255), "USER1 should have role 255"); + assertFalse(beamState.hasUserRole(USER1, 1), "USER1 should not have role 1"); + } + + function testUnsetUserRole() public { + beamState.setUserRole(USER1, 0, true); + assertTrue(beamState.hasUserRole(USER1, 0), "USER1 should have role 0"); + + vm.expectEmit(); + emit SetUserRole(USER1, 0, false); + beamState.setUserRole(USER1, 0, false); + + assertFalse(beamState.hasUserRole(USER1, 0), "USER1 should not have role 0 after unsetting"); + } + + function testSetRoleAction() public { + bytes4 sig = bytes4(keccak256("testFunction()")); + assertFalse(beamState.isActionInRole(sig, 0), "action should not be in role 0 initially"); + + vm.expectEmit(); + emit SetRoleAction(0, sig, true); + beamState.setRoleAction(0, sig, true); + + assertTrue(beamState.isActionInRole(sig, 0), "action should be in role 0 after setting"); + } + + function testSetRoleActionMultiple() public { + bytes4 sig = bytes4(keccak256("testFunction()")); + beamState.setRoleAction(0, sig, true); + beamState.setRoleAction(5, sig, true); + beamState.setRoleAction(255, sig, true); + + assertTrue(beamState.isActionInRole(sig, 0), "action should be in role 0"); + assertTrue(beamState.isActionInRole(sig, 5), "action should be in role 5"); + assertTrue(beamState.isActionInRole(sig, 255), "action should be in role 255"); + assertFalse(beamState.isActionInRole(sig, 1), "action should not be in role 1"); + } + + function testUnsetRoleAction() public { + bytes4 sig = bytes4(keccak256("testFunction()")); + beamState.setRoleAction(0, sig, true); + assertTrue(beamState.isActionInRole(sig, 0), "action should be in role 0"); + + vm.expectEmit(); + emit SetRoleAction(0, sig, false); + beamState.setRoleAction(0, sig, false); + + assertFalse(beamState.isActionInRole(sig, 0), "action should not be in role 0 after unsetting"); + } + + // --- Role-based Authorization Tests --- + + function testRoleAuthWithWard() public { + // Ward can call without role + beamState.setHop(PAU1, 100); + assertEq(beamState.hop(PAU1), 100, "hop should be set by ward"); + } + + function testRoleAuthWithRole() public { + // Set up role + beamState.setRoleAction(5, beamState.setHop.selector, true); + beamState.setUserRole(USER1, 5, true); + + // User with role can call + vm.prank(USER1); + beamState.setHop(PAU1, 200); + assertEq(beamState.hop(PAU1), 200, "hop should be set by role user"); + } + + function testRoleAuthWithoutRoleOrWard() public { + vm.prank(USER1); + vm.expectRevert("BeamState/role-not-authorized"); + beamState.setHop(PAU1, 100); + } + + function testRoleAuthWithWrongRole() public { + // Set up role 5 for action + beamState.setRoleAction(5, beamState.setHop.selector, true); + // Give user role 3 instead + beamState.setUserRole(USER1, 3, true); + + vm.prank(USER1); + vm.expectRevert("BeamState/role-not-authorized"); + beamState.setHop(PAU1, 100); + } + + // --- Hop Configuration Tests --- + + function testSetHop() public { + vm.expectEmit(); + emit SetHop(PAU1, 86400); + beamState.setHop(PAU1, 86400); + + assertEq(beamState.hop(PAU1), 86400, "hop should be set for PAU1"); + assertEq(beamState.getHop(PAU1), 86400, "getHop should return correct value"); + } + + function testSetHopGlobal() public { + beamState.setHop(address(0), 3600); + assertEq(beamState.hop(address(0)), 3600, "global hop should be set"); + assertEq(beamState.getHop(address(0)), 3600, "getHop should return global value"); + } + + function testGetHopFallback() public { + beamState.setHop(address(0), 7200); + + // PAU1 has no specific hop, should fallback to global + assertEq(beamState.hop(PAU1), 0, "PAU1 hop storage should be 0"); + assertEq(beamState.getHop(PAU1), 7200, "getHop should return global fallback"); + + // Set specific hop for PAU1 + beamState.setHop(PAU1, 14400); + assertEq(beamState.getHop(PAU1), 14400, "getHop should return PAU1 specific value"); + } + + function testSetHopRoleAuth() public { + beamState.setRoleAction(1, beamState.setHop.selector, true); + beamState.setUserRole(USER1, 1, true); + + vm.prank(USER1); + beamState.setHop(PAU1, 1000); + + assertEq(beamState.hop(PAU1), 1000, "hop should be set by role user"); + } + + // --- MaxChange Configuration Tests --- + + function testSetMaxChange() public { + vm.expectEmit(); + emit SetMaxChange(PAU1, 2 * WAD); + beamState.setMaxChange(PAU1, 2 * WAD); + + assertEq(beamState.maxChange(PAU1), 2 * WAD, "maxChange should be set for PAU1"); + assertEq(beamState.getMaxChange(PAU1), 2 * WAD, "getMaxChange should return correct value"); + } + + function testSetMaxChangeMinimumWad() public { + beamState.setMaxChange(PAU1, WAD); + assertEq(beamState.maxChange(PAU1), WAD, "maxChange should be WAD"); + } + + function testSetMaxChangeBelowWad() public { + vm.expectRevert("Configurator/maxChange-below-1x"); + beamState.setMaxChange(PAU1, WAD - 1); + } + + function testSetMaxChangeGlobal() public { + beamState.setMaxChange(address(0), 15 * WAD / 10); // 1.5x + assertEq(beamState.maxChange(address(0)), 15 * WAD / 10, "global maxChange should be set"); + assertEq(beamState.getMaxChange(address(0)), 15 * WAD / 10, "getMaxChange should return global value"); + } + + function testGetMaxChangeFallback() public { + beamState.setMaxChange(address(0), 2 * WAD); + + // PAU1 has no specific maxChange, should fallback to global + assertEq(beamState.maxChange(PAU1), 0, "PAU1 maxChange storage should be 0"); + assertEq(beamState.getMaxChange(PAU1), 2 * WAD, "getMaxChange should return global fallback"); + + // Set specific maxChange for PAU1 + beamState.setMaxChange(PAU1, 3 * WAD); + assertEq(beamState.getMaxChange(PAU1), 3 * WAD, "getMaxChange should return PAU1 specific value"); + } + + function testSetMaxChangeRoleAuth() public { + beamState.setRoleAction(2, beamState.setMaxChange.selector, true); + beamState.setUserRole(USER1, 2, true); + + vm.prank(USER1); + beamState.setMaxChange(PAU1, 2 * WAD); + + assertEq(beamState.maxChange(PAU1), 2 * WAD, "maxChange should be set by role user"); + } + + // --- cBeam Management Tests --- + + function testAddCBeam() public { + assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should not be registered initially"); + + vm.expectEmit(); + emit AddCBeam(CBEAM1); + beamState.addCBeam(CBEAM1); + + assertEq(beamState.cBeams(CBEAM1), 1, "CBEAM1 should be registered"); + } + + function testAddCBeamRoleAuth() public { + beamState.setRoleAction(3, beamState.addCBeam.selector, true); + beamState.setUserRole(USER1, 3, true); + + vm.prank(USER1); + beamState.addCBeam(CBEAM1); + + assertEq(beamState.cBeams(CBEAM1), 1, "CBEAM1 should be registered by role user"); + } + + function testDelCBeam() public { + beamState.addCBeam(CBEAM1); + assertEq(beamState.cBeams(CBEAM1), 1, "CBEAM1 should be registered"); + + vm.expectEmit(); + emit DelCBeam(CBEAM1); + beamState.delCBeam(CBEAM1); + + assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should not be registered after delete"); + } + + function testDelCBeamRoleAuth() public { + beamState.addCBeam(CBEAM1); + + beamState.setRoleAction(3, beamState.delCBeam.selector, true); + beamState.setUserRole(USER1, 3, true); + + vm.prank(USER1); + beamState.delCBeam(CBEAM1); + + assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should be deleted by role user"); + } + + // --- cBeam-PAU Mapping Tests --- + + function testSetCBeamForPau() public { + beamState.addCBeam(CBEAM1); + + assertEq(beamState.pauCBeams(PAU1, CBEAM1), 0, "CBEAM1 should not be authorized for PAU1 initially"); + + vm.expectEmit(); + emit SetCBeamForPau(PAU1, CBEAM1); + beamState.setCBeamForPau(PAU1, CBEAM1); + + assertEq(beamState.pauCBeams(PAU1, CBEAM1), 1, "CBEAM1 should be authorized for PAU1"); + } + + function testSetCBeamForPauNotExisting() public { + vm.expectRevert("BeamState/not-existing-cBeam"); + beamState.setCBeamForPau(PAU1, CBEAM1); + } + + function testSetCBeamForPauRoleAuth() public { + beamState.addCBeam(CBEAM1); + + beamState.setRoleAction(4, beamState.setCBeamForPau.selector, true); + beamState.setUserRole(USER1, 4, true); + + vm.prank(USER1); + beamState.setCBeamForPau(PAU1, CBEAM1); + + assertEq(beamState.pauCBeams(PAU1, CBEAM1), 1, "CBEAM1 should be set for PAU1 by role user"); + } + + function testUnsetCBeamForPau() public { + beamState.addCBeam(CBEAM1); + beamState.setCBeamForPau(PAU1, CBEAM1); + assertEq(beamState.pauCBeams(PAU1, CBEAM1), 1, "CBEAM1 should be authorized for PAU1"); + + vm.expectEmit(); + emit UnsetCBeamForPau(PAU1, CBEAM1); + beamState.unsetCBeamForPau(PAU1, CBEAM1); + + assertEq(beamState.pauCBeams(PAU1, CBEAM1), 0, "CBEAM1 should not be authorized for PAU1 after unset"); + } + + function testUnsetCBeamForPauRoleAuth() public { + beamState.addCBeam(CBEAM1); + beamState.setCBeamForPau(PAU1, CBEAM1); + + beamState.setRoleAction(4, beamState.unsetCBeamForPau.selector, true); + beamState.setUserRole(USER1, 4, true); + + vm.prank(USER1); + beamState.unsetCBeamForPau(PAU1, CBEAM1); + + assertEq(beamState.pauCBeams(PAU1, CBEAM1), 0, "CBEAM1 should be unset for PAU1 by role user"); + } + + // --- Rate Limits Tests --- + + function testAddInitRateLimits() public { + bytes32 key = keccak256("test-key"); + + vm.expectEmit(); + emit AddInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, 1000 * WAD, "maxAmount should be set"); + assertEq(limits.slope, 10 * WAD, "slope should be set"); + } + + function testAddInitRateLimitsGlobal() public { + bytes32 key = keccak256("global-key"); + + beamState.addInitRateLimits(key, address(0), 500 * WAD, 5 * WAD); + + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, address(0)); + assertEq(limits.maxAmount, 500 * WAD, "global maxAmount should be set"); + assertEq(limits.slope, 5 * WAD, "global slope should be set"); + } + + function testGetInitRateLimitsFallback() public { + bytes32 key = keccak256("fallback-key"); + + // Set global default + beamState.addInitRateLimits(key, address(0), 100 * WAD, 1 * WAD); + + // Get for specific PAU should return global + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, 100 * WAD, "should return global maxAmount"); + assertEq(limits.slope, 1 * WAD, "should return global slope"); + + // Set specific for PAU1 + beamState.addInitRateLimits(key, PAU1, 200 * WAD, 2 * WAD); + + // Should now return PAU1 specific + limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, 200 * WAD, "should return PAU1 specific maxAmount"); + assertEq(limits.slope, 2 * WAD, "should return PAU1 specific slope"); + } + + function testGetInitRateLimitsPartialFallback() public { + bytes32 key = keccak256("partial-key"); + + // Set global default + beamState.addInitRateLimits(key, address(0), 100 * WAD, 1 * WAD); + + // Set only maxAmount for PAU1 + beamState.addInitRateLimits(key, PAU1, 200 * WAD, 0); + + // Should fallback to global because slope is 0 + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, 100 * WAD, "should fallback to global maxAmount"); + assertEq(limits.slope, 1 * WAD, "should fallback to global slope"); + } + + function testAddInitRateLimitsRoleAuth() public { + bytes32 key = keccak256("auth-key"); + beamState.setRoleAction(5, beamState.addInitRateLimits.selector, true); + beamState.setUserRole(USER1, 5, true); + + vm.prank(USER1); + beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, 1000 * WAD, "maxAmount should be set by role user"); + } + + function testDelInitRateLimits() public { + bytes32 key = keccak256("del-key"); + + beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, 1000 * WAD, "maxAmount should be set"); + + vm.expectEmit(); + emit DelInitRateLimits(key, PAU1); + beamState.delInitRateLimits(key, PAU1); + + limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, 0, "maxAmount should be deleted"); + assertEq(limits.slope, 0, "slope should be deleted"); + } + + function testDelInitRateLimitsRoleAuth() public { + bytes32 key = keccak256("del-auth-key"); + beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + + beamState.setRoleAction(5, beamState.delInitRateLimits.selector, true); + beamState.setUserRole(USER1, 5, true); + + vm.prank(USER1); + beamState.delInitRateLimits(key, PAU1); + + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, 0, "maxAmount should be deleted by role user"); + } + + // --- Controller Actions Tests --- + + function testAddInitControllerActionsFromBytes() public { + bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); + bytes32 expectedKey = keccak256(data); + + vm.expectEmit(); + emit AddInitControllerActions(expectedKey, PAU1); + bytes32 returnedKey = beamState.addInitControllerActions(data, PAU1); + + assertEq(returnedKey, expectedKey, "returned key should match expected"); + assertTrue(beamState.isControllerActionEnabled(expectedKey, PAU1), "action should be enabled for PAU1"); + } + + function testAddInitControllerActionsGlobal() public { + bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); + bytes32 key = keccak256(data); + + beamState.addInitControllerActions(data, address(0)); + + assertTrue(beamState.isControllerActionEnabled(key, address(0)), "action should be enabled globally"); + assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled for any PAU"); + assertTrue(beamState.isControllerActionEnabled(key, PAU2), "action should be enabled for any PAU"); + } + + function testIsControllerActionEnabledSpecificOverGlobal() public { + bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); + bytes32 key = keccak256(data); + + beamState.addInitControllerActions(data, PAU1); + + assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled for PAU1"); + assertFalse(beamState.isControllerActionEnabled(key, PAU2), "action should not be enabled for PAU2"); + } + + function testIsControllerActionEnabledBothGlobalAndSpecific() public { + bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); + bytes32 key = keccak256(data); + + beamState.addInitControllerActions(data, address(0)); + beamState.addInitControllerActions(data, PAU1); + + assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled for PAU1"); + assertTrue(beamState.isControllerActionEnabled(key, PAU2), "action should be enabled for PAU2 via global"); + } + + function testAddInitControllerActionsRoleAuth() public { + bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); + bytes32 key = keccak256(data); + beamState.setRoleAction(6, beamState.addInitControllerActions.selector, true); + beamState.setUserRole(USER1, 6, true); + + vm.prank(USER1); + beamState.addInitControllerActions(data, PAU1); + + assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled by role user"); + } + + function testDelInitControllerActions() public { + bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); + bytes32 key = keccak256(data); + + beamState.addInitControllerActions(data, PAU1); + assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled"); + + vm.expectEmit(); + emit DelInitControllerActions(key, PAU1); + beamState.delInitControllerActions(key, PAU1); + + assertFalse(beamState.isControllerActionEnabled(key, PAU1), "action should be disabled after delete"); + } + + function testDelInitControllerActionsRoleAuth() public { + bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); + bytes32 key = keccak256(data); + beamState.addInitControllerActions(data, PAU1); + + beamState.setRoleAction(6, beamState.delInitControllerActions.selector, true); + beamState.setUserRole(USER1, 6, true); + + vm.prank(USER1); + beamState.delInitControllerActions(key, PAU1); + + assertFalse(beamState.isControllerActionEnabled(key, PAU1), "action should be disabled by role user"); + } + + // --- Combined functions Tests --- + + function testFullRoleBasedWorkflow() public { + // Setup: Create roles for different operations + bytes4 hopSig = beamState.setHop.selector; + bytes4 cBeamSig = beamState.addCBeam.selector; + bytes4 limitsSig = beamState.addInitRateLimits.selector; + + // Role 1: Can set hop + beamState.setRoleAction(1, hopSig, true); + // Role 2: Can manage cBeams + beamState.setRoleAction(2, cBeamSig, true); + beamState.setRoleAction(2, beamState.setCBeamForPau.selector, true); + // Role 3: Can set rate limits + beamState.setRoleAction(3, limitsSig, true); + + // Assign roles to users + beamState.setUserRole(USER1, 1, true); + beamState.setUserRole(USER2, 2, true); + beamState.setUserRole(USER2, 3, true); // USER2 has multiple roles + + // USER1 sets hop + vm.prank(USER1); + beamState.setHop(PAU1, 3600); + assertEq(beamState.hop(PAU1), 3600, "USER1 should be able to set hop"); + + // USER2 adds cBeam + vm.prank(USER2); + beamState.addCBeam(CBEAM1); + assertEq(beamState.cBeams(CBEAM1), 1, "USER2 should be able to add cBeam"); + + // USER2 sets cBeam for PAU + vm.prank(USER2); + beamState.setCBeamForPau(PAU1, CBEAM1); + assertEq(beamState.pauCBeams(PAU1, CBEAM1), 1, "USER2 should be able to set cBeam for PAU"); + + // USER2 adds rate limits + bytes32 key = keccak256("integration-key"); + vm.prank(USER2); + beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, 1000 * WAD, "USER2 should be able to add rate limits"); + + // USER1 cannot add cBeam (wrong role) + vm.prank(USER1); + vm.expectRevert("BeamState/role-not-authorized"); + beamState.addCBeam(CBEAM2); + } + + function testMultiplePauConfiguration() public { + bytes32 key = keccak256("multi-pau-key"); + + // Configure different settings for different PAUs + beamState.setHop(PAU1, 1000); + beamState.setHop(PAU2, 2000); + beamState.setMaxChange(PAU1, 2 * WAD); + beamState.setMaxChange(PAU2, 3 * WAD); + beamState.addInitRateLimits(key, PAU1, 100 * WAD, 1 * WAD); + beamState.addInitRateLimits(key, PAU2, 200 * WAD, 2 * WAD); + + // Verify each PAU has its own configuration + assertEq(beamState.getHop(PAU1), 1000, "PAU1 hop should be 1000"); + assertEq(beamState.getHop(PAU2), 2000, "PAU2 hop should be 2000"); + assertEq(beamState.getMaxChange(PAU1), 2 * WAD, "PAU1 maxChange should be 2x"); + assertEq(beamState.getMaxChange(PAU2), 3 * WAD, "PAU2 maxChange should be 3x"); + + BeamState.DefaultRateLimits memory limits1 = beamState.getInitRateLimits(key, PAU1); + BeamState.DefaultRateLimits memory limits2 = beamState.getInitRateLimits(key, PAU2); + assertEq(limits1.maxAmount, 100 * WAD, "PAU1 maxAmount should be 100"); + assertEq(limits2.maxAmount, 200 * WAD, "PAU2 maxAmount should be 200"); + } + + function testUnlimitedRateLimitConfiguration() public { + bytes32 key = keccak256("unlimited-key"); + + // Set unlimited as global default first (since getInitRateLimits has fallback logic for slope == 0) + beamState.addInitRateLimits(key, address(0), type(uint256).max, 0); + + // Now set for specific PAU + beamState.addInitRateLimits(key, PAU1, type(uint256).max, 1); + + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + assertEq(limits.maxAmount, type(uint256).max, "maxAmount should be unlimited"); + assertEq(limits.slope, 1, "slope should be 1"); + + // Test that global unlimited also works + limits = beamState.getInitRateLimits(key, PAU2); + assertEq(limits.maxAmount, type(uint256).max, "global maxAmount should be unlimited"); + assertEq(limits.slope, 0, "global slope should be 0 for unlimited"); + } +} From 0d66c590e43254c85bb03b4c94564e2d0b4ad53c Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 9 Jan 2026 09:53:28 -0300 Subject: [PATCH 025/150] nit change --- test/BeamState.t.sol | 58 ++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 8156d25..22c1737 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -166,29 +166,29 @@ contract BeamStateTest is DssTest { function testSetHop() public { vm.expectEmit(); - emit SetHop(PAU1, 86400); - beamState.setHop(PAU1, 86400); + emit SetHop(PAU1, 86_400); + beamState.setHop(PAU1, 86_400); - assertEq(beamState.hop(PAU1), 86400, "hop should be set for PAU1"); - assertEq(beamState.getHop(PAU1), 86400, "getHop should return correct value"); + assertEq(beamState.hop(PAU1), 86_400, "hop should be set for PAU1"); + assertEq(beamState.getHop(PAU1), 86_400, "getHop should return correct value"); } function testSetHopGlobal() public { - beamState.setHop(address(0), 3600); - assertEq(beamState.hop(address(0)), 3600, "global hop should be set"); - assertEq(beamState.getHop(address(0)), 3600, "getHop should return global value"); + beamState.setHop(address(0), 3_600); + assertEq(beamState.hop(address(0)), 3_600, "global hop should be set"); + assertEq(beamState.getHop(address(0)), 3_600, "getHop should return global value"); } function testGetHopFallback() public { - beamState.setHop(address(0), 7200); + beamState.setHop(address(0), 7_200); // PAU1 has no specific hop, should fallback to global assertEq(beamState.hop(PAU1), 0, "PAU1 hop storage should be 0"); - assertEq(beamState.getHop(PAU1), 7200, "getHop should return global fallback"); + assertEq(beamState.getHop(PAU1), 7_200, "getHop should return global fallback"); // Set specific hop for PAU1 - beamState.setHop(PAU1, 14400); - assertEq(beamState.getHop(PAU1), 14400, "getHop should return PAU1 specific value"); + beamState.setHop(PAU1, 14_400); + assertEq(beamState.getHop(PAU1), 14_400, "getHop should return PAU1 specific value"); } function testSetHopRoleAuth() public { @@ -196,9 +196,9 @@ contract BeamStateTest is DssTest { beamState.setUserRole(USER1, 1, true); vm.prank(USER1); - beamState.setHop(PAU1, 1000); + beamState.setHop(PAU1, 1_000); - assertEq(beamState.hop(PAU1), 1000, "hop should be set by role user"); + assertEq(beamState.hop(PAU1), 1_000, "hop should be set by role user"); } // --- MaxChange Configuration Tests --- @@ -357,11 +357,11 @@ contract BeamStateTest is DssTest { bytes32 key = keccak256("test-key"); vm.expectEmit(); - emit AddInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); - beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + emit AddInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); + beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); - assertEq(limits.maxAmount, 1000 * WAD, "maxAmount should be set"); + assertEq(limits.maxAmount, 1_000 * WAD, "maxAmount should be set"); assertEq(limits.slope, 10 * WAD, "slope should be set"); } @@ -416,18 +416,18 @@ contract BeamStateTest is DssTest { beamState.setUserRole(USER1, 5, true); vm.prank(USER1); - beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); - assertEq(limits.maxAmount, 1000 * WAD, "maxAmount should be set by role user"); + assertEq(limits.maxAmount, 1_000 * WAD, "maxAmount should be set by role user"); } function testDelInitRateLimits() public { bytes32 key = keccak256("del-key"); - beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); - assertEq(limits.maxAmount, 1000 * WAD, "maxAmount should be set"); + assertEq(limits.maxAmount, 1_000 * WAD, "maxAmount should be set"); vm.expectEmit(); emit DelInitRateLimits(key, PAU1); @@ -440,7 +440,7 @@ contract BeamStateTest is DssTest { function testDelInitRateLimitsRoleAuth() public { bytes32 key = keccak256("del-auth-key"); - beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); beamState.setRoleAction(5, beamState.delInitRateLimits.selector, true); beamState.setUserRole(USER1, 5, true); @@ -561,8 +561,8 @@ contract BeamStateTest is DssTest { // USER1 sets hop vm.prank(USER1); - beamState.setHop(PAU1, 3600); - assertEq(beamState.hop(PAU1), 3600, "USER1 should be able to set hop"); + beamState.setHop(PAU1, 3_600); + assertEq(beamState.hop(PAU1), 3_600, "USER1 should be able to set hop"); // USER2 adds cBeam vm.prank(USER2); @@ -577,9 +577,9 @@ contract BeamStateTest is DssTest { // USER2 adds rate limits bytes32 key = keccak256("integration-key"); vm.prank(USER2); - beamState.addInitRateLimits(key, PAU1, 1000 * WAD, 10 * WAD); + beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); - assertEq(limits.maxAmount, 1000 * WAD, "USER2 should be able to add rate limits"); + assertEq(limits.maxAmount, 1_000 * WAD, "USER2 should be able to add rate limits"); // USER1 cannot add cBeam (wrong role) vm.prank(USER1); @@ -591,16 +591,16 @@ contract BeamStateTest is DssTest { bytes32 key = keccak256("multi-pau-key"); // Configure different settings for different PAUs - beamState.setHop(PAU1, 1000); - beamState.setHop(PAU2, 2000); + beamState.setHop(PAU1, 1_000); + beamState.setHop(PAU2, 2_000); beamState.setMaxChange(PAU1, 2 * WAD); beamState.setMaxChange(PAU2, 3 * WAD); beamState.addInitRateLimits(key, PAU1, 100 * WAD, 1 * WAD); beamState.addInitRateLimits(key, PAU2, 200 * WAD, 2 * WAD); // Verify each PAU has its own configuration - assertEq(beamState.getHop(PAU1), 1000, "PAU1 hop should be 1000"); - assertEq(beamState.getHop(PAU2), 2000, "PAU2 hop should be 2000"); + assertEq(beamState.getHop(PAU1), 1_000, "PAU1 hop should be 1_000"); + assertEq(beamState.getHop(PAU2), 2_000, "PAU2 hop should be 2_000"); assertEq(beamState.getMaxChange(PAU1), 2 * WAD, "PAU1 maxChange should be 2x"); assertEq(beamState.getMaxChange(PAU2), 3 * WAD, "PAU2 maxChange should be 3x"); From 18145a435a6a1e5cd48d7d8b32bc076cfddcf2e7 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 9 Jan 2026 10:00:56 -0300 Subject: [PATCH 026/150] Add comment to apply fix --- src/BeamState.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BeamState.sol b/src/BeamState.sol index 45b6a8e..99cc16c 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -83,6 +83,7 @@ contract BeamState { function getInitRateLimits(bytes32 key, address pau) external view returns (DefaultRateLimits memory defaultRateLimits) { defaultRateLimits = initRateLimits[key][pau]; + // TODO: fix the next conditional to use "&&" instead of "||" as otherwise you can not set unlimited rate limit for a specific pau, tests also need to be revisited due to this change if (defaultRateLimits.maxAmount == 0 || defaultRateLimits.slope == 0) { // If not set for specific pau, check in general defaultRateLimits = initRateLimits[key][address(0)]; From 6a3c76434d0284e785a1bb5383e6ac2b9ba199b7 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 9 Jan 2026 12:09:56 -0300 Subject: [PATCH 027/150] Add Configurator unit tests --- test/Configurator.t.sol | 728 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 728 insertions(+) create mode 100644 test/Configurator.t.sol diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol new file mode 100644 index 0000000..4801d38 --- /dev/null +++ b/test/Configurator.t.sol @@ -0,0 +1,728 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +import "dss-test/DssTest.sol"; +import { Configurator, BeamStateLike, RateLimitsLike } from "../src/Configurator.sol"; +import { BeamState } from "../src/BeamState.sol"; + +// Mock PAU contract implementing RateLimits interface +contract MockPau { + RateLimitsLike.RateLimitData public rateLimitData; + uint256 public currentRateLimit; + bool public shouldFail; + bytes public lastCallData; + + function setRateLimitData( + bytes32, + uint256 maxAmount, + uint256 slope, + uint256 lastAmount, + uint256 lastUpdated + ) external { + rateLimitData = RateLimitsLike.RateLimitData(maxAmount, slope, lastAmount, lastUpdated); + } + + function setUnlimitedRateLimitData(bytes32) external { + rateLimitData = RateLimitsLike.RateLimitData(type(uint256).max, 0, type(uint256).max, block.timestamp); + } + + function getRateLimitData(bytes32) external view returns (RateLimitsLike.RateLimitData memory) { + return rateLimitData; + } + + function getCurrentRateLimit(bytes32) external view returns (uint256) { + return currentRateLimit; + } + + function setCurrentRateLimit(uint256 _current) external { + currentRateLimit = _current; + } + + function setShouldFail(bool _fail) external { + shouldFail = _fail; + } + + // Mock controller function for testing callControllerAction + function controllerFunction(uint256 value) external returns (uint256) { + lastCallData = msg.data; + require(!shouldFail, "MockPau/controller-function-failed"); + return value * 2; + } + + fallback() external { + lastCallData = msg.data; + require(!shouldFail, "MockPau/fallback-failed"); + } +} + +contract ConfiguratorTest is DssTest { + + Configurator configurator; + BeamState beamState; + MockPau pau1; + MockPau pau2; + + address constant CBEAM1 = address(0xC1); + address constant CBEAM2 = address(0xC2); + address constant USER1 = address(0x1); + + event SetRateLimit(address indexed pau, bytes32 indexed key, uint256 maxAmount, uint256 slope); + + function setUp() public { + beamState = new BeamState(); + configurator = new Configurator(address(beamState)); + + pau1 = new MockPau(); + pau2 = new MockPau(); + + // Setup default configuration + beamState.setHop(address(0), 86_400); // 1 day global default + beamState.setMaxChange(address(0), 15 * WAD / 10); // 1.5x global default + } + + // --- Constructor Tests --- + + function testConstructor() public view { + assertEq(address(configurator.beamState()), address(beamState), "beamState should be set"); + } + + // --- Helper Functions --- + + function _setupCBeam(address pau, address cBeam) internal { + beamState.addCBeam(cBeam); + beamState.setCBeamForPau(pau, cBeam); + } + + function _setupRateLimitData( + MockPau pau, + bytes32 key, + uint256 maxAmount, + uint256 slope, + uint256 lastAmount, + uint256 lastUpdated + ) internal { + pau.setRateLimitData(key, maxAmount, slope, lastAmount, lastUpdated); + pau.setCurrentRateLimit(lastAmount); + } + + function _setupDefaultRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) internal { + beamState.addInitRateLimits(key, pau, maxAmount, slope); + } + + // --- Authorization Tests --- + + function testAuthNotAuthorizedCBeam() public { + bytes32 key = keccak256("test-key"); + + vm.prank(CBEAM1); + vm.expectRevert("Configurator/not-authorized-cBeam"); + configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 10 * WAD); + } + + function testAuthWithAuthorizedCBeam() public { + bytes32 key = keccak256("test-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 400 * WAD, 4 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 400 * WAD, "maxAmount should be updated by authorized cBeam"); + } + + function testAuthCBeamCannotConfigureDifferentPau() public { + bytes32 key = keccak256("test-key"); + _setupCBeam(address(pau1), CBEAM1); + + vm.prank(CBEAM1); + vm.expectRevert("Configurator/not-authorized-cBeam"); + configurator.setRateLimit(address(pau2), key, 1_000 * WAD, 10 * WAD); + } + + // --- Safe Rate Limit Changes Tests --- + + function testSetRateLimitDecreasing() public { + bytes32 key = keccak256("decreasing-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + vm.prank(CBEAM1); + vm.expectEmit(); + emit SetRateLimit(address(pau1), key, 400 * WAD, 4 * WAD); + configurator.setRateLimit(address(pau1), key, 400 * WAD, 4 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 400 * WAD, "maxAmount should decrease immediately"); + assertEq(data.slope, 4 * WAD, "slope should decrease immediately"); + } + + function testSetRateLimitWithinDefaults() public { + bytes32 key = keccak256("within-defaults-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + // Increase but stay within defaults is safe + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 800 * WAD, 8 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 800 * WAD, "maxAmount should be set within defaults"); + assertEq(data.slope, 8 * WAD, "slope should be set within defaults"); + } + + function testSetRateLimitDecreasingOneParameter() public { + bytes32 key = keccak256("partial-decrease-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + // Decrease maxAmount, keep slope same - still safe + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 400 * WAD, 5 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 400 * WAD, "maxAmount should decrease"); + assertEq(data.slope, 5 * WAD, "slope should stay same"); + } + + // --- Unsafe Rate Limit Changes Tests --- + + function testSetRateLimitIncreasingTooSoon() public { + bytes32 key = keccak256("too-soon-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_200 * WAD, 12 * WAD); + _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + beamState.setHop(address(pau1), 3_600); // 1 hour hop + beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + + vm.warp(block.timestamp + 3_600); // Warp to allow first increase + + // First increase beyond defaults (unsafe, respects maxChange) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_300 * WAD, 13 * WAD); // 1.3x from current + + // Try to increase again immediately (should fail) + vm.prank(CBEAM1); + vm.expectRevert("Configurator/increment-too-soon"); + configurator.setRateLimit(address(pau1), key, 1_400 * WAD, 14 * WAD); + } + + function testSetRateLimitIncreasingAfterHop() public { + bytes32 key = keccak256("after-hop-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 3_000 * WAD, 30 * WAD); + _setupRateLimitData(pau1, key, 2_000 * WAD, 20 * WAD, 2_000 * WAD, block.timestamp); + beamState.setHop(address(pau1), 3_600); // 1 hour hop + beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + + vm.warp(block.timestamp + 3_600); // Warp to allow first increase + + // First increase beyond defaults (unsafe, respects maxChange 1.5x) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 3_000 * WAD, 25 * WAD); // Exactly at defaults, 1.5x maxAmount, 1.25x slope + + // Wait for hop period + vm.warp(block.timestamp + 3_600); + + // Should succeed after hop (respecting maxChange 1.5x from last) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 4_500 * WAD, 37 * WAD); // Exactly 1.5x from 3_000, ~1.48x slope + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 4_500 * WAD, "maxAmount should increase after hop"); + assertEq(data.slope, 37 * WAD, "slope should increase after hop"); + } + + function testSetRateLimitMaxChangeEnforcedMaxAmount() public { + bytes32 key = keccak256("maxchange-max-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 800 * WAD, 8 * WAD, 800 * WAD, block.timestamp); + beamState.setHop(address(pau1), 1); // Minimal hop delay + beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + + vm.warp(block.timestamp + 1); // Warp past hop to allow attempt + + // Try to increase more than maxChange allows (beyond defaults and beyond maxChange) + vm.prank(CBEAM1); + vm.expectRevert("Configurator/maxChange-maxAmount"); + configurator.setRateLimit(address(pau1), key, 1_300 * WAD, 8 * WAD); // 1.625x increase (> 1.5x) + } + + function testSetRateLimitMaxChangeEnforcedSlope() public { + bytes32 key = keccak256("maxchange-slope-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 1_000 * WAD, 8 * WAD, 1_000 * WAD, block.timestamp); + beamState.setHop(address(pau1), 1); // Minimal hop delay + beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + + vm.warp(block.timestamp + 1); // Warp past hop to allow attempt + + // Try to increase slope more than maxChange allows (beyond defaults and beyond maxChange) + vm.prank(CBEAM1); + vm.expectRevert("Configurator/maxChange-slope"); + configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 13 * WAD); // 1.625x increase (> 1.5x) + } + + function testSetRateLimitMaxChangeAtLimit() public { + bytes32 key = keccak256("maxchange-exact-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 100 * WAD); + _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + beamState.setHop(address(pau1), 3_600); + beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + + vm.warp(block.timestamp + 3_600); + + // Exactly at maxChange limit should succeed + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_500 * WAD, 15 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 1_500 * WAD, "maxAmount should be at maxChange limit"); + assertEq(data.slope, 15 * WAD, "slope should be at maxChange limit"); + } + + // --- Timestamp Tracking Tests --- + + function testZzzTimestampSetOnIncrease() public { + bytes32 key = keccak256("zzz-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 100 * WAD); + _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + + uint256 timeBefore = block.timestamp; + + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + + assertEq(configurator.zzz(address(pau1), key), timeBefore, "zzz should be set to current timestamp"); + } + + function testZzzTimestampNotSetOnDecrease() public { + bytes32 key = keccak256("zzz-decrease-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 100 * WAD); + _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 800 * WAD, 8 * WAD); + + assertEq(configurator.zzz(address(pau1), key), 0, "zzz should not be set on decrease"); + } + + function testZzzTimestampUpdatedOnSecondIncrease() public { + bytes32 key = keccak256("zzz-second-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 100 * WAD); + _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + beamState.setHop(address(pau1), 3_600); + + uint256 firstTime = block.timestamp; + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_100 * WAD, 11 * WAD); + assertEq(configurator.zzz(address(pau1), key), firstTime, "zzz should be set to first increase time"); + + vm.warp(block.timestamp + 3_600); + uint256 secondTime = block.timestamp; + + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + assertEq(configurator.zzz(address(pau1), key), secondTime, "zzz should be updated to second increase time"); + } + + // --- Unlimited Rate Limit Tests --- + + function testSetUnlimitedRateLimit() public { + bytes32 key = keccak256("unlimited-key"); + _setupCBeam(address(pau1), CBEAM1); + // Must set at global level due to getInitRateLimits fallback logic when slope == 0 + _setupDefaultRateLimits(key, address(0), type(uint256).max, 0); + _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + + vm.prank(CBEAM1); + vm.expectEmit(); + emit SetRateLimit(address(pau1), key, type(uint256).max, 0); + configurator.setRateLimit(address(pau1), key, type(uint256).max, 0); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, type(uint256).max, "maxAmount should be unlimited"); + assertEq(data.slope, 0, "slope should be 0"); + } + + function testSetUnlimitedFromLimited() public { + bytes32 key = keccak256("limited-to-unlimited-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + + // Set defaults that allow unlimited (must be at global level due to fallback logic) + _setupDefaultRateLimits(key, address(0), type(uint256).max, 0); + + // Any value passed should result in unlimited due to special unlimited check + vm.prank(CBEAM1); + vm.expectEmit(); + emit SetRateLimit(address(pau1), key, type(uint256).max, 0); + configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 10 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, type(uint256).max, "should set unlimited when defaults are unlimited"); + assertEq(data.slope, 0, "slope should be 0 for unlimited"); + } + + // --- LastAmount Capping Tests --- + + function testLastAmountCappedAtMaxAmount() public { + bytes32 key = keccak256("cap-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 2_000 * WAD, 20 * WAD, 1_500 * WAD, block.timestamp); + pau1.setCurrentRateLimit(1_500 * WAD); + + // Decrease maxAmount below current lastAmount + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 10 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.lastAmount, 1_000 * WAD, "lastAmount should be capped at new maxAmount"); + } + + function testLastAmountPreservedWhenBelowMax() public { + bytes32 key = keccak256("preserve-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 2_000 * WAD, 20 * WAD); + _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 500 * WAD, block.timestamp); + pau1.setCurrentRateLimit(500 * WAD); + + // Increase maxAmount + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_500 * WAD, 15 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.lastAmount, 500 * WAD, "lastAmount should be preserved when below new max"); + } + + // --- Global Default Fallback Tests --- + + function testGlobalDefaultsUsedWhenNoSpecific() public { + bytes32 key = keccak256("global-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(0), 1_000 * WAD, 10 * WAD); // Global defaults + _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + // Should use global defaults + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 900 * WAD, 9 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 900 * WAD, "should work with global defaults 1"); + assertEq(data.slope, 9 * WAD, "should work with global defaults 2"); + } + + function testSpecificDefaultsOverrideGlobal() public { + bytes32 key = keccak256("specific-override-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(0), 1_000 * WAD, 10 * WAD); // Global + _setupDefaultRateLimits(key, address(pau1), 2_000 * WAD, 20 * WAD); // Specific + _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + // Should use PAU1 specific defaults (higher limits) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_500 * WAD, 15 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 1_500 * WAD, "should work with specific defaults 1"); + assertEq(data.slope, 15 * WAD, "should work with specific defaults 2"); + } + + function testGlobalHopUsedWhenNoSpecific() public { + bytes32 key = keccak256("global-hop-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + + // Only global hop set (in setUp) + assertEq(beamState.hop(address(pau1)), 0, "PAU1 specific hop should be 0"); + assertEq(beamState.getHop(address(pau1)), 86_400, "should fallback to global hop"); + + vm.warp(block.timestamp + 86_400); // Warp to allow first increase + + // First increase beyond defaults (unsafe) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_100 * WAD, 11 * WAD); + + // Try second increase immediately - should fail due to global hop + vm.prank(CBEAM1); + vm.expectRevert("Configurator/increment-too-soon"); + configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + } + + function testGlobalMaxChangeUsedWhenNoSpecific() public { + bytes32 key = keccak256("global-maxchange-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + + // Only global maxChange set (in setUp to 1.5x) + assertEq(beamState.maxChange(address(pau1)), 0, "PAU1 specific maxChange should be 0"); + assertEq(beamState.getMaxChange(address(pau1)), 15 * WAD / 10, "should fallback to global maxChange"); + + vm.warp(block.timestamp + 86_400); // Wait for hop + + // Try to increase by 1.67x - should fail due to global maxChange of 1.5x (beyond defaults) + vm.prank(CBEAM1); + vm.expectRevert("Configurator/maxChange-maxAmount"); + configurator.setRateLimit(address(pau1), key, 1_500 * WAD, 9 * WAD); + } + + // --- Controller Action Tests --- + + function testCallControllerAction() public { + bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 42); + + _setupCBeam(address(pau1), CBEAM1); + beamState.addInitControllerActions(data, address(pau1)); + + vm.prank(CBEAM1); + bytes memory ret = configurator.callControllerAction(address(pau1), data); + + uint256 result = abi.decode(ret, (uint256)); + assertEq(result, 84, "controller function should return 42 * 2"); + assertEq(pau1.lastCallData(), data, "correct data should be passed to PAU"); + } + + function testCallControllerActionNotWhitelisted() public { + bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 42); + + _setupCBeam(address(pau1), CBEAM1); + // Don't whitelist the action + + vm.prank(CBEAM1); + vm.expectRevert("Configurator/not-valid-data"); + configurator.callControllerAction(address(pau1), data); + } + + function testCallControllerActionNotAuthorized() public { + bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 42); + + beamState.addInitControllerActions(data, address(pau1)); + // Don't setup cBeam authorization + + vm.prank(CBEAM1); + vm.expectRevert("Configurator/not-authorized-cBeam"); + configurator.callControllerAction(address(pau1), data); + } + + function testCallControllerActionGlobalWhitelist() public { + bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 100); + + _setupCBeam(address(pau1), CBEAM1); + _setupCBeam(address(pau2), CBEAM1); + beamState.addInitControllerActions(data, address(0)); // Global whitelist + + // Should work for any PAU + vm.prank(CBEAM1); + bytes memory ret1 = configurator.callControllerAction(address(pau1), data); + assertEq(abi.decode(ret1, (uint256)), 200, "should work with global whitelist for pau1"); + + vm.prank(CBEAM1); + bytes memory ret2 = configurator.callControllerAction(address(pau2), data); + assertEq(abi.decode(ret2, (uint256)), 200, "should work with global whitelist for pau2"); + } + + function testCallControllerActionFails() public { + bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 42); + + _setupCBeam(address(pau1), CBEAM1); + beamState.addInitControllerActions(data, address(pau1)); + pau1.setShouldFail(true); + + vm.prank(CBEAM1); + vm.expectRevert("Configurator/call-failed"); + configurator.callControllerAction(address(pau1), data); + } + + function testCallControllerActionArbitraryData() public { + bytes memory data = abi.encodeWithSignature("someOtherFunction(address,uint256)", address(this), 999); + + _setupCBeam(address(pau1), CBEAM1); + beamState.addInitControllerActions(data, address(pau1)); + + vm.prank(CBEAM1); + configurator.callControllerAction(address(pau1), data); + + assertEq(pau1.lastCallData(), data, "arbitrary data should be passed correctly"); + } + + // --- Combined functions Tests --- + + function testFullRateLimitWorkflow() public { + bytes32 key = keccak256("workflow-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 800 * WAD, 8 * WAD, 800 * WAD, block.timestamp); + beamState.setHop(address(pau1), 3_600); + beamState.setMaxChange(address(pau1), 2 * WAD); // 2x + + vm.warp(block.timestamp + 3_600); // Warp to allow first unsafe increase + + // 1. Decrease limits (safe, immediate) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 700 * WAD, 7 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 700 * WAD, "step 1: decrease should work immediately"); + + // 2. Increase limits beyond defaults (unsafe, sets timestamp) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + + data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 1_200 * WAD, "step 2: first increase should work"); + uint256 firstIncrease = configurator.zzz(address(pau1), key); + assertGt(firstIncrease, 0, "step 2: zzz should be set"); + + // 3. Try immediate second increase (should fail) + vm.prank(CBEAM1); + vm.expectRevert("Configurator/increment-too-soon"); + configurator.setRateLimit(address(pau1), key, 1_400 * WAD, 14 * WAD); + + // 4. Wait hop period + vm.warp(block.timestamp + 3_600); + + // 5. Increase again (should work, respecting maxChange) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 2_400 * WAD, 24 * WAD); // Exactly 2x + + data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 2_400 * WAD, "step 5: second increase should work after hop"); + assertEq(data.slope, 24 * WAD, "step 5: slope should increase"); + + // 6. Try to exceed maxChange + vm.warp(block.timestamp + 3_600); + vm.prank(CBEAM1); + vm.expectRevert("Configurator/maxChange-maxAmount"); + configurator.setRateLimit(address(pau1), key, 4_900 * WAD, 24 * WAD); // >2x (2.04x) + } + + function testMultipleCBeamsMultiplePaus() public { + bytes32 key = keccak256("multi-key"); + + // Setup CBEAM1 for PAU1 + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + // Setup CBEAM2 for PAU2 + _setupCBeam(address(pau2), CBEAM2); + _setupDefaultRateLimits(key, address(pau2), 2_000 * WAD, 20 * WAD); + _setupRateLimitData(pau2, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + + // CBEAM1 configures PAU1 + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 600 * WAD, 6 * WAD); + + // CBEAM2 configures PAU2 + vm.prank(CBEAM2); + configurator.setRateLimit(address(pau2), key, 1_200 * WAD, 12 * WAD); + + // Verify independent operation + RateLimitsLike.RateLimitData memory data1 = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data2 = pau2.getRateLimitData(key); + + assertEq(data1.maxAmount, 600 * WAD, "PAU1 should have its own limits"); + assertEq(data2.maxAmount, 1_200 * WAD, "PAU2 should have its own limits"); + + // CBEAM1 cannot configure PAU2 + vm.prank(CBEAM1); + vm.expectRevert("Configurator/not-authorized-cBeam"); + configurator.setRateLimit(address(pau2), key, 1_500 * WAD, 15 * WAD); + } + + function testIncreaseThenDecreaseThenIncrease() public { + bytes32 key = keccak256("toggle-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + beamState.setHop(address(pau1), 3_600); + + vm.warp(block.timestamp + 3_600); // Warp to allow first increase + + // Increase beyond defaults (sets zzz) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_100 * WAD, 11 * WAD); + uint256 zzzAfterIncrease = configurator.zzz(address(pau1), key); + assertGt(zzzAfterIncrease, 0, "zzz should be set after increase"); + + // Decrease (doesn't update zzz for the decrease itself, but allows future increases) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 900 * WAD, 9 * WAD); + assertEq(configurator.zzz(address(pau1), key), zzzAfterIncrease, "zzz should not change on decrease"); + + // Immediate increase (within defaults now) should work + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 10 * WAD); + + // Increase beyond defaults should respect hop from first increase + vm.prank(CBEAM1); + vm.expectRevert("Configurator/increment-too-soon"); + configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + + // After hop, increase should work + vm.warp(block.timestamp + 3_600); + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 1_200 * WAD, "should be able to increase after hop"); + } + + function testEdgeCaseZeroValues() public { + bytes32 key = keccak256("zero-key"); + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + // Set to zero (should be allowed as it's decreasing) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key, 0, 0); + + RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + assertEq(data.maxAmount, 0, "maxAmount can be set to 0"); + assertEq(data.slope, 0, "slope can be set to 0"); + } + + function testDifferentKeysIndependentZzz() public { + bytes32 key1 = keccak256("key-1"); + bytes32 key2 = keccak256("key-2"); + + _setupCBeam(address(pau1), CBEAM1); + _setupDefaultRateLimits(key1, address(pau1), 1_000 * WAD, 10 * WAD); + _setupDefaultRateLimits(key2, address(pau1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(pau1, key1, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + _setupRateLimitData(pau1, key2, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + beamState.setHop(address(pau1), 3_600); + + vm.warp(block.timestamp + 3_600); // Warp to allow first increase + + // Increase key1 beyond defaults + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key1, 1_100 * WAD, 11 * WAD); + + // Increase key2 immediately should work (different key) + vm.prank(CBEAM1); + configurator.setRateLimit(address(pau1), key2, 1_100 * WAD, 11 * WAD); + + // But can't increase key1 again immediately + vm.prank(CBEAM1); + vm.expectRevert("Configurator/increment-too-soon"); + configurator.setRateLimit(address(pau1), key1, 1_200 * WAD, 12 * WAD); + + assertGt(configurator.zzz(address(pau1), key1), 0, "key1 should have zzz set"); + assertGt(configurator.zzz(address(pau1), key2), 0, "key2 should have zzz set"); + } +} From d39ec4a6675a09906249c604d52734d930d16261 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 9 Jan 2026 12:47:36 -0300 Subject: [PATCH 028/150] Fix unlimited conditional in getInitRateLimits --- src/BeamState.sol | 3 +-- test/BeamState.t.sol | 30 ++++++++++++++++++------------ test/Configurator.t.sol | 8 ++++---- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 99cc16c..116adc6 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -83,8 +83,7 @@ contract BeamState { function getInitRateLimits(bytes32 key, address pau) external view returns (DefaultRateLimits memory defaultRateLimits) { defaultRateLimits = initRateLimits[key][pau]; - // TODO: fix the next conditional to use "&&" instead of "||" as otherwise you can not set unlimited rate limit for a specific pau, tests also need to be revisited due to this change - if (defaultRateLimits.maxAmount == 0 || defaultRateLimits.slope == 0) { + if (defaultRateLimits.maxAmount == 0 && defaultRateLimits.slope == 0) { // If not set for specific pau, check in general defaultRateLimits = initRateLimits[key][address(0)]; } diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 22c1737..eb5003e 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -401,13 +401,19 @@ contract BeamStateTest is DssTest { // Set global default beamState.addInitRateLimits(key, address(0), 100 * WAD, 1 * WAD); - // Set only maxAmount for PAU1 + // Set specific values for PAU1 (with slope = 0 for unlimited) beamState.addInitRateLimits(key, PAU1, 200 * WAD, 0); - // Should fallback to global because slope is 0 + // Should NOT fallback because maxAmount is non-zero (only falls back when BOTH are 0) BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); - assertEq(limits.maxAmount, 100 * WAD, "should fallback to global maxAmount"); - assertEq(limits.slope, 1 * WAD, "should fallback to global slope"); + assertEq(limits.maxAmount, 200 * WAD, "should return PAU1 specific maxAmount"); + assertEq(limits.slope, 0, "should return PAU1 specific slope (unlimited)"); + + // Test that fallback only happens when BOTH are zero + beamState.addInitRateLimits(key, PAU2, 0, 0); + limits = beamState.getInitRateLimits(key, PAU2); + assertEq(limits.maxAmount, 100 * WAD, "should fallback to global when both are 0"); + assertEq(limits.slope, 1 * WAD, "should fallback to global when both are 0"); } function testAddInitRateLimitsRoleAuth() public { @@ -613,19 +619,19 @@ contract BeamStateTest is DssTest { function testUnlimitedRateLimitConfiguration() public { bytes32 key = keccak256("unlimited-key"); - // Set unlimited as global default first (since getInitRateLimits has fallback logic for slope == 0) + // Set unlimited as global default beamState.addInitRateLimits(key, address(0), type(uint256).max, 0); - // Now set for specific PAU - beamState.addInitRateLimits(key, PAU1, type(uint256).max, 1); + // Now set unlimited for specific PAU (this now works with the fix!) + beamState.addInitRateLimits(key, PAU1, type(uint256).max, 0); BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); - assertEq(limits.maxAmount, type(uint256).max, "maxAmount should be unlimited"); - assertEq(limits.slope, 1, "slope should be 1"); + assertEq(limits.maxAmount, type(uint256).max, "PAU1 maxAmount should be unlimited"); + assertEq(limits.slope, 0, "PAU1 slope should be 0 for unlimited"); - // Test that global unlimited also works + // Test that global unlimited also works for PAU2 (fallback) limits = beamState.getInitRateLimits(key, PAU2); - assertEq(limits.maxAmount, type(uint256).max, "global maxAmount should be unlimited"); - assertEq(limits.slope, 0, "global slope should be 0 for unlimited"); + assertEq(limits.maxAmount, type(uint256).max, "PAU2 should fallback to global unlimited maxAmount"); + assertEq(limits.slope, 0, "PAU2 should fallback to global slope of 0 for unlimited"); } } diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 4801d38..d8558e5 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -341,8 +341,8 @@ contract ConfiguratorTest is DssTest { function testSetUnlimitedRateLimit() public { bytes32 key = keccak256("unlimited-key"); _setupCBeam(address(pau1), CBEAM1); - // Must set at global level due to getInitRateLimits fallback logic when slope == 0 - _setupDefaultRateLimits(key, address(0), type(uint256).max, 0); + // Set unlimited for specific PAU + _setupDefaultRateLimits(key, address(pau1), type(uint256).max, 0); _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); vm.prank(CBEAM1); @@ -360,8 +360,8 @@ contract ConfiguratorTest is DssTest { _setupCBeam(address(pau1), CBEAM1); _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); - // Set defaults that allow unlimited (must be at global level due to fallback logic) - _setupDefaultRateLimits(key, address(0), type(uint256).max, 0); + // Set defaults that allow unlimited for specific PAU + _setupDefaultRateLimits(key, address(pau1), type(uint256).max, 0); // Any value passed should result in unlimited due to special unlimited check vm.prank(CBEAM1); From 138c0d3718cf85cee027a8719e46b430f054ce20 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 9 Jan 2026 13:47:45 -0300 Subject: [PATCH 029/150] Decouple pauCBeams mapping in two for controllers and rateLimits --- src/BeamState.sol | 116 +++++----- src/Configurator.sol | 44 ++-- test/BeamState.t.sol | 358 ++++++++++++++++++------------- test/Configurator.t.sol | 461 ++++++++++++++++++++-------------------- 4 files changed, 529 insertions(+), 450 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 116adc6..924d60e 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -5,15 +5,16 @@ contract BeamState { // --- Storage variables --- - mapping(address usr => uint256 allowed) public wards; - mapping(address usr => bytes32 rolesData) public userRoles; - mapping(bytes4 sig => bytes32 rolesData) public actionsRoles; - mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => true - mapping(address pau => mapping(address cBeam => uint256 allowed)) public pauCBeams; // allowed == 0 => false, allowed == 1 => true - mapping(bytes32 key => mapping(address pau => DefaultRateLimits limit)) public initRateLimits; // pau == address(0) every pau allowed - mapping(bytes32 key => mapping(address pau => bool allowed)) public initControllerActions; // pau == address(0) every pau allowed - mapping(address pau => uint256 value) public hop; // pau == address(0) => general backup configuration - mapping(address pau => uint256 value) public maxChange; // pau == address(0) => general backup configuration + mapping(address usr => uint256 allowed) public wards; + mapping(address usr => bytes32 rolesData) public userRoles; + mapping(bytes4 sig => bytes32 rolesData) public actionsRoles; + mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => true + mapping(address controller => mapping(address cBeam => uint256 allowed)) public controllersCBeams; // allowed == 0 => false, allowed == 1 => true + mapping(address rateLimits => mapping(address cBeam => uint256 allowed)) public rateLimitsCBeams; // allowed == 0 => false, allowed == 1 => true + mapping(bytes32 key => mapping(address rateLimits => DefaultRateLimits limit)) public initRateLimits; // rateLimits == address(0) every rateLimits allowed + mapping(bytes32 key => mapping(address rateLimits => bool allowed)) public initControllerActions; // rateLimits == address(0) every rateLimits allowed + mapping(address rateLimits => uint256 value) public hop; // rateLimits == address(0) => general backup configuration + mapping(address rateLimits => uint256 value) public maxChange; // rateLimits == address(0) => general backup configuration struct DefaultRateLimits { uint256 maxAmount; @@ -30,16 +31,18 @@ contract BeamState { event Deny(address indexed usr); event SetUserRole(address indexed who, uint8 indexed role, bool enabled); event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); - event SetHop(address indexed pau, uint256 value); - event SetMaxChange(address indexed pau, uint256 value); + event SetHop(address indexed rateLimits, uint256 value); + event SetMaxChange(address indexed rateLimits, uint256 value); event AddCBeam(address indexed cBeam); event DelCBeam(address indexed cBeam); - event SetCBeamForPau(address indexed pau, address indexed cBeam); - event UnsetCBeamForPau(address indexed pau, address indexed cBeam); - event AddInitRateLimits(bytes32 indexed key, address indexed pau, uint256 maxAmount, uint256 slope); - event DelInitRateLimits(bytes32 indexed key, address indexed pau); - event AddInitControllerActions(bytes32 indexed key, address indexed pau); - event DelInitControllerActions(bytes32 indexed key, address indexed pau); + event SetCBeamForController(address indexed controller, address indexed cBeam); + event UnsetCBeamForController(address indexed controller, address indexed cBeam); + event SetCBeamForRateLimits(address indexed rateLimits, address indexed cBeam); + event UnsetCBeamForRateLimits(address indexed rateLimits, address indexed cBeam); + event AddInitRateLimits(bytes32 indexed key, address indexed rateLimits, uint256 maxAmount, uint256 slope); + event DelInitRateLimits(bytes32 indexed key, address indexed rateLimits); + event AddInitControllerActions(bytes32 indexed key, address indexed rateLimits); + event DelInitControllerActions(bytes32 indexed key, address indexed rateLimits); // --- Modifiers --- @@ -73,25 +76,25 @@ contract BeamState { has = actionsRoles[sig] & bytes32(uint256(1) << role) != bytes32(0); } - function getHop(address pau) external view returns (uint256 hop_) { - hop_ = hop[pau]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; + function getHop(address rateLimits) external view returns (uint256 hop_) { + hop_ = hop[rateLimits]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; } - function getMaxChange(address pau) external view returns (uint256 maxChange_) { - maxChange_ = maxChange[pau]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; + function getMaxChange(address rateLimits) external view returns (uint256 maxChange_) { + maxChange_ = maxChange[rateLimits]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; } - function getInitRateLimits(bytes32 key, address pau) external view returns (DefaultRateLimits memory defaultRateLimits) { - defaultRateLimits = initRateLimits[key][pau]; + function getInitRateLimits(bytes32 key, address rateLimits) external view returns (DefaultRateLimits memory defaultRateLimits) { + defaultRateLimits = initRateLimits[key][rateLimits]; if (defaultRateLimits.maxAmount == 0 && defaultRateLimits.slope == 0) { - // If not set for specific pau, check in general + // If not set for specific rateLimits, check in general defaultRateLimits = initRateLimits[key][address(0)]; } } - function isControllerActionEnabled(bytes32 key, address pau) external view returns (bool ok) { - ok = initControllerActions[key][address(0)] || // address(0) enabled for every pau - initControllerActions[key][pau]; + function isControllerActionEnabled(bytes32 key, address controller) external view returns (bool ok) { + ok = initControllerActions[key][address(0)] || // address(0) enabled for every controller + initControllerActions[key][controller]; } // --- Admin functions --- @@ -128,15 +131,15 @@ contract BeamState { // --- Role authed functions --- - function setHop(address pau, uint256 value) external roleAuth { - hop[pau] = value; - emit SetHop(pau, value); + function setHop(address rateLimits, uint256 value) external roleAuth { + hop[rateLimits] = value; + emit SetHop(rateLimits, value); } - function setMaxChange(address pau, uint256 value) external roleAuth { + function setMaxChange(address rateLimits, uint256 value) external roleAuth { require(value >= WAD, "Configurator/maxChange-below-1x"); - maxChange[pau] = value; - emit SetMaxChange(pau, value); + maxChange[rateLimits] = value; + emit SetMaxChange(rateLimits, value); } function addCBeam(address cBeam) external roleAuth { @@ -149,35 +152,46 @@ contract BeamState { emit DelCBeam(cBeam); } - function setCBeamForPau(address pau, address cBeam) external roleAuth { + function setCBeamForController(address controller, address cBeam) external roleAuth { require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); - pauCBeams[pau][cBeam] = 1; - emit SetCBeamForPau(pau, cBeam); + controllersCBeams[controller][cBeam] = 1; + emit SetCBeamForController(controller, cBeam); } - function unsetCBeamForPau(address pau, address cBeam) external roleAuth { - pauCBeams[pau][cBeam] = 0; - emit UnsetCBeamForPau(pau, cBeam); + function unsetCBeamForController(address controller, address cBeam) external roleAuth { + controllersCBeams[controller][cBeam] = 0; + emit UnsetCBeamForController(controller, cBeam); } - function addInitRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) external roleAuth { - initRateLimits[key][pau] = DefaultRateLimits(maxAmount, slope); - emit AddInitRateLimits(key, pau, maxAmount, slope); + function setCBeamForRateLimits(address rateLimits, address cBeam) external roleAuth { + require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); + rateLimitsCBeams[rateLimits][cBeam] = 1; + emit SetCBeamForRateLimits(rateLimits, cBeam); + } + + function unsetCBeamForRateLimits(address rateLimits, address cBeam) external roleAuth { + rateLimitsCBeams[rateLimits][cBeam] = 0; + emit UnsetCBeamForRateLimits(rateLimits, cBeam); + } + + function addInitRateLimits(bytes32 key, address rateLimits, uint256 maxAmount, uint256 slope) external roleAuth { + initRateLimits[key][rateLimits] = DefaultRateLimits(maxAmount, slope); + emit AddInitRateLimits(key, rateLimits, maxAmount, slope); } - function delInitRateLimits(bytes32 key, address pau) external roleAuth { - delete initRateLimits[key][pau]; - emit DelInitRateLimits(key, pau); + function delInitRateLimits(bytes32 key, address rateLimits) external roleAuth { + delete initRateLimits[key][rateLimits]; + emit DelInitRateLimits(key, rateLimits); } - function addInitControllerActions(bytes calldata data, address pau) external roleAuth returns (bytes32 key) { + function addInitControllerActions(bytes calldata data, address controller) external roleAuth returns (bytes32 key) { key = keccak256(data); - initControllerActions[key][pau] = true; - emit AddInitControllerActions(key, pau); + initControllerActions[key][controller] = true; + emit AddInitControllerActions(key, controller); } - function delInitControllerActions(bytes32 key, address pau) external roleAuth { - delete initControllerActions[key][pau]; - emit DelInitControllerActions(key, pau); + function delInitControllerActions(bytes32 key, address controller) external roleAuth { + delete initControllerActions[key][controller]; + emit DelInitControllerActions(key, controller); } } diff --git a/src/Configurator.sol b/src/Configurator.sol index e9077ad..3746a8c 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -18,7 +18,8 @@ interface RateLimitsLike { interface BeamStateLike { function getHop(address) external view returns (uint256); function getMaxChange(address) external view returns (uint256); - function pauCBeams(address, address) external view returns (uint256); + function controllersCBeams(address, address) external view returns (uint256); + function rateLimitsCBeams(address, address) external view returns (uint256); function getInitRateLimits(bytes32, address) external view returns (uint256, uint256); function isControllerActionEnabled(bytes32, address) external view returns (bool); } @@ -27,7 +28,7 @@ contract Configurator { // --- Storage variables --- - mapping(address pau => mapping(bytes32 key => uint256 timestamp)) public zzz; + mapping(address rateLimits => mapping(bytes32 key => uint256 timestamp)) public zzz; // --- Immutables --- @@ -39,12 +40,17 @@ contract Configurator { // --- Events --- - event SetRateLimit(address indexed pau, bytes32 indexed key, uint256 maxAmount, uint256 slope); + event SetRateLimit(address indexed rateLimits, bytes32 indexed key, uint256 maxAmount, uint256 slope); // --- Modifiers --- - modifier auth(address pau) { - require(beamState.pauCBeams(pau, msg.sender) == 1, "Configurator/not-authorized-cBeam"); + modifier authController(address controller) { + require(beamState.controllersCBeams(controller, msg.sender) == 1, "Configurator/not-authorized-controller-cBeam"); + _; + } + + modifier authRateLimits(address rateLimits) { + require(beamState.rateLimitsCBeams(rateLimits, msg.sender) == 1, "Configurator/not-authorized-ratelimits-cBeam"); _; } @@ -62,31 +68,31 @@ contract Configurator { // cBeams functions - function setRateLimit(address pau, bytes32 key, uint256 maxAmount, uint256 slope) external auth(pau) { - (uint256 defMaxAmount, uint256 defSlope) = beamState.getInitRateLimits(key, pau); + function setRateLimit(address rateLimits, bytes32 key, uint256 maxAmount, uint256 slope) external authRateLimits(rateLimits) { + (uint256 defMaxAmount, uint256 defSlope) = beamState.getInitRateLimits(key, rateLimits); if (defMaxAmount == type(uint256).max && defSlope == 0) { - RateLimitsLike(pau).setUnlimitedRateLimitData(key); - emit SetRateLimit(pau, key, type(uint256).max, 0); + RateLimitsLike(rateLimits).setUnlimitedRateLimitData(key); + emit SetRateLimit(rateLimits, key, type(uint256).max, 0); } else { - RateLimitsLike.RateLimitData memory current = RateLimitsLike(pau).getRateLimitData(key); + RateLimitsLike.RateLimitData memory current = RateLimitsLike(rateLimits).getRateLimitData(key); bool safe = maxAmount <= defMaxAmount && slope <= defSlope || maxAmount <= current.maxAmount && slope <= current.slope; - uint256 maxChange = beamState.getMaxChange(pau); - require(safe || block.timestamp >= zzz[pau][key] + beamState.getHop(pau), "Configurator/increment-too-soon"); + uint256 maxChange = beamState.getMaxChange(rateLimits); + require(safe || block.timestamp >= zzz[rateLimits][key] + beamState.getHop(rateLimits), "Configurator/increment-too-soon"); require(safe || maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD require(safe || slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); if (maxAmount >= current.maxAmount || slope >= current.slope) { - zzz[pau][key] = block.timestamp; + zzz[rateLimits][key] = block.timestamp; } - uint256 lastAmount = RateLimitsLike(pau).getCurrentRateLimit(key); - RateLimitsLike(pau).setRateLimitData(key, maxAmount, slope, _min(maxAmount, lastAmount), block.timestamp); - emit SetRateLimit(pau, key, maxAmount, slope); + uint256 lastAmount = RateLimitsLike(rateLimits).getCurrentRateLimit(key); + RateLimitsLike(rateLimits).setRateLimitData(key, maxAmount, slope, _min(maxAmount, lastAmount), block.timestamp); + emit SetRateLimit(rateLimits, key, maxAmount, slope); } } - function callControllerAction(address pau, bytes calldata data) external auth(pau) returns (bytes memory ret) { - require(beamState.isControllerActionEnabled(keccak256(data), pau), "Configurator/not-valid-data"); + function callControllerAction(address controller, bytes calldata data) external authController(controller) returns (bytes memory ret) { + require(beamState.isControllerActionEnabled(keccak256(data), controller), "Configurator/not-valid-data"); bool ok; - (ok, ret) = pau.call(data); + (ok, ret) = controller.call(data); require(ok, "Configurator/call-failed"); } } diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index eb5003e..05366ad 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -12,21 +12,23 @@ contract BeamStateTest is DssTest { address constant USER2 = address(0x2); address constant CBEAM1 = address(0xC1); address constant CBEAM2 = address(0xC2); - address constant PAU1 = address(0xA1); - address constant PAU2 = address(0xA2); + address constant TARGET1 = address(0xA1); + address constant TARGET2 = address(0xA2); event SetUserRole(address indexed who, uint8 indexed role, bool enabled); event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); - event SetHop(address indexed pau, uint256 value); - event SetMaxChange(address indexed pau, uint256 value); + event SetHop(address indexed rateLimits, uint256 value); + event SetMaxChange(address indexed rateLimits, uint256 value); event AddCBeam(address indexed cBeam); event DelCBeam(address indexed cBeam); - event SetCBeamForPau(address indexed pau, address indexed cBeam); - event UnsetCBeamForPau(address indexed pau, address indexed cBeam); - event AddInitRateLimits(bytes32 indexed key, address indexed pau, uint256 maxAmount, uint256 slope); - event DelInitRateLimits(bytes32 indexed key, address indexed pau); - event AddInitControllerActions(bytes32 indexed key, address indexed pau); - event DelInitControllerActions(bytes32 indexed key, address indexed pau); + event SetCBeamForController(address indexed controller, address indexed cBeam); + event UnsetCBeamForController(address indexed controller, address indexed cBeam); + event SetCBeamForRateLimits(address indexed rateLimits, address indexed cBeam); + event UnsetCBeamForRateLimits(address indexed rateLimits, address indexed cBeam); + event AddInitRateLimits(bytes32 indexed key, address indexed rateLimits, uint256 maxAmount, uint256 slope); + event DelInitRateLimits(bytes32 indexed key, address indexed rateLimits); + event AddInitControllerActions(bytes32 indexed key, address indexed rateLimits); + event DelInitControllerActions(bytes32 indexed key, address indexed rateLimits); function setUp() public { beamState = new BeamState(); @@ -130,8 +132,8 @@ contract BeamStateTest is DssTest { function testRoleAuthWithWard() public { // Ward can call without role - beamState.setHop(PAU1, 100); - assertEq(beamState.hop(PAU1), 100, "hop should be set by ward"); + beamState.setHop(TARGET1, 100); + assertEq(beamState.hop(TARGET1), 100, "hop should be set by ward"); } function testRoleAuthWithRole() public { @@ -141,14 +143,14 @@ contract BeamStateTest is DssTest { // User with role can call vm.prank(USER1); - beamState.setHop(PAU1, 200); - assertEq(beamState.hop(PAU1), 200, "hop should be set by role user"); + beamState.setHop(TARGET1, 200); + assertEq(beamState.hop(TARGET1), 200, "hop should be set by role user"); } function testRoleAuthWithoutRoleOrWard() public { vm.prank(USER1); vm.expectRevert("BeamState/role-not-authorized"); - beamState.setHop(PAU1, 100); + beamState.setHop(TARGET1, 100); } function testRoleAuthWithWrongRole() public { @@ -159,18 +161,18 @@ contract BeamStateTest is DssTest { vm.prank(USER1); vm.expectRevert("BeamState/role-not-authorized"); - beamState.setHop(PAU1, 100); + beamState.setHop(TARGET1, 100); } // --- Hop Configuration Tests --- function testSetHop() public { vm.expectEmit(); - emit SetHop(PAU1, 86_400); - beamState.setHop(PAU1, 86_400); + emit SetHop(TARGET1, 86_400); + beamState.setHop(TARGET1, 86_400); - assertEq(beamState.hop(PAU1), 86_400, "hop should be set for PAU1"); - assertEq(beamState.getHop(PAU1), 86_400, "getHop should return correct value"); + assertEq(beamState.hop(TARGET1), 86_400, "hop should be set for TARGET1"); + assertEq(beamState.getHop(TARGET1), 86_400, "getHop should return correct value"); } function testSetHopGlobal() public { @@ -182,13 +184,13 @@ contract BeamStateTest is DssTest { function testGetHopFallback() public { beamState.setHop(address(0), 7_200); - // PAU1 has no specific hop, should fallback to global - assertEq(beamState.hop(PAU1), 0, "PAU1 hop storage should be 0"); - assertEq(beamState.getHop(PAU1), 7_200, "getHop should return global fallback"); + // TARGET1 has no specific hop, should fallback to global + assertEq(beamState.hop(TARGET1), 0, "TARGET1 hop storage should be 0"); + assertEq(beamState.getHop(TARGET1), 7_200, "getHop should return global fallback"); - // Set specific hop for PAU1 - beamState.setHop(PAU1, 14_400); - assertEq(beamState.getHop(PAU1), 14_400, "getHop should return PAU1 specific value"); + // Set specific hop for TARGET1 + beamState.setHop(TARGET1, 14_400); + assertEq(beamState.getHop(TARGET1), 14_400, "getHop should return TARGET1 specific value"); } function testSetHopRoleAuth() public { @@ -196,30 +198,30 @@ contract BeamStateTest is DssTest { beamState.setUserRole(USER1, 1, true); vm.prank(USER1); - beamState.setHop(PAU1, 1_000); + beamState.setHop(TARGET1, 1_000); - assertEq(beamState.hop(PAU1), 1_000, "hop should be set by role user"); + assertEq(beamState.hop(TARGET1), 1_000, "hop should be set by role user"); } // --- MaxChange Configuration Tests --- function testSetMaxChange() public { vm.expectEmit(); - emit SetMaxChange(PAU1, 2 * WAD); - beamState.setMaxChange(PAU1, 2 * WAD); + emit SetMaxChange(TARGET1, 2 * WAD); + beamState.setMaxChange(TARGET1, 2 * WAD); - assertEq(beamState.maxChange(PAU1), 2 * WAD, "maxChange should be set for PAU1"); - assertEq(beamState.getMaxChange(PAU1), 2 * WAD, "getMaxChange should return correct value"); + assertEq(beamState.maxChange(TARGET1), 2 * WAD, "maxChange should be set for TARGET1"); + assertEq(beamState.getMaxChange(TARGET1), 2 * WAD, "getMaxChange should return correct value"); } function testSetMaxChangeMinimumWad() public { - beamState.setMaxChange(PAU1, WAD); - assertEq(beamState.maxChange(PAU1), WAD, "maxChange should be WAD"); + beamState.setMaxChange(TARGET1, WAD); + assertEq(beamState.maxChange(TARGET1), WAD, "maxChange should be WAD"); } function testSetMaxChangeBelowWad() public { vm.expectRevert("Configurator/maxChange-below-1x"); - beamState.setMaxChange(PAU1, WAD - 1); + beamState.setMaxChange(TARGET1, WAD - 1); } function testSetMaxChangeGlobal() public { @@ -231,13 +233,13 @@ contract BeamStateTest is DssTest { function testGetMaxChangeFallback() public { beamState.setMaxChange(address(0), 2 * WAD); - // PAU1 has no specific maxChange, should fallback to global - assertEq(beamState.maxChange(PAU1), 0, "PAU1 maxChange storage should be 0"); - assertEq(beamState.getMaxChange(PAU1), 2 * WAD, "getMaxChange should return global fallback"); + // TARGET1 has no specific maxChange, should fallback to global + assertEq(beamState.maxChange(TARGET1), 0, "TARGET1 maxChange storage should be 0"); + assertEq(beamState.getMaxChange(TARGET1), 2 * WAD, "getMaxChange should return global fallback"); - // Set specific maxChange for PAU1 - beamState.setMaxChange(PAU1, 3 * WAD); - assertEq(beamState.getMaxChange(PAU1), 3 * WAD, "getMaxChange should return PAU1 specific value"); + // Set specific maxChange for TARGET1 + beamState.setMaxChange(TARGET1, 3 * WAD); + assertEq(beamState.getMaxChange(TARGET1), 3 * WAD, "getMaxChange should return TARGET1 specific value"); } function testSetMaxChangeRoleAuth() public { @@ -245,9 +247,9 @@ contract BeamStateTest is DssTest { beamState.setUserRole(USER1, 2, true); vm.prank(USER1); - beamState.setMaxChange(PAU1, 2 * WAD); + beamState.setMaxChange(TARGET1, 2 * WAD); - assertEq(beamState.maxChange(PAU1), 2 * WAD, "maxChange should be set by role user"); + assertEq(beamState.maxChange(TARGET1), 2 * WAD, "maxChange should be set by role user"); } // --- cBeam Management Tests --- @@ -295,60 +297,116 @@ contract BeamStateTest is DssTest { assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should be deleted by role user"); } - // --- cBeam-PAU Mapping Tests --- + // --- cBeam-Controller Mapping Tests --- - function testSetCBeamForPau() public { + function testSetCBeamForController() public { beamState.addCBeam(CBEAM1); - assertEq(beamState.pauCBeams(PAU1, CBEAM1), 0, "CBEAM1 should not be authorized for PAU1 initially"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 initially"); vm.expectEmit(); - emit SetCBeamForPau(PAU1, CBEAM1); - beamState.setCBeamForPau(PAU1, CBEAM1); + emit SetCBeamForController(TARGET1, CBEAM1); + beamState.setCBeamForController(TARGET1, CBEAM1); - assertEq(beamState.pauCBeams(PAU1, CBEAM1), 1, "CBEAM1 should be authorized for PAU1"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); } - function testSetCBeamForPauNotExisting() public { + function testSetCBeamForControllerNotExisting() public { vm.expectRevert("BeamState/not-existing-cBeam"); - beamState.setCBeamForPau(PAU1, CBEAM1); + beamState.setCBeamForController(TARGET1, CBEAM1); } - function testSetCBeamForPauRoleAuth() public { + function testSetCBeamForControllerRoleAuth() public { beamState.addCBeam(CBEAM1); - beamState.setRoleAction(4, beamState.setCBeamForPau.selector, true); + beamState.setRoleAction(4, beamState.setCBeamForController.selector, true); beamState.setUserRole(USER1, 4, true); vm.prank(USER1); - beamState.setCBeamForPau(PAU1, CBEAM1); + beamState.setCBeamForController(TARGET1, CBEAM1); - assertEq(beamState.pauCBeams(PAU1, CBEAM1), 1, "CBEAM1 should be set for PAU1 by role user"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be set for TARGET1 by role user"); } - function testUnsetCBeamForPau() public { + function testUnsetCBeamForController() public { beamState.addCBeam(CBEAM1); - beamState.setCBeamForPau(PAU1, CBEAM1); - assertEq(beamState.pauCBeams(PAU1, CBEAM1), 1, "CBEAM1 should be authorized for PAU1"); + beamState.setCBeamForController(TARGET1, CBEAM1); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); vm.expectEmit(); - emit UnsetCBeamForPau(PAU1, CBEAM1); - beamState.unsetCBeamForPau(PAU1, CBEAM1); + emit UnsetCBeamForController(TARGET1, CBEAM1); + beamState.unsetCBeamForController(TARGET1, CBEAM1); - assertEq(beamState.pauCBeams(PAU1, CBEAM1), 0, "CBEAM1 should not be authorized for PAU1 after unset"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 after unset"); } - function testUnsetCBeamForPauRoleAuth() public { + function testUnsetCBeamForControllerRoleAuth() public { beamState.addCBeam(CBEAM1); - beamState.setCBeamForPau(PAU1, CBEAM1); + beamState.setCBeamForController(TARGET1, CBEAM1); - beamState.setRoleAction(4, beamState.unsetCBeamForPau.selector, true); + beamState.setRoleAction(4, beamState.unsetCBeamForController.selector, true); beamState.setUserRole(USER1, 4, true); vm.prank(USER1); - beamState.unsetCBeamForPau(PAU1, CBEAM1); + beamState.unsetCBeamForController(TARGET1, CBEAM1); - assertEq(beamState.pauCBeams(PAU1, CBEAM1), 0, "CBEAM1 should be unset for PAU1 by role user"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should be unset for TARGET1 by role user"); + } + + // --- cBeam-RateLimits Mapping Tests --- + + function testSetCBeamForRateLimits() public { + beamState.addCBeam(CBEAM1); + + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 initially"); + + vm.expectEmit(); + emit SetCBeamForRateLimits(TARGET1, CBEAM1); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); + } + + function testSetCBeamForRateLimitsNotExisting() public { + vm.expectRevert("BeamState/not-existing-cBeam"); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + } + + function testSetCBeamForRateLimitsRoleAuth() public { + beamState.addCBeam(CBEAM1); + + beamState.setRoleAction(4, beamState.setCBeamForRateLimits.selector, true); + beamState.setUserRole(USER1, 4, true); + + vm.prank(USER1); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be set for TARGET1 by role user"); + } + + function testUnsetCBeamForRateLimits() public { + beamState.addCBeam(CBEAM1); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); + + vm.expectEmit(); + emit UnsetCBeamForRateLimits(TARGET1, CBEAM1); + beamState.unsetCBeamForRateLimits(TARGET1, CBEAM1); + + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 after unset"); + } + + function testUnsetCBeamForRateLimitsRoleAuth() public { + beamState.addCBeam(CBEAM1); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + + beamState.setRoleAction(4, beamState.unsetCBeamForRateLimits.selector, true); + beamState.setUserRole(USER1, 4, true); + + vm.prank(USER1); + beamState.unsetCBeamForRateLimits(TARGET1, CBEAM1); + + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should be unset for TARGET1 by role user"); } // --- Rate Limits Tests --- @@ -357,10 +415,10 @@ contract BeamStateTest is DssTest { bytes32 key = keccak256("test-key"); vm.expectEmit(); - emit AddInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); - beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); + emit AddInitRateLimits(key, TARGET1, 1_000 * WAD, 10 * WAD); + beamState.addInitRateLimits(key, TARGET1, 1_000 * WAD, 10 * WAD); - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, TARGET1); assertEq(limits.maxAmount, 1_000 * WAD, "maxAmount should be set"); assertEq(limits.slope, 10 * WAD, "slope should be set"); } @@ -381,18 +439,18 @@ contract BeamStateTest is DssTest { // Set global default beamState.addInitRateLimits(key, address(0), 100 * WAD, 1 * WAD); - // Get for specific PAU should return global - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + // Get for specific TARGET should return global + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, TARGET1); assertEq(limits.maxAmount, 100 * WAD, "should return global maxAmount"); assertEq(limits.slope, 1 * WAD, "should return global slope"); - // Set specific for PAU1 - beamState.addInitRateLimits(key, PAU1, 200 * WAD, 2 * WAD); + // Set specific for TARGET1 + beamState.addInitRateLimits(key, TARGET1, 200 * WAD, 2 * WAD); - // Should now return PAU1 specific - limits = beamState.getInitRateLimits(key, PAU1); - assertEq(limits.maxAmount, 200 * WAD, "should return PAU1 specific maxAmount"); - assertEq(limits.slope, 2 * WAD, "should return PAU1 specific slope"); + // Should now return TARGET1 specific + limits = beamState.getInitRateLimits(key, TARGET1); + assertEq(limits.maxAmount, 200 * WAD, "should return TARGET1 specific maxAmount"); + assertEq(limits.slope, 2 * WAD, "should return TARGET1 specific slope"); } function testGetInitRateLimitsPartialFallback() public { @@ -401,17 +459,17 @@ contract BeamStateTest is DssTest { // Set global default beamState.addInitRateLimits(key, address(0), 100 * WAD, 1 * WAD); - // Set specific values for PAU1 (with slope = 0 for unlimited) - beamState.addInitRateLimits(key, PAU1, 200 * WAD, 0); + // Set specific values for TARGET1 (with slope = 0 for unlimited) + beamState.addInitRateLimits(key, TARGET1, 200 * WAD, 0); // Should NOT fallback because maxAmount is non-zero (only falls back when BOTH are 0) - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); - assertEq(limits.maxAmount, 200 * WAD, "should return PAU1 specific maxAmount"); - assertEq(limits.slope, 0, "should return PAU1 specific slope (unlimited)"); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, TARGET1); + assertEq(limits.maxAmount, 200 * WAD, "should return TARGET1 specific maxAmount"); + assertEq(limits.slope, 0, "should return TARGET1 specific slope (unlimited)"); // Test that fallback only happens when BOTH are zero - beamState.addInitRateLimits(key, PAU2, 0, 0); - limits = beamState.getInitRateLimits(key, PAU2); + beamState.addInitRateLimits(key, TARGET2, 0, 0); + limits = beamState.getInitRateLimits(key, TARGET2); assertEq(limits.maxAmount, 100 * WAD, "should fallback to global when both are 0"); assertEq(limits.slope, 1 * WAD, "should fallback to global when both are 0"); } @@ -422,39 +480,39 @@ contract BeamStateTest is DssTest { beamState.setUserRole(USER1, 5, true); vm.prank(USER1); - beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); + beamState.addInitRateLimits(key, TARGET1, 1_000 * WAD, 10 * WAD); - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, TARGET1); assertEq(limits.maxAmount, 1_000 * WAD, "maxAmount should be set by role user"); } function testDelInitRateLimits() public { bytes32 key = keccak256("del-key"); - beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + beamState.addInitRateLimits(key, TARGET1, 1_000 * WAD, 10 * WAD); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, TARGET1); assertEq(limits.maxAmount, 1_000 * WAD, "maxAmount should be set"); vm.expectEmit(); - emit DelInitRateLimits(key, PAU1); - beamState.delInitRateLimits(key, PAU1); + emit DelInitRateLimits(key, TARGET1); + beamState.delInitRateLimits(key, TARGET1); - limits = beamState.getInitRateLimits(key, PAU1); + limits = beamState.getInitRateLimits(key, TARGET1); assertEq(limits.maxAmount, 0, "maxAmount should be deleted"); assertEq(limits.slope, 0, "slope should be deleted"); } function testDelInitRateLimitsRoleAuth() public { bytes32 key = keccak256("del-auth-key"); - beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); + beamState.addInitRateLimits(key, TARGET1, 1_000 * WAD, 10 * WAD); beamState.setRoleAction(5, beamState.delInitRateLimits.selector, true); beamState.setUserRole(USER1, 5, true); vm.prank(USER1); - beamState.delInitRateLimits(key, PAU1); + beamState.delInitRateLimits(key, TARGET1); - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, TARGET1); assertEq(limits.maxAmount, 0, "maxAmount should be deleted by role user"); } @@ -465,11 +523,11 @@ contract BeamStateTest is DssTest { bytes32 expectedKey = keccak256(data); vm.expectEmit(); - emit AddInitControllerActions(expectedKey, PAU1); - bytes32 returnedKey = beamState.addInitControllerActions(data, PAU1); + emit AddInitControllerActions(expectedKey, TARGET1); + bytes32 returnedKey = beamState.addInitControllerActions(data, TARGET1); assertEq(returnedKey, expectedKey, "returned key should match expected"); - assertTrue(beamState.isControllerActionEnabled(expectedKey, PAU1), "action should be enabled for PAU1"); + assertTrue(beamState.isControllerActionEnabled(expectedKey, TARGET1), "action should be enabled for TARGET1"); } function testAddInitControllerActionsGlobal() public { @@ -479,18 +537,18 @@ contract BeamStateTest is DssTest { beamState.addInitControllerActions(data, address(0)); assertTrue(beamState.isControllerActionEnabled(key, address(0)), "action should be enabled globally"); - assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled for any PAU"); - assertTrue(beamState.isControllerActionEnabled(key, PAU2), "action should be enabled for any PAU"); + assertTrue(beamState.isControllerActionEnabled(key, TARGET1), "action should be enabled for any TARGET"); + assertTrue(beamState.isControllerActionEnabled(key, TARGET2), "action should be enabled for any TARGET"); } function testIsControllerActionEnabledSpecificOverGlobal() public { bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); bytes32 key = keccak256(data); - beamState.addInitControllerActions(data, PAU1); + beamState.addInitControllerActions(data, TARGET1); - assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled for PAU1"); - assertFalse(beamState.isControllerActionEnabled(key, PAU2), "action should not be enabled for PAU2"); + assertTrue(beamState.isControllerActionEnabled(key, TARGET1), "action should be enabled for TARGET1"); + assertFalse(beamState.isControllerActionEnabled(key, TARGET2), "action should not be enabled for TARGET2"); } function testIsControllerActionEnabledBothGlobalAndSpecific() public { @@ -498,10 +556,10 @@ contract BeamStateTest is DssTest { bytes32 key = keccak256(data); beamState.addInitControllerActions(data, address(0)); - beamState.addInitControllerActions(data, PAU1); + beamState.addInitControllerActions(data, TARGET1); - assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled for PAU1"); - assertTrue(beamState.isControllerActionEnabled(key, PAU2), "action should be enabled for PAU2 via global"); + assertTrue(beamState.isControllerActionEnabled(key, TARGET1), "action should be enabled for TARGET1"); + assertTrue(beamState.isControllerActionEnabled(key, TARGET2), "action should be enabled for TARGET2 via global"); } function testAddInitControllerActionsRoleAuth() public { @@ -511,37 +569,37 @@ contract BeamStateTest is DssTest { beamState.setUserRole(USER1, 6, true); vm.prank(USER1); - beamState.addInitControllerActions(data, PAU1); + beamState.addInitControllerActions(data, TARGET1); - assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled by role user"); + assertTrue(beamState.isControllerActionEnabled(key, TARGET1), "action should be enabled by role user"); } function testDelInitControllerActions() public { bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); bytes32 key = keccak256(data); - beamState.addInitControllerActions(data, PAU1); - assertTrue(beamState.isControllerActionEnabled(key, PAU1), "action should be enabled"); + beamState.addInitControllerActions(data, TARGET1); + assertTrue(beamState.isControllerActionEnabled(key, TARGET1), "action should be enabled"); vm.expectEmit(); - emit DelInitControllerActions(key, PAU1); - beamState.delInitControllerActions(key, PAU1); + emit DelInitControllerActions(key, TARGET1); + beamState.delInitControllerActions(key, TARGET1); - assertFalse(beamState.isControllerActionEnabled(key, PAU1), "action should be disabled after delete"); + assertFalse(beamState.isControllerActionEnabled(key, TARGET1), "action should be disabled after delete"); } function testDelInitControllerActionsRoleAuth() public { bytes memory data = abi.encodeWithSignature("testFunction(uint256)", 12345); bytes32 key = keccak256(data); - beamState.addInitControllerActions(data, PAU1); + beamState.addInitControllerActions(data, TARGET1); beamState.setRoleAction(6, beamState.delInitControllerActions.selector, true); beamState.setUserRole(USER1, 6, true); vm.prank(USER1); - beamState.delInitControllerActions(key, PAU1); + beamState.delInitControllerActions(key, TARGET1); - assertFalse(beamState.isControllerActionEnabled(key, PAU1), "action should be disabled by role user"); + assertFalse(beamState.isControllerActionEnabled(key, TARGET1), "action should be disabled by role user"); } // --- Combined functions Tests --- @@ -556,7 +614,7 @@ contract BeamStateTest is DssTest { beamState.setRoleAction(1, hopSig, true); // Role 2: Can manage cBeams beamState.setRoleAction(2, cBeamSig, true); - beamState.setRoleAction(2, beamState.setCBeamForPau.selector, true); + beamState.setRoleAction(2, beamState.setCBeamForRateLimits.selector, true); // Role 3: Can set rate limits beamState.setRoleAction(3, limitsSig, true); @@ -567,24 +625,24 @@ contract BeamStateTest is DssTest { // USER1 sets hop vm.prank(USER1); - beamState.setHop(PAU1, 3_600); - assertEq(beamState.hop(PAU1), 3_600, "USER1 should be able to set hop"); + beamState.setHop(TARGET1, 3_600); + assertEq(beamState.hop(TARGET1), 3_600, "USER1 should be able to set hop"); // USER2 adds cBeam vm.prank(USER2); beamState.addCBeam(CBEAM1); assertEq(beamState.cBeams(CBEAM1), 1, "USER2 should be able to add cBeam"); - // USER2 sets cBeam for PAU + // USER2 sets cBeam for rateLimits vm.prank(USER2); - beamState.setCBeamForPau(PAU1, CBEAM1); - assertEq(beamState.pauCBeams(PAU1, CBEAM1), 1, "USER2 should be able to set cBeam for PAU"); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "USER2 should be able to set rate limits cBeam for TARGET"); // USER2 adds rate limits bytes32 key = keccak256("integration-key"); vm.prank(USER2); - beamState.addInitRateLimits(key, PAU1, 1_000 * WAD, 10 * WAD); - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); + beamState.addInitRateLimits(key, TARGET1, 1_000 * WAD, 10 * WAD); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, TARGET1); assertEq(limits.maxAmount, 1_000 * WAD, "USER2 should be able to add rate limits"); // USER1 cannot add cBeam (wrong role) @@ -593,27 +651,27 @@ contract BeamStateTest is DssTest { beamState.addCBeam(CBEAM2); } - function testMultiplePauConfiguration() public { - bytes32 key = keccak256("multi-pau-key"); + function testMultipleTargetConfiguration() public { + bytes32 key = keccak256("multi-target-key"); - // Configure different settings for different PAUs - beamState.setHop(PAU1, 1_000); - beamState.setHop(PAU2, 2_000); - beamState.setMaxChange(PAU1, 2 * WAD); - beamState.setMaxChange(PAU2, 3 * WAD); - beamState.addInitRateLimits(key, PAU1, 100 * WAD, 1 * WAD); - beamState.addInitRateLimits(key, PAU2, 200 * WAD, 2 * WAD); + // Configure different settings for different TARGETs + beamState.setHop(TARGET1, 1_000); + beamState.setHop(TARGET2, 2_000); + beamState.setMaxChange(TARGET1, 2 * WAD); + beamState.setMaxChange(TARGET2, 3 * WAD); + beamState.addInitRateLimits(key, TARGET1, 100 * WAD, 1 * WAD); + beamState.addInitRateLimits(key, TARGET2, 200 * WAD, 2 * WAD); - // Verify each PAU has its own configuration - assertEq(beamState.getHop(PAU1), 1_000, "PAU1 hop should be 1_000"); - assertEq(beamState.getHop(PAU2), 2_000, "PAU2 hop should be 2_000"); - assertEq(beamState.getMaxChange(PAU1), 2 * WAD, "PAU1 maxChange should be 2x"); - assertEq(beamState.getMaxChange(PAU2), 3 * WAD, "PAU2 maxChange should be 3x"); + // Verify each TARGET has its own configuration + assertEq(beamState.getHop(TARGET1), 1_000, "TARGET1 hop should be 1_000"); + assertEq(beamState.getHop(TARGET2), 2_000, "TARGET2 hop should be 2_000"); + assertEq(beamState.getMaxChange(TARGET1), 2 * WAD, "TARGET1 maxChange should be 2x"); + assertEq(beamState.getMaxChange(TARGET2), 3 * WAD, "TARGET2 maxChange should be 3x"); - BeamState.DefaultRateLimits memory limits1 = beamState.getInitRateLimits(key, PAU1); - BeamState.DefaultRateLimits memory limits2 = beamState.getInitRateLimits(key, PAU2); - assertEq(limits1.maxAmount, 100 * WAD, "PAU1 maxAmount should be 100"); - assertEq(limits2.maxAmount, 200 * WAD, "PAU2 maxAmount should be 200"); + BeamState.DefaultRateLimits memory limits1 = beamState.getInitRateLimits(key, TARGET1); + BeamState.DefaultRateLimits memory limits2 = beamState.getInitRateLimits(key, TARGET2); + assertEq(limits1.maxAmount, 100 * WAD, "TARGET1 maxAmount should be 100"); + assertEq(limits2.maxAmount, 200 * WAD, "TARGET2 maxAmount should be 200"); } function testUnlimitedRateLimitConfiguration() public { @@ -622,16 +680,16 @@ contract BeamStateTest is DssTest { // Set unlimited as global default beamState.addInitRateLimits(key, address(0), type(uint256).max, 0); - // Now set unlimited for specific PAU (this now works with the fix!) - beamState.addInitRateLimits(key, PAU1, type(uint256).max, 0); + // Now set unlimited for specific TARGET (this now works with the fix!) + beamState.addInitRateLimits(key, TARGET1, type(uint256).max, 0); - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, PAU1); - assertEq(limits.maxAmount, type(uint256).max, "PAU1 maxAmount should be unlimited"); - assertEq(limits.slope, 0, "PAU1 slope should be 0 for unlimited"); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, TARGET1); + assertEq(limits.maxAmount, type(uint256).max, "TARGET1 maxAmount should be unlimited"); + assertEq(limits.slope, 0, "TARGET1 slope should be 0 for unlimited"); - // Test that global unlimited also works for PAU2 (fallback) - limits = beamState.getInitRateLimits(key, PAU2); - assertEq(limits.maxAmount, type(uint256).max, "PAU2 should fallback to global unlimited maxAmount"); - assertEq(limits.slope, 0, "PAU2 should fallback to global slope of 0 for unlimited"); + // Test that global unlimited also works for TARGET2 (fallback) + limits = beamState.getInitRateLimits(key, TARGET2); + assertEq(limits.maxAmount, type(uint256).max, "TARGET2 should fallback to global unlimited maxAmount"); + assertEq(limits.slope, 0, "TARGET2 should fallback to global slope of 0 for unlimited"); } } diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index d8558e5..2779f3b 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -5,8 +5,8 @@ import "dss-test/DssTest.sol"; import { Configurator, BeamStateLike, RateLimitsLike } from "../src/Configurator.sol"; import { BeamState } from "../src/BeamState.sol"; -// Mock PAU contract implementing RateLimits interface -contract MockPau { +// Mock TARGET contract implementing RateLimits interface +contract MockTarget { RateLimitsLike.RateLimitData public rateLimitData; uint256 public currentRateLimit; bool public shouldFail; @@ -45,13 +45,13 @@ contract MockPau { // Mock controller function for testing callControllerAction function controllerFunction(uint256 value) external returns (uint256) { lastCallData = msg.data; - require(!shouldFail, "MockPau/controller-function-failed"); + require(!shouldFail, "MockTarget/controller-function-failed"); return value * 2; } fallback() external { lastCallData = msg.data; - require(!shouldFail, "MockPau/fallback-failed"); + require(!shouldFail, "MockTarget/fallback-failed"); } } @@ -59,21 +59,21 @@ contract ConfiguratorTest is DssTest { Configurator configurator; BeamState beamState; - MockPau pau1; - MockPau pau2; + MockTarget target1; + MockTarget target2; address constant CBEAM1 = address(0xC1); address constant CBEAM2 = address(0xC2); address constant USER1 = address(0x1); - event SetRateLimit(address indexed pau, bytes32 indexed key, uint256 maxAmount, uint256 slope); + event SetRateLimit(address indexed target, bytes32 indexed key, uint256 maxAmount, uint256 slope); function setUp() public { beamState = new BeamState(); configurator = new Configurator(address(beamState)); - pau1 = new MockPau(); - pau2 = new MockPau(); + target1 = new MockTarget(); + target2 = new MockTarget(); // Setup default configuration beamState.setHop(address(0), 86_400); // 1 day global default @@ -88,25 +88,26 @@ contract ConfiguratorTest is DssTest { // --- Helper Functions --- - function _setupCBeam(address pau, address cBeam) internal { + function _setupCBeam(address target, address cBeam) internal { beamState.addCBeam(cBeam); - beamState.setCBeamForPau(pau, cBeam); + beamState.setCBeamForController(target, cBeam); + beamState.setCBeamForRateLimits(target, cBeam); } function _setupRateLimitData( - MockPau pau, + MockTarget target, bytes32 key, uint256 maxAmount, uint256 slope, uint256 lastAmount, uint256 lastUpdated ) internal { - pau.setRateLimitData(key, maxAmount, slope, lastAmount, lastUpdated); - pau.setCurrentRateLimit(lastAmount); + target.setRateLimitData(key, maxAmount, slope, lastAmount, lastUpdated); + target.setCurrentRateLimit(lastAmount); } - function _setupDefaultRateLimits(bytes32 key, address pau, uint256 maxAmount, uint256 slope) internal { - beamState.addInitRateLimits(key, pau, maxAmount, slope); + function _setupDefaultRateLimits(bytes32 key, address target, uint256 maxAmount, uint256 slope) internal { + beamState.addInitRateLimits(key, target, maxAmount, slope); } // --- Authorization Tests --- @@ -115,76 +116,76 @@ contract ConfiguratorTest is DssTest { bytes32 key = keccak256("test-key"); vm.prank(CBEAM1); - vm.expectRevert("Configurator/not-authorized-cBeam"); - configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 10 * WAD); + vm.expectRevert("Configurator/not-authorized-ratelimits-cBeam"); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); } function testAuthWithAuthorizedCBeam() public { bytes32 key = keccak256("test-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 400 * WAD, 4 * WAD); + configurator.setRateLimit(address(target1), key, 400 * WAD, 4 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 400 * WAD, "maxAmount should be updated by authorized cBeam"); } - function testAuthCBeamCannotConfigureDifferentPau() public { + function testAuthCBeamCannotConfigureDifferentTarget() public { bytes32 key = keccak256("test-key"); - _setupCBeam(address(pau1), CBEAM1); + _setupCBeam(address(target1), CBEAM1); vm.prank(CBEAM1); - vm.expectRevert("Configurator/not-authorized-cBeam"); - configurator.setRateLimit(address(pau2), key, 1_000 * WAD, 10 * WAD); + vm.expectRevert("Configurator/not-authorized-ratelimits-cBeam"); + configurator.setRateLimit(address(target2), key, 1_000 * WAD, 10 * WAD); } // --- Safe Rate Limit Changes Tests --- function testSetRateLimitDecreasing() public { bytes32 key = keccak256("decreasing-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); vm.prank(CBEAM1); vm.expectEmit(); - emit SetRateLimit(address(pau1), key, 400 * WAD, 4 * WAD); - configurator.setRateLimit(address(pau1), key, 400 * WAD, 4 * WAD); + emit SetRateLimit(address(target1), key, 400 * WAD, 4 * WAD); + configurator.setRateLimit(address(target1), key, 400 * WAD, 4 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 400 * WAD, "maxAmount should decrease immediately"); assertEq(data.slope, 4 * WAD, "slope should decrease immediately"); } function testSetRateLimitWithinDefaults() public { bytes32 key = keccak256("within-defaults-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); // Increase but stay within defaults is safe vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 800 * WAD, 8 * WAD); + configurator.setRateLimit(address(target1), key, 800 * WAD, 8 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 800 * WAD, "maxAmount should be set within defaults"); assertEq(data.slope, 8 * WAD, "slope should be set within defaults"); } function testSetRateLimitDecreasingOneParameter() public { bytes32 key = keccak256("partial-decrease-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); // Decrease maxAmount, keep slope same - still safe vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 400 * WAD, 5 * WAD); + configurator.setRateLimit(address(target1), key, 400 * WAD, 5 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 400 * WAD, "maxAmount should decrease"); assertEq(data.slope, 5 * WAD, "slope should stay same"); } @@ -193,97 +194,97 @@ contract ConfiguratorTest is DssTest { function testSetRateLimitIncreasingTooSoon() public { bytes32 key = keccak256("too-soon-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_200 * WAD, 12 * WAD); - _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); - beamState.setHop(address(pau1), 3_600); // 1 hour hop - beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_200 * WAD, 12 * WAD); + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + beamState.setHop(address(target1), 3_600); // 1 hour hop + beamState.setMaxChange(address(target1), 15 * WAD / 10); // 1.5x vm.warp(block.timestamp + 3_600); // Warp to allow first increase // First increase beyond defaults (unsafe, respects maxChange) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_300 * WAD, 13 * WAD); // 1.3x from current + configurator.setRateLimit(address(target1), key, 1_300 * WAD, 13 * WAD); // 1.3x from current // Try to increase again immediately (should fail) vm.prank(CBEAM1); vm.expectRevert("Configurator/increment-too-soon"); - configurator.setRateLimit(address(pau1), key, 1_400 * WAD, 14 * WAD); + configurator.setRateLimit(address(target1), key, 1_400 * WAD, 14 * WAD); } function testSetRateLimitIncreasingAfterHop() public { bytes32 key = keccak256("after-hop-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 3_000 * WAD, 30 * WAD); - _setupRateLimitData(pau1, key, 2_000 * WAD, 20 * WAD, 2_000 * WAD, block.timestamp); - beamState.setHop(address(pau1), 3_600); // 1 hour hop - beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 3_000 * WAD, 30 * WAD); + _setupRateLimitData(target1, key, 2_000 * WAD, 20 * WAD, 2_000 * WAD, block.timestamp); + beamState.setHop(address(target1), 3_600); // 1 hour hop + beamState.setMaxChange(address(target1), 15 * WAD / 10); // 1.5x vm.warp(block.timestamp + 3_600); // Warp to allow first increase // First increase beyond defaults (unsafe, respects maxChange 1.5x) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 3_000 * WAD, 25 * WAD); // Exactly at defaults, 1.5x maxAmount, 1.25x slope + configurator.setRateLimit(address(target1), key, 3_000 * WAD, 25 * WAD); // Exactly at defaults, 1.5x maxAmount, 1.25x slope // Wait for hop period vm.warp(block.timestamp + 3_600); // Should succeed after hop (respecting maxChange 1.5x from last) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 4_500 * WAD, 37 * WAD); // Exactly 1.5x from 3_000, ~1.48x slope + configurator.setRateLimit(address(target1), key, 4_500 * WAD, 37 * WAD); // Exactly 1.5x from 3_000, ~1.48x slope - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 4_500 * WAD, "maxAmount should increase after hop"); assertEq(data.slope, 37 * WAD, "slope should increase after hop"); } function testSetRateLimitMaxChangeEnforcedMaxAmount() public { bytes32 key = keccak256("maxchange-max-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 800 * WAD, 8 * WAD, 800 * WAD, block.timestamp); - beamState.setHop(address(pau1), 1); // Minimal hop delay - beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 800 * WAD, 8 * WAD, 800 * WAD, block.timestamp); + beamState.setHop(address(target1), 1); // Minimal hop delay + beamState.setMaxChange(address(target1), 15 * WAD / 10); // 1.5x vm.warp(block.timestamp + 1); // Warp past hop to allow attempt // Try to increase more than maxChange allows (beyond defaults and beyond maxChange) vm.prank(CBEAM1); vm.expectRevert("Configurator/maxChange-maxAmount"); - configurator.setRateLimit(address(pau1), key, 1_300 * WAD, 8 * WAD); // 1.625x increase (> 1.5x) + configurator.setRateLimit(address(target1), key, 1_300 * WAD, 8 * WAD); // 1.625x increase (> 1.5x) } function testSetRateLimitMaxChangeEnforcedSlope() public { bytes32 key = keccak256("maxchange-slope-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 1_000 * WAD, 8 * WAD, 1_000 * WAD, block.timestamp); - beamState.setHop(address(pau1), 1); // Minimal hop delay - beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 1_000 * WAD, 8 * WAD, 1_000 * WAD, block.timestamp); + beamState.setHop(address(target1), 1); // Minimal hop delay + beamState.setMaxChange(address(target1), 15 * WAD / 10); // 1.5x vm.warp(block.timestamp + 1); // Warp past hop to allow attempt // Try to increase slope more than maxChange allows (beyond defaults and beyond maxChange) vm.prank(CBEAM1); vm.expectRevert("Configurator/maxChange-slope"); - configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 13 * WAD); // 1.625x increase (> 1.5x) + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 13 * WAD); // 1.625x increase (> 1.5x) } function testSetRateLimitMaxChangeAtLimit() public { bytes32 key = keccak256("maxchange-exact-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 100 * WAD); - _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); - beamState.setHop(address(pau1), 3_600); - beamState.setMaxChange(address(pau1), 15 * WAD / 10); // 1.5x + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 100 * WAD); + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + beamState.setHop(address(target1), 3_600); + beamState.setMaxChange(address(target1), 15 * WAD / 10); // 1.5x vm.warp(block.timestamp + 3_600); // Exactly at maxChange limit should succeed vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_500 * WAD, 15 * WAD); + configurator.setRateLimit(address(target1), key, 1_500 * WAD, 15 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 1_500 * WAD, "maxAmount should be at maxChange limit"); assertEq(data.slope, 15 * WAD, "slope should be at maxChange limit"); } @@ -292,84 +293,84 @@ contract ConfiguratorTest is DssTest { function testZzzTimestampSetOnIncrease() public { bytes32 key = keccak256("zzz-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 100 * WAD); - _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 100 * WAD); + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); uint256 timeBefore = block.timestamp; vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); - assertEq(configurator.zzz(address(pau1), key), timeBefore, "zzz should be set to current timestamp"); + assertEq(configurator.zzz(address(target1), key), timeBefore, "zzz should be set to current timestamp"); } function testZzzTimestampNotSetOnDecrease() public { bytes32 key = keccak256("zzz-decrease-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 100 * WAD); - _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 100 * WAD); + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 800 * WAD, 8 * WAD); + configurator.setRateLimit(address(target1), key, 800 * WAD, 8 * WAD); - assertEq(configurator.zzz(address(pau1), key), 0, "zzz should not be set on decrease"); + assertEq(configurator.zzz(address(target1), key), 0, "zzz should not be set on decrease"); } function testZzzTimestampUpdatedOnSecondIncrease() public { bytes32 key = keccak256("zzz-second-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_0000 * WAD, 100 * WAD); - _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); - beamState.setHop(address(pau1), 3_600); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 100 * WAD); + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + beamState.setHop(address(target1), 3_600); uint256 firstTime = block.timestamp; vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_100 * WAD, 11 * WAD); - assertEq(configurator.zzz(address(pau1), key), firstTime, "zzz should be set to first increase time"); + configurator.setRateLimit(address(target1), key, 1_100 * WAD, 11 * WAD); + assertEq(configurator.zzz(address(target1), key), firstTime, "zzz should be set to first increase time"); vm.warp(block.timestamp + 3_600); uint256 secondTime = block.timestamp; vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); - assertEq(configurator.zzz(address(pau1), key), secondTime, "zzz should be updated to second increase time"); + configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); + assertEq(configurator.zzz(address(target1), key), secondTime, "zzz should be updated to second increase time"); } // --- Unlimited Rate Limit Tests --- function testSetUnlimitedRateLimit() public { bytes32 key = keccak256("unlimited-key"); - _setupCBeam(address(pau1), CBEAM1); - // Set unlimited for specific PAU - _setupDefaultRateLimits(key, address(pau1), type(uint256).max, 0); - _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + // Set unlimited for specific TARGET + _setupDefaultRateLimits(key, address(target1), type(uint256).max, 0); + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); vm.prank(CBEAM1); vm.expectEmit(); - emit SetRateLimit(address(pau1), key, type(uint256).max, 0); - configurator.setRateLimit(address(pau1), key, type(uint256).max, 0); + emit SetRateLimit(address(target1), key, type(uint256).max, 0); + configurator.setRateLimit(address(target1), key, type(uint256).max, 0); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, type(uint256).max, "maxAmount should be unlimited"); assertEq(data.slope, 0, "slope should be 0"); } function testSetUnlimitedFromLimited() public { bytes32 key = keccak256("limited-to-unlimited-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); - // Set defaults that allow unlimited for specific PAU - _setupDefaultRateLimits(key, address(pau1), type(uint256).max, 0); + // Set defaults that allow unlimited for specific TARGET + _setupDefaultRateLimits(key, address(target1), type(uint256).max, 0); // Any value passed should result in unlimited due to special unlimited check vm.prank(CBEAM1); vm.expectEmit(); - emit SetRateLimit(address(pau1), key, type(uint256).max, 0); - configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 10 * WAD); + emit SetRateLimit(address(target1), key, type(uint256).max, 0); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, type(uint256).max, "should set unlimited when defaults are unlimited"); assertEq(data.slope, 0, "slope should be 0 for unlimited"); } @@ -378,31 +379,31 @@ contract ConfiguratorTest is DssTest { function testLastAmountCappedAtMaxAmount() public { bytes32 key = keccak256("cap-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 2_000 * WAD, 20 * WAD, 1_500 * WAD, block.timestamp); - pau1.setCurrentRateLimit(1_500 * WAD); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 2_000 * WAD, 20 * WAD, 1_500 * WAD, block.timestamp); + target1.setCurrentRateLimit(1_500 * WAD); // Decrease maxAmount below current lastAmount vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 10 * WAD); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.lastAmount, 1_000 * WAD, "lastAmount should be capped at new maxAmount"); } function testLastAmountPreservedWhenBelowMax() public { bytes32 key = keccak256("preserve-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 2_000 * WAD, 20 * WAD); - _setupRateLimitData(pau1, key, 1_000 * WAD, 10 * WAD, 500 * WAD, block.timestamp); - pau1.setCurrentRateLimit(500 * WAD); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 2_000 * WAD, 20 * WAD); + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 500 * WAD, block.timestamp); + target1.setCurrentRateLimit(500 * WAD); // Increase maxAmount vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_500 * WAD, 15 * WAD); + configurator.setRateLimit(address(target1), key, 1_500 * WAD, 15 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.lastAmount, 500 * WAD, "lastAmount should be preserved when below new max"); } @@ -410,73 +411,73 @@ contract ConfiguratorTest is DssTest { function testGlobalDefaultsUsedWhenNoSpecific() public { bytes32 key = keccak256("global-key"); - _setupCBeam(address(pau1), CBEAM1); + _setupCBeam(address(target1), CBEAM1); _setupDefaultRateLimits(key, address(0), 1_000 * WAD, 10 * WAD); // Global defaults - _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); // Should use global defaults vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 900 * WAD, 9 * WAD); + configurator.setRateLimit(address(target1), key, 900 * WAD, 9 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 900 * WAD, "should work with global defaults 1"); assertEq(data.slope, 9 * WAD, "should work with global defaults 2"); } function testSpecificDefaultsOverrideGlobal() public { bytes32 key = keccak256("specific-override-key"); - _setupCBeam(address(pau1), CBEAM1); + _setupCBeam(address(target1), CBEAM1); _setupDefaultRateLimits(key, address(0), 1_000 * WAD, 10 * WAD); // Global - _setupDefaultRateLimits(key, address(pau1), 2_000 * WAD, 20 * WAD); // Specific - _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + _setupDefaultRateLimits(key, address(target1), 2_000 * WAD, 20 * WAD); // Specific + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); - // Should use PAU1 specific defaults (higher limits) + // Should use TARGET1 specific defaults (higher limits) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_500 * WAD, 15 * WAD); + configurator.setRateLimit(address(target1), key, 1_500 * WAD, 15 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 1_500 * WAD, "should work with specific defaults 1"); assertEq(data.slope, 15 * WAD, "should work with specific defaults 2"); } function testGlobalHopUsedWhenNoSpecific() public { bytes32 key = keccak256("global-hop-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); // Only global hop set (in setUp) - assertEq(beamState.hop(address(pau1)), 0, "PAU1 specific hop should be 0"); - assertEq(beamState.getHop(address(pau1)), 86_400, "should fallback to global hop"); + assertEq(beamState.hop(address(target1)), 0, "TARGET1 specific hop should be 0"); + assertEq(beamState.getHop(address(target1)), 86_400, "should fallback to global hop"); vm.warp(block.timestamp + 86_400); // Warp to allow first increase // First increase beyond defaults (unsafe) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_100 * WAD, 11 * WAD); + configurator.setRateLimit(address(target1), key, 1_100 * WAD, 11 * WAD); // Try second increase immediately - should fail due to global hop vm.prank(CBEAM1); vm.expectRevert("Configurator/increment-too-soon"); - configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); } function testGlobalMaxChangeUsedWhenNoSpecific() public { bytes32 key = keccak256("global-maxchange-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); // Only global maxChange set (in setUp to 1.5x) - assertEq(beamState.maxChange(address(pau1)), 0, "PAU1 specific maxChange should be 0"); - assertEq(beamState.getMaxChange(address(pau1)), 15 * WAD / 10, "should fallback to global maxChange"); + assertEq(beamState.maxChange(address(target1)), 0, "TARGET1 specific maxChange should be 0"); + assertEq(beamState.getMaxChange(address(target1)), 15 * WAD / 10, "should fallback to global maxChange"); vm.warp(block.timestamp + 86_400); // Wait for hop // Try to increase by 1.67x - should fail due to global maxChange of 1.5x (beyond defaults) vm.prank(CBEAM1); vm.expectRevert("Configurator/maxChange-maxAmount"); - configurator.setRateLimit(address(pau1), key, 1_500 * WAD, 9 * WAD); + configurator.setRateLimit(address(target1), key, 1_500 * WAD, 9 * WAD); } // --- Controller Action Tests --- @@ -484,121 +485,121 @@ contract ConfiguratorTest is DssTest { function testCallControllerAction() public { bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 42); - _setupCBeam(address(pau1), CBEAM1); - beamState.addInitControllerActions(data, address(pau1)); + _setupCBeam(address(target1), CBEAM1); + beamState.addInitControllerActions(data, address(target1)); vm.prank(CBEAM1); - bytes memory ret = configurator.callControllerAction(address(pau1), data); + bytes memory ret = configurator.callControllerAction(address(target1), data); uint256 result = abi.decode(ret, (uint256)); assertEq(result, 84, "controller function should return 42 * 2"); - assertEq(pau1.lastCallData(), data, "correct data should be passed to PAU"); + assertEq(target1.lastCallData(), data, "correct data should be passed to TARGET"); } function testCallControllerActionNotWhitelisted() public { bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 42); - _setupCBeam(address(pau1), CBEAM1); + _setupCBeam(address(target1), CBEAM1); // Don't whitelist the action vm.prank(CBEAM1); vm.expectRevert("Configurator/not-valid-data"); - configurator.callControllerAction(address(pau1), data); + configurator.callControllerAction(address(target1), data); } function testCallControllerActionNotAuthorized() public { bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 42); - beamState.addInitControllerActions(data, address(pau1)); + beamState.addInitControllerActions(data, address(target1)); // Don't setup cBeam authorization vm.prank(CBEAM1); - vm.expectRevert("Configurator/not-authorized-cBeam"); - configurator.callControllerAction(address(pau1), data); + vm.expectRevert("Configurator/not-authorized-controller-cBeam"); + configurator.callControllerAction(address(target1), data); } function testCallControllerActionGlobalWhitelist() public { bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 100); - _setupCBeam(address(pau1), CBEAM1); - _setupCBeam(address(pau2), CBEAM1); + _setupCBeam(address(target1), CBEAM1); + _setupCBeam(address(target2), CBEAM1); beamState.addInitControllerActions(data, address(0)); // Global whitelist - // Should work for any PAU + // Should work for any TARGET vm.prank(CBEAM1); - bytes memory ret1 = configurator.callControllerAction(address(pau1), data); - assertEq(abi.decode(ret1, (uint256)), 200, "should work with global whitelist for pau1"); + bytes memory ret1 = configurator.callControllerAction(address(target1), data); + assertEq(abi.decode(ret1, (uint256)), 200, "should work with global whitelist for target1"); vm.prank(CBEAM1); - bytes memory ret2 = configurator.callControllerAction(address(pau2), data); - assertEq(abi.decode(ret2, (uint256)), 200, "should work with global whitelist for pau2"); + bytes memory ret2 = configurator.callControllerAction(address(target2), data); + assertEq(abi.decode(ret2, (uint256)), 200, "should work with global whitelist for target2"); } function testCallControllerActionFails() public { bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 42); - _setupCBeam(address(pau1), CBEAM1); - beamState.addInitControllerActions(data, address(pau1)); - pau1.setShouldFail(true); + _setupCBeam(address(target1), CBEAM1); + beamState.addInitControllerActions(data, address(target1)); + target1.setShouldFail(true); vm.prank(CBEAM1); vm.expectRevert("Configurator/call-failed"); - configurator.callControllerAction(address(pau1), data); + configurator.callControllerAction(address(target1), data); } function testCallControllerActionArbitraryData() public { bytes memory data = abi.encodeWithSignature("someOtherFunction(address,uint256)", address(this), 999); - _setupCBeam(address(pau1), CBEAM1); - beamState.addInitControllerActions(data, address(pau1)); + _setupCBeam(address(target1), CBEAM1); + beamState.addInitControllerActions(data, address(target1)); vm.prank(CBEAM1); - configurator.callControllerAction(address(pau1), data); + configurator.callControllerAction(address(target1), data); - assertEq(pau1.lastCallData(), data, "arbitrary data should be passed correctly"); + assertEq(target1.lastCallData(), data, "arbitrary data should be passed correctly"); } // --- Combined functions Tests --- function testFullRateLimitWorkflow() public { bytes32 key = keccak256("workflow-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 800 * WAD, 8 * WAD, 800 * WAD, block.timestamp); - beamState.setHop(address(pau1), 3_600); - beamState.setMaxChange(address(pau1), 2 * WAD); // 2x + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 800 * WAD, 8 * WAD, 800 * WAD, block.timestamp); + beamState.setHop(address(target1), 3_600); + beamState.setMaxChange(address(target1), 2 * WAD); // 2x vm.warp(block.timestamp + 3_600); // Warp to allow first unsafe increase // 1. Decrease limits (safe, immediate) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 700 * WAD, 7 * WAD); + configurator.setRateLimit(address(target1), key, 700 * WAD, 7 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 700 * WAD, "step 1: decrease should work immediately"); // 2. Increase limits beyond defaults (unsafe, sets timestamp) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); - data = pau1.getRateLimitData(key); + data = target1.getRateLimitData(key); assertEq(data.maxAmount, 1_200 * WAD, "step 2: first increase should work"); - uint256 firstIncrease = configurator.zzz(address(pau1), key); + uint256 firstIncrease = configurator.zzz(address(target1), key); assertGt(firstIncrease, 0, "step 2: zzz should be set"); // 3. Try immediate second increase (should fail) vm.prank(CBEAM1); vm.expectRevert("Configurator/increment-too-soon"); - configurator.setRateLimit(address(pau1), key, 1_400 * WAD, 14 * WAD); + configurator.setRateLimit(address(target1), key, 1_400 * WAD, 14 * WAD); // 4. Wait hop period vm.warp(block.timestamp + 3_600); // 5. Increase again (should work, respecting maxChange) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 2_400 * WAD, 24 * WAD); // Exactly 2x + configurator.setRateLimit(address(target1), key, 2_400 * WAD, 24 * WAD); // Exactly 2x - data = pau1.getRateLimitData(key); + data = target1.getRateLimitData(key); assertEq(data.maxAmount, 2_400 * WAD, "step 5: second increase should work after hop"); assertEq(data.slope, 24 * WAD, "step 5: slope should increase"); @@ -606,92 +607,92 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 3_600); vm.prank(CBEAM1); vm.expectRevert("Configurator/maxChange-maxAmount"); - configurator.setRateLimit(address(pau1), key, 4_900 * WAD, 24 * WAD); // >2x (2.04x) + configurator.setRateLimit(address(target1), key, 4_900 * WAD, 24 * WAD); // >2x (2.04x) } - function testMultipleCBeamsMultiplePaus() public { + function testMultipleCBeamsMultipleTargets() public { bytes32 key = keccak256("multi-key"); - // Setup CBEAM1 for PAU1 - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + // Setup CBEAM1 for TARGET1 + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); - // Setup CBEAM2 for PAU2 - _setupCBeam(address(pau2), CBEAM2); - _setupDefaultRateLimits(key, address(pau2), 2_000 * WAD, 20 * WAD); - _setupRateLimitData(pau2, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + // Setup CBEAM2 for TARGET2 + _setupCBeam(address(target2), CBEAM2); + _setupDefaultRateLimits(key, address(target2), 2_000 * WAD, 20 * WAD); + _setupRateLimitData(target2, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); - // CBEAM1 configures PAU1 + // CBEAM1 configures TARGET1 vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 600 * WAD, 6 * WAD); + configurator.setRateLimit(address(target1), key, 600 * WAD, 6 * WAD); - // CBEAM2 configures PAU2 + // CBEAM2 configures TARGET2 vm.prank(CBEAM2); - configurator.setRateLimit(address(pau2), key, 1_200 * WAD, 12 * WAD); + configurator.setRateLimit(address(target2), key, 1_200 * WAD, 12 * WAD); // Verify independent operation - RateLimitsLike.RateLimitData memory data1 = pau1.getRateLimitData(key); - RateLimitsLike.RateLimitData memory data2 = pau2.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data1 = target1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data2 = target2.getRateLimitData(key); - assertEq(data1.maxAmount, 600 * WAD, "PAU1 should have its own limits"); - assertEq(data2.maxAmount, 1_200 * WAD, "PAU2 should have its own limits"); + assertEq(data1.maxAmount, 600 * WAD, "TARGET1 should have its own limits"); + assertEq(data2.maxAmount, 1_200 * WAD, "TARGET2 should have its own limits"); - // CBEAM1 cannot configure PAU2 + // CBEAM1 cannot configure TARGET2 vm.prank(CBEAM1); - vm.expectRevert("Configurator/not-authorized-cBeam"); - configurator.setRateLimit(address(pau2), key, 1_500 * WAD, 15 * WAD); + vm.expectRevert("Configurator/not-authorized-ratelimits-cBeam"); + configurator.setRateLimit(address(target2), key, 1_500 * WAD, 15 * WAD); } function testIncreaseThenDecreaseThenIncrease() public { bytes32 key = keccak256("toggle-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); - beamState.setHop(address(pau1), 3_600); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + beamState.setHop(address(target1), 3_600); vm.warp(block.timestamp + 3_600); // Warp to allow first increase // Increase beyond defaults (sets zzz) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_100 * WAD, 11 * WAD); - uint256 zzzAfterIncrease = configurator.zzz(address(pau1), key); + configurator.setRateLimit(address(target1), key, 1_100 * WAD, 11 * WAD); + uint256 zzzAfterIncrease = configurator.zzz(address(target1), key); assertGt(zzzAfterIncrease, 0, "zzz should be set after increase"); // Decrease (doesn't update zzz for the decrease itself, but allows future increases) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 900 * WAD, 9 * WAD); - assertEq(configurator.zzz(address(pau1), key), zzzAfterIncrease, "zzz should not change on decrease"); + configurator.setRateLimit(address(target1), key, 900 * WAD, 9 * WAD); + assertEq(configurator.zzz(address(target1), key), zzzAfterIncrease, "zzz should not change on decrease"); // Immediate increase (within defaults now) should work vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_000 * WAD, 10 * WAD); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); // Increase beyond defaults should respect hop from first increase vm.prank(CBEAM1); vm.expectRevert("Configurator/increment-too-soon"); - configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); // After hop, increase should work vm.warp(block.timestamp + 3_600); vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 1_200 * WAD, 12 * WAD); + configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 1_200 * WAD, "should be able to increase after hop"); } function testEdgeCaseZeroValues() public { bytes32 key = keccak256("zero-key"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); // Set to zero (should be allowed as it's decreasing) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key, 0, 0); + configurator.setRateLimit(address(target1), key, 0, 0); - RateLimitsLike.RateLimitData memory data = pau1.getRateLimitData(key); + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 0, "maxAmount can be set to 0"); assertEq(data.slope, 0, "slope can be set to 0"); } @@ -700,29 +701,29 @@ contract ConfiguratorTest is DssTest { bytes32 key1 = keccak256("key-1"); bytes32 key2 = keccak256("key-2"); - _setupCBeam(address(pau1), CBEAM1); - _setupDefaultRateLimits(key1, address(pau1), 1_000 * WAD, 10 * WAD); - _setupDefaultRateLimits(key2, address(pau1), 1_000 * WAD, 10 * WAD); - _setupRateLimitData(pau1, key1, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); - _setupRateLimitData(pau1, key2, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); - beamState.setHop(address(pau1), 3_600); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key1, address(target1), 1_000 * WAD, 10 * WAD); + _setupDefaultRateLimits(key2, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key1, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + _setupRateLimitData(target1, key2, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + beamState.setHop(address(target1), 3_600); vm.warp(block.timestamp + 3_600); // Warp to allow first increase // Increase key1 beyond defaults vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key1, 1_100 * WAD, 11 * WAD); + configurator.setRateLimit(address(target1), key1, 1_100 * WAD, 11 * WAD); // Increase key2 immediately should work (different key) vm.prank(CBEAM1); - configurator.setRateLimit(address(pau1), key2, 1_100 * WAD, 11 * WAD); + configurator.setRateLimit(address(target1), key2, 1_100 * WAD, 11 * WAD); // But can't increase key1 again immediately vm.prank(CBEAM1); vm.expectRevert("Configurator/increment-too-soon"); - configurator.setRateLimit(address(pau1), key1, 1_200 * WAD, 12 * WAD); + configurator.setRateLimit(address(target1), key1, 1_200 * WAD, 12 * WAD); - assertGt(configurator.zzz(address(pau1), key1), 0, "key1 should have zzz set"); - assertGt(configurator.zzz(address(pau1), key2), 0, "key2 should have zzz set"); + assertGt(configurator.zzz(address(target1), key1), 0, "key1 should have zzz set"); + assertGt(configurator.zzz(address(target1), key2), 0, "key2 should have zzz set"); } } From 07a56c092c95a73594e92e6a57e493530c0d5742 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 9 Jan 2026 14:02:19 -0300 Subject: [PATCH 030/150] Flip order some functions --- src/BeamState.sol | 22 +++++------ test/BeamState.t.sol | 92 ++++++++++++++++++++++---------------------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 924d60e..c91a254 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -152,17 +152,6 @@ contract BeamState { emit DelCBeam(cBeam); } - function setCBeamForController(address controller, address cBeam) external roleAuth { - require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); - controllersCBeams[controller][cBeam] = 1; - emit SetCBeamForController(controller, cBeam); - } - - function unsetCBeamForController(address controller, address cBeam) external roleAuth { - controllersCBeams[controller][cBeam] = 0; - emit UnsetCBeamForController(controller, cBeam); - } - function setCBeamForRateLimits(address rateLimits, address cBeam) external roleAuth { require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); rateLimitsCBeams[rateLimits][cBeam] = 1; @@ -174,6 +163,17 @@ contract BeamState { emit UnsetCBeamForRateLimits(rateLimits, cBeam); } + function setCBeamForController(address controller, address cBeam) external roleAuth { + require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); + controllersCBeams[controller][cBeam] = 1; + emit SetCBeamForController(controller, cBeam); + } + + function unsetCBeamForController(address controller, address cBeam) external roleAuth { + controllersCBeams[controller][cBeam] = 0; + emit UnsetCBeamForController(controller, cBeam); + } + function addInitRateLimits(bytes32 key, address rateLimits, uint256 maxAmount, uint256 slope) external roleAuth { initRateLimits[key][rateLimits] = DefaultRateLimits(maxAmount, slope); emit AddInitRateLimits(key, rateLimits, maxAmount, slope); diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 05366ad..29c99b0 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -297,116 +297,116 @@ contract BeamStateTest is DssTest { assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should be deleted by role user"); } - // --- cBeam-Controller Mapping Tests --- + // --- cBeam-RateLimits Mapping Tests --- - function testSetCBeamForController() public { + function testSetCBeamForRateLimits() public { beamState.addCBeam(CBEAM1); - assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 initially"); + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 initially"); vm.expectEmit(); - emit SetCBeamForController(TARGET1, CBEAM1); - beamState.setCBeamForController(TARGET1, CBEAM1); + emit SetCBeamForRateLimits(TARGET1, CBEAM1); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); - assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); } - function testSetCBeamForControllerNotExisting() public { + function testSetCBeamForRateLimitsNotExisting() public { vm.expectRevert("BeamState/not-existing-cBeam"); - beamState.setCBeamForController(TARGET1, CBEAM1); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); } - function testSetCBeamForControllerRoleAuth() public { + function testSetCBeamForRateLimitsRoleAuth() public { beamState.addCBeam(CBEAM1); - beamState.setRoleAction(4, beamState.setCBeamForController.selector, true); + beamState.setRoleAction(4, beamState.setCBeamForRateLimits.selector, true); beamState.setUserRole(USER1, 4, true); vm.prank(USER1); - beamState.setCBeamForController(TARGET1, CBEAM1); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); - assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be set for TARGET1 by role user"); + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be set for TARGET1 by role user"); } - function testUnsetCBeamForController() public { + function testUnsetCBeamForRateLimits() public { beamState.addCBeam(CBEAM1); - beamState.setCBeamForController(TARGET1, CBEAM1); - assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); vm.expectEmit(); - emit UnsetCBeamForController(TARGET1, CBEAM1); - beamState.unsetCBeamForController(TARGET1, CBEAM1); + emit UnsetCBeamForRateLimits(TARGET1, CBEAM1); + beamState.unsetCBeamForRateLimits(TARGET1, CBEAM1); - assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 after unset"); + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 after unset"); } - function testUnsetCBeamForControllerRoleAuth() public { + function testUnsetCBeamForRateLimitsRoleAuth() public { beamState.addCBeam(CBEAM1); - beamState.setCBeamForController(TARGET1, CBEAM1); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); - beamState.setRoleAction(4, beamState.unsetCBeamForController.selector, true); + beamState.setRoleAction(4, beamState.unsetCBeamForRateLimits.selector, true); beamState.setUserRole(USER1, 4, true); vm.prank(USER1); - beamState.unsetCBeamForController(TARGET1, CBEAM1); + beamState.unsetCBeamForRateLimits(TARGET1, CBEAM1); - assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should be unset for TARGET1 by role user"); + assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should be unset for TARGET1 by role user"); } - // --- cBeam-RateLimits Mapping Tests --- + // --- cBeam-Controller Mapping Tests --- - function testSetCBeamForRateLimits() public { + function testSetCBeamForController() public { beamState.addCBeam(CBEAM1); - assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 initially"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 initially"); vm.expectEmit(); - emit SetCBeamForRateLimits(TARGET1, CBEAM1); - beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + emit SetCBeamForController(TARGET1, CBEAM1); + beamState.setCBeamForController(TARGET1, CBEAM1); - assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); } - function testSetCBeamForRateLimitsNotExisting() public { + function testSetCBeamForControllerNotExisting() public { vm.expectRevert("BeamState/not-existing-cBeam"); - beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + beamState.setCBeamForController(TARGET1, CBEAM1); } - function testSetCBeamForRateLimitsRoleAuth() public { + function testSetCBeamForControllerRoleAuth() public { beamState.addCBeam(CBEAM1); - beamState.setRoleAction(4, beamState.setCBeamForRateLimits.selector, true); + beamState.setRoleAction(4, beamState.setCBeamForController.selector, true); beamState.setUserRole(USER1, 4, true); vm.prank(USER1); - beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + beamState.setCBeamForController(TARGET1, CBEAM1); - assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be set for TARGET1 by role user"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be set for TARGET1 by role user"); } - function testUnsetCBeamForRateLimits() public { + function testUnsetCBeamForController() public { beamState.addCBeam(CBEAM1); - beamState.setCBeamForRateLimits(TARGET1, CBEAM1); - assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); + beamState.setCBeamForController(TARGET1, CBEAM1); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); vm.expectEmit(); - emit UnsetCBeamForRateLimits(TARGET1, CBEAM1); - beamState.unsetCBeamForRateLimits(TARGET1, CBEAM1); + emit UnsetCBeamForController(TARGET1, CBEAM1); + beamState.unsetCBeamForController(TARGET1, CBEAM1); - assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 after unset"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 after unset"); } - function testUnsetCBeamForRateLimitsRoleAuth() public { + function testUnsetCBeamForControllerRoleAuth() public { beamState.addCBeam(CBEAM1); - beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + beamState.setCBeamForController(TARGET1, CBEAM1); - beamState.setRoleAction(4, beamState.unsetCBeamForRateLimits.selector, true); + beamState.setRoleAction(4, beamState.unsetCBeamForController.selector, true); beamState.setUserRole(USER1, 4, true); vm.prank(USER1); - beamState.unsetCBeamForRateLimits(TARGET1, CBEAM1); + beamState.unsetCBeamForController(TARGET1, CBEAM1); - assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should be unset for TARGET1 by role user"); + assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should be unset for TARGET1 by role user"); } // --- Rate Limits Tests --- From d29008f1052d743998ad133ef7ea1cca568f8e1d Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 9 Jan 2026 19:00:12 -0300 Subject: [PATCH 031/150] Add event for callControllerAction --- src/Configurator.sol | 20 +++++++++++--------- test/Configurator.t.sol | 9 +++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Configurator.sol b/src/Configurator.sol index 3746a8c..81972c2 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -1,6 +1,15 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; +interface BeamStateLike { + function getHop(address) external view returns (uint256); + function getMaxChange(address) external view returns (uint256); + function controllersCBeams(address, address) external view returns (uint256); + function rateLimitsCBeams(address, address) external view returns (uint256); + function getInitRateLimits(bytes32, address) external view returns (uint256, uint256); + function isControllerActionEnabled(bytes32, address) external view returns (bool); +} + interface RateLimitsLike { struct RateLimitData { uint256 maxAmount; @@ -15,15 +24,6 @@ interface RateLimitsLike { function setUnlimitedRateLimitData(bytes32) external; } -interface BeamStateLike { - function getHop(address) external view returns (uint256); - function getMaxChange(address) external view returns (uint256); - function controllersCBeams(address, address) external view returns (uint256); - function rateLimitsCBeams(address, address) external view returns (uint256); - function getInitRateLimits(bytes32, address) external view returns (uint256, uint256); - function isControllerActionEnabled(bytes32, address) external view returns (bool); -} - contract Configurator { // --- Storage variables --- @@ -41,6 +41,7 @@ contract Configurator { // --- Events --- event SetRateLimit(address indexed rateLimits, bytes32 indexed key, uint256 maxAmount, uint256 slope); + event CallControllerAction(address indexed controller, bytes data); // --- Modifiers --- @@ -94,5 +95,6 @@ contract Configurator { bool ok; (ok, ret) = controller.call(data); require(ok, "Configurator/call-failed"); + emit CallControllerAction(controller, data); } } diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 2779f3b..b182f57 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -67,6 +67,7 @@ contract ConfiguratorTest is DssTest { address constant USER1 = address(0x1); event SetRateLimit(address indexed target, bytes32 indexed key, uint256 maxAmount, uint256 slope); + event CallControllerAction(address indexed controller, bytes data); function setUp() public { beamState = new BeamState(); @@ -489,6 +490,8 @@ contract ConfiguratorTest is DssTest { beamState.addInitControllerActions(data, address(target1)); vm.prank(CBEAM1); + vm.expectEmit(); + emit CallControllerAction(address(target1), data); bytes memory ret = configurator.callControllerAction(address(target1), data); uint256 result = abi.decode(ret, (uint256)); @@ -527,10 +530,14 @@ contract ConfiguratorTest is DssTest { // Should work for any TARGET vm.prank(CBEAM1); + vm.expectEmit(); + emit CallControllerAction(address(target1), data); bytes memory ret1 = configurator.callControllerAction(address(target1), data); assertEq(abi.decode(ret1, (uint256)), 200, "should work with global whitelist for target1"); vm.prank(CBEAM1); + vm.expectEmit(); + emit CallControllerAction(address(target2), data); bytes memory ret2 = configurator.callControllerAction(address(target2), data); assertEq(abi.decode(ret2, (uint256)), 200, "should work with global whitelist for target2"); } @@ -554,6 +561,8 @@ contract ConfiguratorTest is DssTest { beamState.addInitControllerActions(data, address(target1)); vm.prank(CBEAM1); + vm.expectEmit(); + emit CallControllerAction(address(target1), data); configurator.callControllerAction(address(target1), data); assertEq(target1.lastCallData(), data, "arbitrary data should be passed correctly"); From 7c0f2889b2cdc1f990990324aa5b1a1945126be4 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 12 Jan 2026 10:47:06 -0300 Subject: [PATCH 032/150] Add whitelisting for rateLimits and controllers --- src/BeamState.sol | 112 ++++++++++++++++++------------ test/BeamState.t.sol | 147 ++++++++++++++++++++++++++++++++++++++-- test/Configurator.t.sol | 2 + 3 files changed, 212 insertions(+), 49 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index c91a254..43b7e68 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -5,16 +5,18 @@ contract BeamState { // --- Storage variables --- - mapping(address usr => uint256 allowed) public wards; - mapping(address usr => bytes32 rolesData) public userRoles; - mapping(bytes4 sig => bytes32 rolesData) public actionsRoles; - mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => true - mapping(address controller => mapping(address cBeam => uint256 allowed)) public controllersCBeams; // allowed == 0 => false, allowed == 1 => true - mapping(address rateLimits => mapping(address cBeam => uint256 allowed)) public rateLimitsCBeams; // allowed == 0 => false, allowed == 1 => true - mapping(bytes32 key => mapping(address rateLimits => DefaultRateLimits limit)) public initRateLimits; // rateLimits == address(0) every rateLimits allowed - mapping(bytes32 key => mapping(address rateLimits => bool allowed)) public initControllerActions; // rateLimits == address(0) every rateLimits allowed - mapping(address rateLimits => uint256 value) public hop; // rateLimits == address(0) => general backup configuration - mapping(address rateLimits => uint256 value) public maxChange; // rateLimits == address(0) => general backup configuration + mapping(address usr => uint256 allowed) public wards; + mapping(address usr => bytes32 rolesData) public userRoles; + mapping(bytes4 sig => bytes32 rolesData) public actionsRoles; + mapping(address rateLimits_ => uint256 added) public rateLimits; // allowed == 0 => false, allowed == 1 => true + mapping(address controller => uint256 added) public controllers; // allowed == 0 => false, allowed == 1 => true + mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => true + mapping(address rateLimits_ => mapping(address cBeam => uint256 allowed)) public rateLimitsCBeams; // allowed == 0 => false, allowed == 1 => true + mapping(address controller => mapping(address cBeam => uint256 allowed)) public controllersCBeams; // allowed == 0 => false, allowed == 1 => true + mapping(bytes32 key => mapping(address rateLimits_ => DefaultRateLimits limit)) public initRateLimits; // rateLimits == address(0) every rateLimits allowed + mapping(bytes32 key => mapping(address rateLimits_ => bool allowed)) public initControllerActions; // rateLimits == address(0) every rateLimits allowed + mapping(address rateLimits_ => uint256 value) public hop; // rateLimits == address(0) => general backup configuration + mapping(address rateLimits_ => uint256 value) public maxChange; // rateLimits == address(0) => general backup configuration struct DefaultRateLimits { uint256 maxAmount; @@ -31,18 +33,22 @@ contract BeamState { event Deny(address indexed usr); event SetUserRole(address indexed who, uint8 indexed role, bool enabled); event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); - event SetHop(address indexed rateLimits, uint256 value); - event SetMaxChange(address indexed rateLimits, uint256 value); + event SetHop(address indexed rateLimits_, uint256 value); + event SetMaxChange(address indexed rateLimits_, uint256 value); event AddCBeam(address indexed cBeam); event DelCBeam(address indexed cBeam); + event AddRateLimits(address indexed rateLimits_); + event DelRateLimits(address indexed rateLimits_); + event AddController(address indexed controller); + event DelController(address indexed controller); event SetCBeamForController(address indexed controller, address indexed cBeam); event UnsetCBeamForController(address indexed controller, address indexed cBeam); - event SetCBeamForRateLimits(address indexed rateLimits, address indexed cBeam); - event UnsetCBeamForRateLimits(address indexed rateLimits, address indexed cBeam); - event AddInitRateLimits(bytes32 indexed key, address indexed rateLimits, uint256 maxAmount, uint256 slope); - event DelInitRateLimits(bytes32 indexed key, address indexed rateLimits); - event AddInitControllerActions(bytes32 indexed key, address indexed rateLimits); - event DelInitControllerActions(bytes32 indexed key, address indexed rateLimits); + event SetCBeamForRateLimits(address indexed rateLimits_, address indexed cBeam); + event UnsetCBeamForRateLimits(address indexed rateLimits_, address indexed cBeam); + event AddInitRateLimits(bytes32 indexed key, address indexed rateLimits_, uint256 maxAmount, uint256 slope); + event DelInitRateLimits(bytes32 indexed key, address indexed rateLimits_); + event AddInitControllerActions(bytes32 indexed key, address indexed rateLimits_); + event DelInitControllerActions(bytes32 indexed key, address indexed rateLimits_); // --- Modifiers --- @@ -76,16 +82,16 @@ contract BeamState { has = actionsRoles[sig] & bytes32(uint256(1) << role) != bytes32(0); } - function getHop(address rateLimits) external view returns (uint256 hop_) { - hop_ = hop[rateLimits]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; + function getHop(address rateLimits_) external view returns (uint256 hop_) { + hop_ = hop[rateLimits_]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; } - function getMaxChange(address rateLimits) external view returns (uint256 maxChange_) { - maxChange_ = maxChange[rateLimits]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; + function getMaxChange(address rateLimits_) external view returns (uint256 maxChange_) { + maxChange_ = maxChange[rateLimits_]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; } - function getInitRateLimits(bytes32 key, address rateLimits) external view returns (DefaultRateLimits memory defaultRateLimits) { - defaultRateLimits = initRateLimits[key][rateLimits]; + function getInitRateLimits(bytes32 key, address rateLimits_) external view returns (DefaultRateLimits memory defaultRateLimits) { + defaultRateLimits = initRateLimits[key][rateLimits_]; if (defaultRateLimits.maxAmount == 0 && defaultRateLimits.slope == 0) { // If not set for specific rateLimits, check in general defaultRateLimits = initRateLimits[key][address(0)]; @@ -131,15 +137,35 @@ contract BeamState { // --- Role authed functions --- - function setHop(address rateLimits, uint256 value) external roleAuth { - hop[rateLimits] = value; - emit SetHop(rateLimits, value); + function setHop(address rateLimits_, uint256 value) external roleAuth { + hop[rateLimits_] = value; + emit SetHop(rateLimits_, value); } - function setMaxChange(address rateLimits, uint256 value) external roleAuth { + function setMaxChange(address rateLimits_, uint256 value) external roleAuth { require(value >= WAD, "Configurator/maxChange-below-1x"); - maxChange[rateLimits] = value; - emit SetMaxChange(rateLimits, value); + maxChange[rateLimits_] = value; + emit SetMaxChange(rateLimits_, value); + } + + function addRateLimits(address rateLimits_) external roleAuth { + rateLimits[rateLimits_] = 1; + emit AddRateLimits(rateLimits_); + } + + function delRateLimits(address rateLimits_) external roleAuth { + rateLimits[rateLimits_] = 0; + emit DelRateLimits(rateLimits_); + } + + function addController(address rateLimits_) external roleAuth { + controllers[rateLimits_] = 1; + emit AddController(rateLimits_); + } + + function delController(address controller) external roleAuth { + controllers[controller] = 0; + emit DelController(controller); } function addCBeam(address cBeam) external roleAuth { @@ -152,18 +178,20 @@ contract BeamState { emit DelCBeam(cBeam); } - function setCBeamForRateLimits(address rateLimits, address cBeam) external roleAuth { + function setCBeamForRateLimits(address rateLimits_, address cBeam) external roleAuth { + require(rateLimits[rateLimits_] == 1, "BeamState/not-existing-rateLimits"); require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); - rateLimitsCBeams[rateLimits][cBeam] = 1; - emit SetCBeamForRateLimits(rateLimits, cBeam); + rateLimitsCBeams[rateLimits_][cBeam] = 1; + emit SetCBeamForRateLimits(rateLimits_, cBeam); } - function unsetCBeamForRateLimits(address rateLimits, address cBeam) external roleAuth { - rateLimitsCBeams[rateLimits][cBeam] = 0; - emit UnsetCBeamForRateLimits(rateLimits, cBeam); + function unsetCBeamForRateLimits(address rateLimits_, address cBeam) external roleAuth { + rateLimitsCBeams[rateLimits_][cBeam] = 0; + emit UnsetCBeamForRateLimits(rateLimits_, cBeam); } function setCBeamForController(address controller, address cBeam) external roleAuth { + require(controllers[controller] == 1, "BeamState/not-existing-controller"); require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); controllersCBeams[controller][cBeam] = 1; emit SetCBeamForController(controller, cBeam); @@ -174,14 +202,14 @@ contract BeamState { emit UnsetCBeamForController(controller, cBeam); } - function addInitRateLimits(bytes32 key, address rateLimits, uint256 maxAmount, uint256 slope) external roleAuth { - initRateLimits[key][rateLimits] = DefaultRateLimits(maxAmount, slope); - emit AddInitRateLimits(key, rateLimits, maxAmount, slope); + function addInitRateLimits(bytes32 key, address rateLimits_, uint256 maxAmount, uint256 slope) external roleAuth { + initRateLimits[key][rateLimits_] = DefaultRateLimits(maxAmount, slope); + emit AddInitRateLimits(key, rateLimits_, maxAmount, slope); } - function delInitRateLimits(bytes32 key, address rateLimits) external roleAuth { - delete initRateLimits[key][rateLimits]; - emit DelInitRateLimits(key, rateLimits); + function delInitRateLimits(bytes32 key, address rateLimits_) external roleAuth { + delete initRateLimits[key][rateLimits_]; + emit DelInitRateLimits(key, rateLimits_); } function addInitControllerActions(bytes calldata data, address controller) external roleAuth returns (bytes32 key) { diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 29c99b0..9db297a 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -19,6 +19,10 @@ contract BeamStateTest is DssTest { event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); event SetHop(address indexed rateLimits, uint256 value); event SetMaxChange(address indexed rateLimits, uint256 value); + event AddRateLimits(address indexed rateLimits_); + event DelRateLimits(address indexed rateLimits_); + event AddController(address indexed controller); + event DelController(address indexed controller); event AddCBeam(address indexed cBeam); event DelCBeam(address indexed cBeam); event SetCBeamForController(address indexed controller, address indexed cBeam); @@ -297,9 +301,116 @@ contract BeamStateTest is DssTest { assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should be deleted by role user"); } + // --- RateLimits Management Tests --- + + function testAddRateLimits() public { + assertEq(beamState.rateLimits(TARGET1), 0, "TARGET1 should not be registered initially"); + + vm.expectEmit(); + emit AddRateLimits(TARGET1); + beamState.addRateLimits(TARGET1); + + assertEq(beamState.rateLimits(TARGET1), 1, "TARGET1 should be registered"); + } + + function testAddRateLimitsRoleAuth() public { + beamState.setRoleAction(8, beamState.addRateLimits.selector, true); + beamState.setUserRole(USER1, 8, true); + + vm.prank(USER1); + beamState.addRateLimits(TARGET1); + + assertEq(beamState.rateLimits(TARGET1), 1, "TARGET1 should be registered by role user"); + } + + function testDelRateLimits() public { + beamState.addRateLimits(TARGET1); + assertEq(beamState.rateLimits(TARGET1), 1, "TARGET1 should be registered"); + + vm.expectEmit(); + emit DelRateLimits(TARGET1); + beamState.delRateLimits(TARGET1); + + assertEq(beamState.rateLimits(TARGET1), 0, "TARGET1 should not be registered after delete"); + } + + function testDelRateLimitsRoleAuth() public { + beamState.addRateLimits(TARGET1); + + beamState.setRoleAction(8, beamState.delRateLimits.selector, true); + beamState.setUserRole(USER1, 8, true); + + vm.prank(USER1); + beamState.delRateLimits(TARGET1); + + assertEq(beamState.rateLimits(TARGET1), 0, "TARGET1 should be deleted by role user"); + } + + function testAddRateLimitsMultiple() public { + beamState.addRateLimits(TARGET1); + beamState.addRateLimits(TARGET2); + + assertEq(beamState.rateLimits(TARGET1), 1, "TARGET1 should be registered"); + assertEq(beamState.rateLimits(TARGET2), 1, "TARGET2 should be registered"); + } + + // --- Controller Management Tests --- + + function testAddController() public { + assertEq(beamState.controllers(TARGET1), 0, "TARGET1 should not be registered initially"); + + vm.expectEmit(); + emit AddController(TARGET1); + beamState.addController(TARGET1); + + assertEq(beamState.controllers(TARGET1), 1, "TARGET1 should be registered"); + } + + function testAddControllerRoleAuth() public { + beamState.setRoleAction(9, beamState.addController.selector, true); + beamState.setUserRole(USER1, 9, true); + + vm.prank(USER1); + beamState.addController(TARGET1); + + assertEq(beamState.controllers(TARGET1), 1, "TARGET1 should be registered by role user"); + } + + function testDelController() public { + beamState.addController(TARGET1); + assertEq(beamState.controllers(TARGET1), 1, "TARGET1 should be registered"); + + vm.expectEmit(); + emit DelController(TARGET1); + beamState.delController(TARGET1); + + assertEq(beamState.controllers(TARGET1), 0, "TARGET1 should not be registered after delete"); + } + + function testDelControllerRoleAuth() public { + beamState.addController(TARGET1); + + beamState.setRoleAction(9, beamState.delController.selector, true); + beamState.setUserRole(USER1, 9, true); + + vm.prank(USER1); + beamState.delController(TARGET1); + + assertEq(beamState.controllers(TARGET1), 0, "TARGET1 should be deleted by role user"); + } + + function testAddControllerMultiple() public { + beamState.addController(TARGET1); + beamState.addController(TARGET2); + + assertEq(beamState.controllers(TARGET1), 1, "TARGET1 should be registered"); + assertEq(beamState.controllers(TARGET2), 1, "TARGET2 should be registered"); + } + // --- cBeam-RateLimits Mapping Tests --- function testSetCBeamForRateLimits() public { + beamState.addRateLimits(TARGET1); beamState.addCBeam(CBEAM1); assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 initially"); @@ -312,11 +423,18 @@ contract BeamStateTest is DssTest { } function testSetCBeamForRateLimitsNotExisting() public { + // Test when rateLimits not registered + vm.expectRevert("BeamState/not-existing-rateLimits"); + beamState.setCBeamForRateLimits(TARGET1, CBEAM1); + + // Test when rateLimits registered but cBeam not registered + beamState.addRateLimits(TARGET1); vm.expectRevert("BeamState/not-existing-cBeam"); beamState.setCBeamForRateLimits(TARGET1, CBEAM1); } function testSetCBeamForRateLimitsRoleAuth() public { + beamState.addRateLimits(TARGET1); beamState.addCBeam(CBEAM1); beamState.setRoleAction(4, beamState.setCBeamForRateLimits.selector, true); @@ -329,6 +447,7 @@ contract BeamStateTest is DssTest { } function testUnsetCBeamForRateLimits() public { + beamState.addRateLimits(TARGET1); beamState.addCBeam(CBEAM1); beamState.setCBeamForRateLimits(TARGET1, CBEAM1); assertEq(beamState.rateLimitsCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); @@ -341,6 +460,7 @@ contract BeamStateTest is DssTest { } function testUnsetCBeamForRateLimitsRoleAuth() public { + beamState.addRateLimits(TARGET1); beamState.addCBeam(CBEAM1); beamState.setCBeamForRateLimits(TARGET1, CBEAM1); @@ -356,6 +476,7 @@ contract BeamStateTest is DssTest { // --- cBeam-Controller Mapping Tests --- function testSetCBeamForController() public { + beamState.addController(TARGET1); beamState.addCBeam(CBEAM1); assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 0, "CBEAM1 should not be authorized for TARGET1 initially"); @@ -368,11 +489,18 @@ contract BeamStateTest is DssTest { } function testSetCBeamForControllerNotExisting() public { + // Test when controller not registered + vm.expectRevert("BeamState/not-existing-controller"); + beamState.setCBeamForController(TARGET1, CBEAM1); + + // Test when controller registered but cBeam not registered + beamState.addController(TARGET1); vm.expectRevert("BeamState/not-existing-cBeam"); beamState.setCBeamForController(TARGET1, CBEAM1); } function testSetCBeamForControllerRoleAuth() public { + beamState.addController(TARGET1); beamState.addCBeam(CBEAM1); beamState.setRoleAction(4, beamState.setCBeamForController.selector, true); @@ -385,6 +513,7 @@ contract BeamStateTest is DssTest { } function testUnsetCBeamForController() public { + beamState.addController(TARGET1); beamState.addCBeam(CBEAM1); beamState.setCBeamForController(TARGET1, CBEAM1); assertEq(beamState.controllersCBeams(TARGET1, CBEAM1), 1, "CBEAM1 should be authorized for TARGET1"); @@ -397,6 +526,7 @@ contract BeamStateTest is DssTest { } function testUnsetCBeamForControllerRoleAuth() public { + beamState.addController(TARGET1); beamState.addCBeam(CBEAM1); beamState.setCBeamForController(TARGET1, CBEAM1); @@ -606,17 +736,15 @@ contract BeamStateTest is DssTest { function testFullRoleBasedWorkflow() public { // Setup: Create roles for different operations - bytes4 hopSig = beamState.setHop.selector; - bytes4 cBeamSig = beamState.addCBeam.selector; - bytes4 limitsSig = beamState.addInitRateLimits.selector; // Role 1: Can set hop - beamState.setRoleAction(1, hopSig, true); - // Role 2: Can manage cBeams - beamState.setRoleAction(2, cBeamSig, true); + beamState.setRoleAction(1, beamState.setHop.selector, true); + // Role 2: Can manage cBeams and rateLimits + beamState.setRoleAction(2, beamState.addCBeam.selector, true); + beamState.setRoleAction(2, beamState.addRateLimits.selector, true); beamState.setRoleAction(2, beamState.setCBeamForRateLimits.selector, true); // Role 3: Can set rate limits - beamState.setRoleAction(3, limitsSig, true); + beamState.setRoleAction(3, beamState.addInitRateLimits.selector, true); // Assign roles to users beamState.setUserRole(USER1, 1, true); @@ -633,6 +761,11 @@ contract BeamStateTest is DssTest { beamState.addCBeam(CBEAM1); assertEq(beamState.cBeams(CBEAM1), 1, "USER2 should be able to add cBeam"); + // USER2 adds rateLimits + vm.prank(USER2); + beamState.addRateLimits(TARGET1); + assertEq(beamState.rateLimits(TARGET1), 1, "USER2 should be able to add rateLimits"); + // USER2 sets cBeam for rateLimits vm.prank(USER2); beamState.setCBeamForRateLimits(TARGET1, CBEAM1); diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index b182f57..1cfe2d3 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -90,6 +90,8 @@ contract ConfiguratorTest is DssTest { // --- Helper Functions --- function _setupCBeam(address target, address cBeam) internal { + beamState.addController(target); + beamState.addRateLimits(target); beamState.addCBeam(cBeam); beamState.setCBeamForController(target, cBeam); beamState.setCBeamForRateLimits(target, cBeam); From 88adf6bfc4187fa7179ec5fc11f5c2d35a9150be Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 12 Jan 2026 11:58:47 -0300 Subject: [PATCH 033/150] Add stopped functionality --- src/BeamState.sol | 14 ++++++++ src/Configurator.sol | 14 +++++--- test/BeamState.t.sol | 78 +++++++++++++++++++++++++++++++++++++++++ test/Configurator.t.sol | 55 +++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 4 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 43b7e68..8802af0 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -18,6 +18,8 @@ contract BeamState { mapping(address rateLimits_ => uint256 value) public hop; // rateLimits == address(0) => general backup configuration mapping(address rateLimits_ => uint256 value) public maxChange; // rateLimits == address(0) => general backup configuration + bool public stopped; + struct DefaultRateLimits { uint256 maxAmount; uint256 slope; @@ -33,6 +35,8 @@ contract BeamState { event Deny(address indexed usr); event SetUserRole(address indexed who, uint8 indexed role, bool enabled); event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); + event Stop(); + event Start(); event SetHop(address indexed rateLimits_, uint256 value); event SetMaxChange(address indexed rateLimits_, uint256 value); event AddCBeam(address indexed cBeam); @@ -137,6 +141,16 @@ contract BeamState { // --- Role authed functions --- + function stop() external roleAuth { + stopped = true; + emit Stop(); + } + + function start()external roleAuth { + stopped = false; + emit Start(); + } + function setHop(address rateLimits_, uint256 value) external roleAuth { hop[rateLimits_] = value; emit SetHop(rateLimits_, value); diff --git a/src/Configurator.sol b/src/Configurator.sol index 81972c2..6a2d40a 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.21; interface BeamStateLike { - function getHop(address) external view returns (uint256); - function getMaxChange(address) external view returns (uint256); function controllersCBeams(address, address) external view returns (uint256); function rateLimitsCBeams(address, address) external view returns (uint256); + function stopped() external view returns (bool); + function getHop(address) external view returns (uint256); + function getMaxChange(address) external view returns (uint256); function getInitRateLimits(bytes32, address) external view returns (uint256, uint256); function isControllerActionEnabled(bytes32, address) external view returns (bool); } @@ -45,6 +46,11 @@ contract Configurator { // --- Modifiers --- + modifier notStopped() { + require(!beamState.stopped(), "Configurator/stopped"); + _; + } + modifier authController(address controller) { require(beamState.controllersCBeams(controller, msg.sender) == 1, "Configurator/not-authorized-controller-cBeam"); _; @@ -69,7 +75,7 @@ contract Configurator { // cBeams functions - function setRateLimit(address rateLimits, bytes32 key, uint256 maxAmount, uint256 slope) external authRateLimits(rateLimits) { + function setRateLimit(address rateLimits, bytes32 key, uint256 maxAmount, uint256 slope) external notStopped authRateLimits(rateLimits) { (uint256 defMaxAmount, uint256 defSlope) = beamState.getInitRateLimits(key, rateLimits); if (defMaxAmount == type(uint256).max && defSlope == 0) { RateLimitsLike(rateLimits).setUnlimitedRateLimitData(key); @@ -90,7 +96,7 @@ contract Configurator { } } - function callControllerAction(address controller, bytes calldata data) external authController(controller) returns (bytes memory ret) { + function callControllerAction(address controller, bytes calldata data) external notStopped authController(controller) returns (bytes memory ret) { require(beamState.isControllerActionEnabled(keccak256(data), controller), "Configurator/not-valid-data"); bool ok; (ok, ret) = controller.call(data); diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 9db297a..48a1f5c 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -17,6 +17,8 @@ contract BeamStateTest is DssTest { event SetUserRole(address indexed who, uint8 indexed role, bool enabled); event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); + event Stop(); + event Start(); event SetHop(address indexed rateLimits, uint256 value); event SetMaxChange(address indexed rateLimits, uint256 value); event AddRateLimits(address indexed rateLimits_); @@ -825,4 +827,80 @@ contract BeamStateTest is DssTest { assertEq(limits.maxAmount, type(uint256).max, "TARGET2 should fallback to global unlimited maxAmount"); assertEq(limits.slope, 0, "TARGET2 should fallback to global slope of 0 for unlimited"); } + + // --- Stop/Start Tests --- + + function testStop() public { + assertFalse(beamState.stopped(), "BeamState should not be stopped initially"); + + vm.expectEmit(); + emit Stop(); + beamState.stop(); + + assertTrue(beamState.stopped(), "BeamState should be stopped after calling stop"); + } + + function testStopRoleAuth() public { + beamState.setRoleAction(7, beamState.stop.selector, true); + beamState.setUserRole(USER1, 7, true); + + vm.prank(USER1); + beamState.stop(); + + assertTrue(beamState.stopped(), "BeamState should be stopped by role user"); + } + + function testStopNotAuthorized() public { + vm.prank(USER1); + vm.expectRevert("BeamState/role-not-authorized"); + beamState.stop(); + } + + function testStart() public { + beamState.stop(); + assertTrue(beamState.stopped(), "BeamState should be stopped"); + + vm.expectEmit(); + emit Start(); + beamState.start(); + + assertFalse(beamState.stopped(), "BeamState should not be stopped after calling start"); + } + + function testStartRoleAuth() public { + beamState.stop(); + + beamState.setRoleAction(7, beamState.start.selector, true); + beamState.setUserRole(USER1, 7, true); + + vm.prank(USER1); + beamState.start(); + + assertFalse(beamState.stopped(), "BeamState should be started by role user"); + } + + function testStartNotAuthorized() public { + beamState.stop(); + + vm.prank(USER1); + vm.expectRevert("BeamState/role-not-authorized"); + beamState.start(); + } + + function testMultipleStopCalls() public { + beamState.stop(); + assertTrue(beamState.stopped(), "BeamState should be stopped"); + + // Calling stop again should not revert + beamState.stop(); + assertTrue(beamState.stopped(), "BeamState should still be stopped"); + } + + function testMultipleStartCalls() public { + assertFalse(beamState.stopped(), "BeamState should not be stopped initially"); + + // Calling start when not stopped should not revert + beamState.start(); + assertFalse(beamState.stopped(), "BeamState should still not be stopped"); + } } diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 1cfe2d3..419b9a2 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -737,4 +737,59 @@ contract ConfiguratorTest is DssTest { assertGt(configurator.zzz(address(target1), key1), 0, "key1 should have zzz set"); assertGt(configurator.zzz(address(target1), key2), 0, "key2 should have zzz set"); } + + // --- Stop/Start Tests --- + + function testSetRateLimitStoppedAndAfterRestart() public { + bytes32 key = keccak256("test-key"); + + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + // Stop the BeamState + beamState.stop(); + + // Try to set rate limit - should revert + vm.prank(CBEAM1); + vm.expectRevert("Configurator/stopped"); + configurator.setRateLimit(address(target1), key, 600 * WAD, 6 * WAD); + + // Restart the BeamState + beamState.start(); + + // Now it should work + vm.prank(CBEAM1); + configurator.setRateLimit(address(target1), key, 600 * WAD, 6 * WAD); + + RateLimitsLike.RateLimitData memory result = target1.getRateLimitData(key); + assertEq(result.maxAmount, 600 * WAD, "maxAmount should be updated after restart"); + } + + function testCallControllerActionStoppedAndAfterRestart() public { + _setupCBeam(address(target1), CBEAM1); + + bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 123); + bytes32 key = keccak256(data); + beamState.addInitControllerActions(data, address(0)); // Global whitelist + + // Stop the BeamState + beamState.stop(); + + // Try to call controller action - should revert + vm.prank(CBEAM1); + vm.expectRevert("Configurator/stopped"); + configurator.callControllerAction(address(target1), data); + + // Restart the BeamState + beamState.start(); + + // Now it should work + vm.prank(CBEAM1); + bytes memory ret = configurator.callControllerAction(address(target1), data); + + uint256 result = abi.decode(ret, (uint256)); + assertEq(result, 246, "controller function should return 123 * 2"); + assertEq(target1.lastCallData(), data, "correct data should be passed to target"); + } } From 15502150012b21371598ab883596e94ac854cb13 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 12 Jan 2026 12:59:54 -0300 Subject: [PATCH 034/150] Roles.md --- ROLES.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 ROLES.md diff --git a/ROLES.md b/ROLES.md new file mode 100644 index 0000000..b727bf4 --- /dev/null +++ b/ROLES.md @@ -0,0 +1,34 @@ +# Roles functions + +## PauseProxy + +- BeamState.* + +## Core Council timelocked + +- BeamState.start +- BeamState.setHop +- BeamState.setMaxChange +- BeamState.addRateLimits +- BeamState.addController +- BeamState.addCBeam +- BeamState.addInitRateLimits +- BeamState.addInitControllerActions + +## Core Council direct + +- BeamState.stop +- BeamState.delRateLimits +- BeamState.delController +- BeamState.delCBeam +- BeamState.setCBeamForRateLimits +- BeamState.unsetCBeamForRateLimits +- BeamState.setCBeamForController +- BeamState.unsetCBeamForController +- BeamState.delInitRateLimits +- BeamState.delInitControllerActions + +## Gov Ops + +- Configurator.setRateLimit +- Configurator.callControllerAction From c2a1845d4ff177b4483827465ade4491d701923c Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:54:42 +0700 Subject: [PATCH 035/150] Minor Change --- src/timelock/ATWLTimeLockedWrapper.sol | 47 +++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/timelock/ATWLTimeLockedWrapper.sol b/src/timelock/ATWLTimeLockedWrapper.sol index 8161c8e..c606cee 100644 --- a/src/timelock/ATWLTimeLockedWrapper.sol +++ b/src/timelock/ATWLTimeLockedWrapper.sol @@ -43,14 +43,17 @@ interface BeamStateLike { } interface MainnetControllerLike { + // Spark functions + function grantRole(bytes32 role, address account) external; + function revokeRole(bytes32 role, address account) external; function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; function setLayerZeroRecipient(uint32 destinationEndpointId, bytes32 layerZeroRecipient) external; function setMaxSlippage(address pool, uint256 maxSlippage) external; - function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; function setOTCBuffer(address exchange, address otcBuffer) external; function setOTCRechargeRate(address exchange, uint256 rechargeRate18) external; function setOTCWhitelistedAsset(address exchange, address asset, bool isWhitelisted) external; function setUniswapV4TickLimits(bytes32 poolId, int24 tickLowerMin, int24 tickUpperMax, uint24 maxTickSpacing) external; + function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; // Grove-only functions function setCentrifugeRecipient(uint16 centrifugeId, bytes32 recipient) external; function setUniswapV3PoolLowerTick(address pool, int24 lowerTick) external; @@ -248,6 +251,48 @@ contract ATWLTimeLockedWrapper { // --- Controller Actions --- + // Spark functions + + // Common roles: RELAYER = keccak256("RELAYER"), FREEZER = keccak256("FREEZER") + // Role bytes32 can be computed off-chain and passed as parameter + function grantRole( + address controller, + bytes32 role, + address account, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.grantRole.selector, + role, + account + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "grantRole"); + } + + function revokeRole( + address controller, + bytes32 role, + address account, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + MainnetControllerLike.revokeRole.selector, + role, + account + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "revokeRole"); + } + function setMintRecipient( uint32 destinationDomain, bytes32 mintRecipient, From 92f55cfaef334b770f0a1c1bea07102945389014 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:14:50 +0700 Subject: [PATCH 036/150] File renamings --- .../{SkyTimelock.sol => Timelock.sol} | 10 +++--- src/timelock/TimelockKeeperJob.sol | 8 ++--- ...eLockedWrapper.sol => TimelockWrapper.sol} | 14 ++++---- .../{SkyTimelock.t.sol => Timelock.t.sol} | 32 +++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) rename src/timelock/{SkyTimelock.sol => Timelock.sol} (95%) rename src/timelock/{ATWLTimeLockedWrapper.sol => TimelockWrapper.sol} (98%) rename test/timelock/{SkyTimelock.t.sol => Timelock.t.sol} (96%) diff --git a/src/timelock/SkyTimelock.sol b/src/timelock/Timelock.sol similarity index 95% rename from src/timelock/SkyTimelock.sol rename to src/timelock/Timelock.sol index a73ce92..5bbc98a 100644 --- a/src/timelock/SkyTimelock.sol +++ b/src/timelock/Timelock.sol @@ -20,7 +20,7 @@ import { TimelockController } from "@openzeppelin/contracts/governance/TimelockC import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; import { EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -contract SkyTimelock is TimelockController, Pausable { +contract Timelock is TimelockController, Pausable { using EnumerableSet for EnumerableSet.Bytes32Set; bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); @@ -58,7 +58,7 @@ contract SkyTimelock is TimelockController, Pausable { ) TimelockController(minDelay, proposers, new address[](0), admin) { _revokeRole(DEFAULT_ADMIN_ROLE, address(this)); // do not allow proposers to change admin-only configurations - require(admin != address(0), "SkyTimelock/admin-zero-address"); + require(admin != address(0), "Timelock/admin-zero-address"); // add cancellers which are not necessarily proposers for (uint256 i = 0; i < cancellers.length; ++i) { @@ -98,7 +98,7 @@ contract SkyTimelock is TimelockController, Pausable { // ------------------------------------------------------------------------ function schedule(address, uint256, bytes calldata, bytes32, bytes32, uint256) public pure override { - revert("SkyTimelock/use-scheduleBatch"); + revert("Timelock/use-scheduleBatch"); } function scheduleBatch( @@ -111,7 +111,7 @@ contract SkyTimelock is TimelockController, Pausable { ) public virtual override whenNotPaused { uint256 len = targets.length; for (uint256 i = 0; i < len; ++i) { - require(targets[i] != address(this), "SkyTimelock/self-calls-disabled"); + require(targets[i] != address(this), "Timelock/self-calls-disabled"); } super.scheduleBatch(targets, values, payloads, predecessor, salt, delay); @@ -133,7 +133,7 @@ contract SkyTimelock is TimelockController, Pausable { // ------------------------------------------------------------------------ function execute(address, uint256, bytes calldata, bytes32, bytes32) public payable override { - revert("SkyTimelock/use-executeBatch"); + revert("Timelock/use-executeBatch"); } function executeBatch( diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol index 731daa3..ffd6a42 100644 --- a/src/timelock/TimelockKeeperJob.sol +++ b/src/timelock/TimelockKeeperJob.sol @@ -24,7 +24,7 @@ interface SequencerLike { function isMaster(bytes32 network) external view returns (bool); } -interface SkyTimelockLike { +interface TimelockLike { struct Operation { address[] targets; uint256[] values; @@ -47,7 +47,7 @@ interface SkyTimelockLike { contract TimelockKeeperJob is IJob { SequencerLike public immutable sequencer; - SkyTimelockLike public immutable timelock; + TimelockLike public immutable timelock; // --- Errors --- error NotMaster(bytes32 network); @@ -58,7 +58,7 @@ contract TimelockKeeperJob is IJob { constructor(address _sequencer, address _timelock) { sequencer = SequencerLike(_sequencer); - timelock = SkyTimelockLike(_timelock); + timelock = TimelockLike(_timelock); } function work(bytes32 network, bytes calldata) external { @@ -67,7 +67,7 @@ contract TimelockKeeperJob is IJob { bytes32 id = timelock.getNextExecutableOperation(); if (id == bytes32(0)) revert NoExecutableOperation(); - SkyTimelockLike.Operation memory op = timelock.getOperation(id); + TimelockLike.Operation memory op = timelock.getOperation(id); // Assume that in case a proposal needs eth the timelock is pre-funded, or alternatively it is executed manually timelock.executeBatch{value: 0}(op.targets, op.values, op.payloads, op.predecessor, op.salt); diff --git a/src/timelock/ATWLTimeLockedWrapper.sol b/src/timelock/TimelockWrapper.sol similarity index 98% rename from src/timelock/ATWLTimeLockedWrapper.sol rename to src/timelock/TimelockWrapper.sol index c606cee..f380f1e 100644 --- a/src/timelock/ATWLTimeLockedWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -22,7 +22,7 @@ pragma solidity ^0.8.21; // TODO: Consider renaming parameters for clarity - `rateLimits_` for rate limit functions, `controller` for controller functions -import { SkyTimelock } from "src/timelock/SkyTimelock.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; interface BeamStateLike { // Rate limit functions (timelocked) @@ -69,18 +69,18 @@ struct RateLimitConfig { uint256 slope; } -contract ATWLTimeLockedWrapper { +contract TimelockWrapper { // --- Auth --- mapping(address => uint256) public wards; mapping(address => uint256) public buds; modifier auth() { - require(wards[msg.sender] == 1, "ATWLTimeLockedWrapper/not-authorized"); + require(wards[msg.sender] == 1, "TimelockWrapper/not-authorized"); _; } modifier toll() { - require(buds[msg.sender] == 1, "ATWLTimeLockedWrapper/not-whitelisted"); + require(buds[msg.sender] == 1, "TimelockWrapper/not-whitelisted"); _; } @@ -111,12 +111,12 @@ contract ATWLTimeLockedWrapper { event Diss(address indexed usr); event ProposalSubmitted(bytes32 indexed operationId, string functionName); - SkyTimelock public immutable timelock; + Timelock public immutable timelock; BeamStateLike public immutable beamState; address public immutable mainnetController; // TODO: unused for now constructor(address timelock_, address beamState_, address mainnetController_) { - timelock = SkyTimelock(payable(timelock_)); + timelock = Timelock(payable(timelock_)); beamState = BeamStateLike(beamState_); mainnetController = mainnetController_; @@ -225,7 +225,7 @@ contract ATWLTimeLockedWrapper { uint256 delay ) external toll returns (bytes32 operationId) { uint256 len = configs.length; - require(len > 0, "ATWLTimeLockedWrapper/empty-configs"); + require(len > 0, "TimelockWrapper/empty-configs"); address[] memory targets = new address[](len); uint256[] memory values = new uint256[](len); diff --git a/test/timelock/SkyTimelock.t.sol b/test/timelock/Timelock.t.sol similarity index 96% rename from test/timelock/SkyTimelock.t.sol rename to test/timelock/Timelock.t.sol index e52b5d6..a162313 100644 --- a/test/timelock/SkyTimelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -17,7 +17,7 @@ pragma solidity ^0.8.21; import {Test} from "forge-std/Test.sol"; -import {SkyTimelock} from "../../src/timelock/SkyTimelock.sol"; +import {Timelock} from "../../src/timelock/Timelock.sol"; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; @@ -49,8 +49,8 @@ contract MockTarget { } } -contract SkyTimelockTest is Test { - SkyTimelock public timelock; +contract TimelockTest is Test { + Timelock public timelock; MockTarget public mockTarget; address public admin; @@ -132,7 +132,7 @@ contract SkyTimelockTest is Test { address[] memory pausers = new address[](1); pausers[0] = pauser; - timelock = new SkyTimelock(MIN_DELAY, admin, proposers, cancellers, pausers); + timelock = new Timelock(MIN_DELAY, admin, proposers, cancellers, pausers); mockTarget = new MockTarget(); } @@ -164,8 +164,8 @@ contract SkyTimelockTest is Test { address[] memory cancellers = new address[](0); address[] memory pausers = new address[](0); - vm.expectRevert("SkyTimelock/admin-zero-address"); - new SkyTimelock(MIN_DELAY, address(0), proposers, cancellers, pausers); + vm.expectRevert("Timelock/admin-zero-address"); + new Timelock(MIN_DELAY, address(0), proposers, cancellers, pausers); } function testConstructorMultipleProposers() public { @@ -175,7 +175,7 @@ contract SkyTimelockTest is Test { address[] memory cancellers = new address[](0); address[] memory pausers = new address[](0); - SkyTimelock newTimelock = new SkyTimelock(MIN_DELAY, admin, proposers, cancellers, pausers); + Timelock newTimelock = new Timelock(MIN_DELAY, admin, proposers, cancellers, pausers); assertTrue(newTimelock.hasRole(newTimelock.PROPOSER_ROLE(), address(0x10))); assertTrue(newTimelock.hasRole(newTimelock.PROPOSER_ROLE(), address(0x11))); } @@ -187,7 +187,7 @@ contract SkyTimelockTest is Test { function testScheduleReverts() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); vm.prank(proposer); - vm.expectRevert("SkyTimelock/use-scheduleBatch"); + vm.expectRevert("Timelock/use-scheduleBatch"); timelock.schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); } @@ -234,7 +234,7 @@ contract SkyTimelockTest is Test { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); vm.prank(proposer); - vm.expectRevert("SkyTimelock/self-calls-disabled"); + vm.expectRevert("Timelock/self-calls-disabled"); _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); } @@ -299,7 +299,7 @@ contract SkyTimelockTest is Test { payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); vm.prank(proposer); - vm.expectRevert("SkyTimelock/self-calls-disabled"); + vm.expectRevert("Timelock/self-calls-disabled"); timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); } @@ -313,7 +313,7 @@ contract SkyTimelockTest is Test { _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); - vm.expectRevert("SkyTimelock/use-executeBatch"); + vm.expectRevert("Timelock/use-executeBatch"); timelock.execute(address(mockTarget), 0, data, bytes32(0), SALT); } @@ -658,13 +658,13 @@ contract SkyTimelockTest is Test { function testUpdateDelayImmediatelyProposerCannotChangeDelay() public { bytes memory data = abi.encodeWithSelector( - SkyTimelock.updateDelayImmediately.selector, + Timelock.updateDelayImmediately.selector, 2 days ); // Try to schedule a call to updateDelayImmediately vm.prank(proposer); - vm.expectRevert("SkyTimelock/self-calls-disabled"); + vm.expectRevert("Timelock/self-calls-disabled"); _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); } @@ -697,7 +697,7 @@ contract SkyTimelockTest is Test { ); vm.prank(proposer); - vm.expectRevert("SkyTimelock/self-calls-disabled"); + vm.expectRevert("Timelock/self-calls-disabled"); _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); } @@ -715,7 +715,7 @@ contract SkyTimelockTest is Test { ); vm.prank(proposer); - vm.expectRevert("SkyTimelock/self-calls-disabled"); + vm.expectRevert("Timelock/self-calls-disabled"); timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); } @@ -727,7 +727,7 @@ contract SkyTimelockTest is Test { ); vm.prank(proposer); - vm.expectRevert("SkyTimelock/self-calls-disabled"); + vm.expectRevert("Timelock/self-calls-disabled"); _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); } From 1fde3f0258efba52d58d10b3c66df874aeaeda27 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:17:15 +0700 Subject: [PATCH 037/150] Remove comments --- src/timelock/TimelockWrapper.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index f380f1e..db0e70f 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -16,12 +16,9 @@ pragma solidity ^0.8.21; -// PAU code aligned to: https://github.com/sunbreak1211/pau/blob/a3e4b519c238d32c08c82145f04341144361e943/src/Option_4/Configurator.sol // Spark Mainnet controller aligned to: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol // Grove Mainnet controller aligned to: https://github.com/grove-labs/grove-alm-controller/blob/548c96fa22bcb13afd25cb592ec8cb4bb98c2d86/src/MainnetController.sol -// TODO: Consider renaming parameters for clarity - `rateLimits_` for rate limit functions, `controller` for controller functions - import { Timelock } from "src/timelock/Timelock.sol"; interface BeamStateLike { From c952fa73327ec3e0dc70bfbb8948d98cb9814dcb Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:37:11 +0700 Subject: [PATCH 038/150] Style changes and notes --- src/timelock/TimelockWrapper.sol | 125 ++++++++++++------------------- 1 file changed, 46 insertions(+), 79 deletions(-) diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index db0e70f..240da4e 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -16,30 +16,20 @@ pragma solidity ^0.8.21; -// Spark Mainnet controller aligned to: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol -// Grove Mainnet controller aligned to: https://github.com/grove-labs/grove-alm-controller/blob/548c96fa22bcb13afd25cb592ec8cb4bb98c2d86/src/MainnetController.sol - import { Timelock } from "src/timelock/Timelock.sol"; interface BeamStateLike { - // Rate limit functions (timelocked) + function start() external; function setHop(address rateLimits_, uint256 value) external; function setMaxChange(address rateLimits_, uint256 value) external; function addRateLimits(address rateLimits_) external; - function addInitRateLimits(bytes32 key, address rateLimits_, uint256 maxAmount, uint256 slope) external; - - // Controller functions (timelocked) function addController(address rateLimits_) external; - function addInitControllerActions(bytes calldata data, address controller) external returns (bytes32 key); - - // CBeam functions (timelocked) function addCBeam(address cBeam) external; - - // System functions (timelocked) - function start() external; + function addInitRateLimits(bytes32 key, address rateLimits_, uint256 maxAmount, uint256 slope) external; + function addInitControllerActions(bytes calldata data, address controller) external returns (bytes32 key); } -interface MainnetControllerLike { +interface ControllerLike { // Spark functions function grantRole(bytes32 role, address account) external; function revokeRole(bytes32 role, address account) external; @@ -66,6 +56,14 @@ struct RateLimitConfig { uint256 slope; } +// Spark Mainnet controller aligned to: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol +// Grove Mainnet controller aligned to: https://github.com/grove-labs/grove-alm-controller/blob/548c96fa22bcb13afd25cb592ec8cb4bb98c2d86/src/MainnetController.sol + +// Notes: +// - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). +// - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. +// - Only the addInitRateLimits calls can be batched in this wrapper, other calls can use the predecessor mechanism to enforce ordering. +// - As part of a controller onboarding it should be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. contract TimelockWrapper { // --- Auth --- mapping(address => uint256) public wards; @@ -108,14 +106,12 @@ contract TimelockWrapper { event Diss(address indexed usr); event ProposalSubmitted(bytes32 indexed operationId, string functionName); - Timelock public immutable timelock; + Timelock public immutable timelock; BeamStateLike public immutable beamState; - address public immutable mainnetController; // TODO: unused for now - constructor(address timelock_, address beamState_, address mainnetController_) { - timelock = Timelock(payable(timelock_)); - beamState = BeamStateLike(beamState_); - mainnetController = mainnetController_; + constructor(address timelock_, address beamState_) { + timelock = Timelock(payable(timelock_)); + beamState = BeamStateLike(beamState_); wards[msg.sender] = 1; emit Rely(msg.sender); @@ -135,67 +131,41 @@ contract TimelockWrapper { timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); } - // --- System Functions --- - function start(bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { bytes memory payload = abi.encodeWithSelector(BeamStateLike.start.selector); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "start"); } - // --- Configuration Parameters --- - - function setHop(address rateLimits_, uint256 value, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector( - BeamStateLike.setHop.selector, - rateLimits_, - value - ); - + function setHop(address rateLimits, uint256 hop, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.setHop.selector, rateLimits, hop); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setHop"); } - function setMaxChange(address rateLimits_, uint256 value, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector( - BeamStateLike.setMaxChange.selector, - rateLimits_, - value - ); - + function setMaxChange(address rateLimits, uint256 maxChange, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.setMaxChange.selector, rateLimits, maxChange); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxChange"); } - // --- CBeam Management --- - - function addCBeam(address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector( - BeamStateLike.addCBeam.selector, - cBeam - ); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "addCBeam"); - } - - // --- Rate Limits Management --- - - function addRateLimits(address rateLimits_, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addRateLimits.selector, rateLimits_); + function addRateLimits(address rateLimits, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addRateLimits.selector, rateLimits); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "addRateLimits"); } - // --- Controller Management --- - - function addController(address rateLimits_, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addController.selector, rateLimits_); + function addController(address controller, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addController.selector, controller); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "addController"); } - // --- Rate Limits --- + function addCBeam(address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addCBeam.selector, cBeam); + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "addCBeam"); + } function addInitRateLimits( RateLimitConfig calldata config, @@ -222,15 +192,13 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { uint256 len = configs.length; - require(len > 0, "TimelockWrapper/empty-configs"); address[] memory targets = new address[](len); uint256[] memory values = new uint256[](len); bytes[] memory payloads = new bytes[](len); - address target = address(beamState); for (uint256 i = 0; i < len; ++i) { - targets[i] = target; + targets[i] = address(beamState); payloads[i] = abi.encodeWithSelector( BeamStateLike.addInitRateLimits.selector, configs[i].key, @@ -250,8 +218,7 @@ contract TimelockWrapper { // Spark functions - // Common roles: RELAYER = keccak256("RELAYER"), FREEZER = keccak256("FREEZER") - // Role bytes32 can be computed off-chain and passed as parameter + // Role bytes32 can be computed off-chain and passed as parameter, e.g keccak256("RELAYER"), keccak256("FREEZER") function grantRole( address controller, bytes32 role, @@ -261,7 +228,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.grantRole.selector, + ControllerLike.grantRole.selector, role, account ); @@ -280,7 +247,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.revokeRole.selector, + ControllerLike.revokeRole.selector, role, account ); @@ -299,7 +266,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setMintRecipient.selector, + ControllerLike.setMintRecipient.selector, destinationDomain, mintRecipient ); @@ -318,7 +285,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setLayerZeroRecipient.selector, + ControllerLike.setLayerZeroRecipient.selector, destinationEndpointId, layerZeroRecipient ); @@ -337,7 +304,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setMaxSlippage.selector, + ControllerLike.setMaxSlippage.selector, pool, maxSlippage ); @@ -356,7 +323,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setOTCBuffer.selector, + ControllerLike.setOTCBuffer.selector, exchange, otcBuffer ); @@ -375,7 +342,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setOTCRechargeRate.selector, + ControllerLike.setOTCRechargeRate.selector, exchange, rechargeRate18 ); @@ -395,7 +362,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setOTCWhitelistedAsset.selector, + ControllerLike.setOTCWhitelistedAsset.selector, exchange, asset, isWhitelisted @@ -417,7 +384,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setUniswapV4TickLimits.selector, + ControllerLike.setUniswapV4TickLimits.selector, poolId, tickLowerMin, tickUpperMax, @@ -439,7 +406,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setMaxExchangeRate.selector, + ControllerLike.setMaxExchangeRate.selector, token, shares, maxExpectedAssets @@ -461,7 +428,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setCentrifugeRecipient.selector, + ControllerLike.setCentrifugeRecipient.selector, centrifugeId, recipient ); @@ -480,7 +447,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setUniswapV3PoolLowerTick.selector, + ControllerLike.setUniswapV3PoolLowerTick.selector, pool, lowerTick ); @@ -499,7 +466,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setUniswapV3PoolUpperTick.selector, + ControllerLike.setUniswapV3PoolUpperTick.selector, pool, upperTick ); @@ -518,7 +485,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setUniswapV3PoolMaxTickDelta.selector, + ControllerLike.setUniswapV3PoolMaxTickDelta.selector, pool, maxTickDelta ); @@ -537,7 +504,7 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - MainnetControllerLike.setUniswapV3PoolTwapSecondsAgo.selector, + ControllerLike.setUniswapV3PoolTwapSecondsAgo.selector, pool, twapSecondsAgo ); From e60b6b8768c537de72dda6c6a65614113abd49ed Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:45:24 +0700 Subject: [PATCH 039/150] README.md -> ROLES-TIMELOCK.md --- README.md => ROLES-TIMELOCK.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.md => ROLES-TIMELOCK.md (100%) diff --git a/README.md b/ROLES-TIMELOCK.md similarity index 100% rename from README.md rename to ROLES-TIMELOCK.md From 4be40c775de599c4254758fbe6a2648f1d24d819 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:01:07 +0700 Subject: [PATCH 040/150] Pause to block also cancelling --- src/timelock/Timelock.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 5bbc98a..08012ec 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -122,7 +122,7 @@ contract Timelock is TimelockController, Pausable { operations[id] = Operation(targets, values, payloads, predecessor, salt); } - function cancel(bytes32 id) public virtual override { + function cancel(bytes32 id) public virtual override whenNotPaused { super.cancel(id); _operationIds.remove(id); delete operations[id]; From 0e8b66e2bf77bb2b9f4fd5cced1fa32a9970ce20 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:33:21 +0700 Subject: [PATCH 041/150] Remove Timelock test until ready --- test/timelock/Timelock.t.sol | 852 ----------------------------------- 1 file changed, 852 deletions(-) delete mode 100644 test/timelock/Timelock.t.sol diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol deleted file mode 100644 index a162313..0000000 --- a/test/timelock/Timelock.t.sol +++ /dev/null @@ -1,852 +0,0 @@ -// SPDX-FileCopyrightText: © 2025 Dai Foundation -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.21; - -import {Test} from "forge-std/Test.sol"; -import {Timelock} from "../../src/timelock/Timelock.sol"; -import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; - -// Mock contract for testing timelock operations -contract MockTarget { - uint256 public value; - bool public called; - bytes public lastData; - uint256 public lastValue; - - function setValue(uint256 _value) external { - value = _value; - called = true; - } - - function setValueWithData(uint256 _value, bytes calldata _data) external { - value = _value; - lastData = _data; - called = true; - } - - function receiveEther() external payable { - lastValue = msg.value; - called = true; - } - - function revertCall() external pure { - revert("MockTarget: intentional revert"); - } -} - -contract TimelockTest is Test { - Timelock public timelock; - MockTarget public mockTarget; - - address public admin; - address public proposer; - address public canceller; - address public pauser; - address public executor; - address public other; - - uint256 public constant MIN_DELAY = 1 days; - bytes32 public constant SALT = keccak256("test-salt"); - - // Events for easier testing - event CallScheduled( - bytes32 indexed id, - uint256 indexed index, - address target, - uint256 value, - bytes data, - bytes32 predecessor, - uint256 delay - ); - - event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data); - - event Cancelled(bytes32 indexed id); - event MinDelayChange(uint256 oldDuration, uint256 newDuration); - event Paused(address account); - event Unpaused(address account); - - // Helper functions to convert single operations to batch format - function _schedule( - address target, - uint256 value, - bytes memory data, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) internal { - address[] memory targets = new address[](1); - targets[0] = target; - uint256[] memory values = new uint256[](1); - values[0] = value; - bytes[] memory payloads = new bytes[](1); - payloads[0] = data; - timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); - } - - function _execute( - address target, - uint256 value, - bytes memory data, - bytes32 predecessor, - bytes32 salt - ) internal { - address[] memory targets = new address[](1); - targets[0] = target; - uint256[] memory values = new uint256[](1); - values[0] = value; - bytes[] memory payloads = new bytes[](1); - payloads[0] = data; - timelock.executeBatch{value: value}(targets, values, payloads, predecessor, salt); - } - - function setUp() public { - admin = address(0x1); - proposer = address(0x2); - canceller = address(0x3); - pauser = address(0x4); - executor = address(0x5); - other = address(0x6); - - address[] memory proposers = new address[](1); - proposers[0] = proposer; - - address[] memory cancellers = new address[](1); - cancellers[0] = canceller; - - address[] memory pausers = new address[](1); - pausers[0] = pauser; - - timelock = new Timelock(MIN_DELAY, admin, proposers, cancellers, pausers); - mockTarget = new MockTarget(); - } - - // ============================================================================ - // Constructor and Initialization Tests - // ============================================================================ - - function testConstructorInitialization() public { - // Check min delay - assertEq(timelock.getMinDelay(), MIN_DELAY); - - // Check roles are set correctly - assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), admin)); - assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), proposer)); - // Proposers are automatically given CANCELLER_ROLE by parent constructor - assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), proposer)); - assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller)); - assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser)); - // address(0) means anyone can execute - assertTrue(timelock.hasRole(timelock.EXECUTOR_ROLE(), address(0))); - - // Key security feature: contract itself should not have admin role - assertFalse(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), address(timelock))); - } - - function testConstructorRevertsIfAdminIsZero() public { - address[] memory proposers = new address[](1); - proposers[0] = proposer; - address[] memory cancellers = new address[](0); - address[] memory pausers = new address[](0); - - vm.expectRevert("Timelock/admin-zero-address"); - new Timelock(MIN_DELAY, address(0), proposers, cancellers, pausers); - } - - function testConstructorMultipleProposers() public { - address[] memory proposers = new address[](2); - proposers[0] = address(0x10); - proposers[1] = address(0x11); - address[] memory cancellers = new address[](0); - address[] memory pausers = new address[](0); - - Timelock newTimelock = new Timelock(MIN_DELAY, admin, proposers, cancellers, pausers); - assertTrue(newTimelock.hasRole(newTimelock.PROPOSER_ROLE(), address(0x10))); - assertTrue(newTimelock.hasRole(newTimelock.PROPOSER_ROLE(), address(0x11))); - } - - // ============================================================================ - // Scheduling Tests - // ============================================================================ - - function testScheduleReverts() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - vm.prank(proposer); - vm.expectRevert("Timelock/use-scheduleBatch"); - timelock.schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - } - - function testScheduleSingleOperation() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); - - vm.prank(proposer); - vm.expectEmit(true, true, true, true); - emit CallScheduled(id, 0, address(mockTarget), 0, data, bytes32(0), MIN_DELAY); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - assertEq(uint256(timelock.getTimestamp(id)), block.timestamp + MIN_DELAY); - } - - function testScheduleRevertsIfNotProposer() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(other); - vm.expectRevert(); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - } - - function testScheduleRevertsIfDelayTooShort() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - vm.expectRevert(); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY - 1); - } - - function testScheduleRevertsIfPaused() public { - vm.prank(pauser); - timelock.pause(); - - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - vm.expectRevert(); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - } - - function testScheduleRevertsOnSelfCall() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - vm.expectRevert("Timelock/self-calls-disabled"); - _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); - } - - function testScheduleWithValue() public { - bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); - bytes32 id = _hashOperation(address(mockTarget), 1 ether, data, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); - - assertEq(uint256(timelock.getTimestamp(id)), block.timestamp + MIN_DELAY); - } - - function testScheduleWithPredecessor() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); - - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("salt2")); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data2, id1, keccak256("salt2"), MIN_DELAY); - - assertEq(uint256(timelock.getTimestamp(id2)), block.timestamp + MIN_DELAY); - } - - function testScheduleBatchMultipleOperations() public { - address[] memory targets = new address[](2); - targets[0] = address(mockTarget); - targets[1] = address(mockTarget); - - uint256[] memory values = new uint256[](2); - values[0] = 0; - values[1] = 0; - - bytes[] memory payloads = new bytes[](2); - payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); - payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); - - bytes32 id = _hashOperationBatch(targets, values, payloads, bytes32(0), SALT); - - vm.prank(proposer); - timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); - - assertEq(uint256(timelock.getTimestamp(id)), block.timestamp + MIN_DELAY); - } - - function testScheduleBatchRevertsOnSelfCall() public { - address[] memory targets = new address[](2); - targets[0] = address(mockTarget); - targets[1] = address(timelock); // Self call - - uint256[] memory values = new uint256[](2); - values[0] = 0; - values[1] = 0; - - bytes[] memory payloads = new bytes[](2); - payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); - payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); - - vm.prank(proposer); - vm.expectRevert("Timelock/self-calls-disabled"); - timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); - } - - // ============================================================================ - // Execution Tests - // ============================================================================ - - function testExecuteReverts() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - vm.warp(block.timestamp + MIN_DELAY); - - vm.expectRevert("Timelock/use-executeBatch"); - timelock.execute(address(mockTarget), 0, data, bytes32(0), SALT); - } - - function testExecuteSingleOperation() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - vm.expectEmit(true, true, true, true); - emit CallExecuted(id, 0, address(mockTarget), 0, data); - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - - assertTrue(mockTarget.called()); - assertEq(mockTarget.value(), 42); - } - - function testExecutePermissionless() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // Anyone can execute - vm.prank(other); - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - - assertTrue(mockTarget.called()); - } - - function testExecuteRevertsIfNotReady() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - // Try to execute before delay - vm.expectRevert(); - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - } - - function testExecuteRevertsIfPaused() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - vm.prank(pauser); - timelock.pause(); - - vm.expectRevert(); - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - } - - function testExecuteWithValue() public { - bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); - bytes32 id = _hashOperation(address(mockTarget), 1 ether, data, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - vm.deal(address(timelock), 1 ether); - _execute(address(mockTarget), 1 ether, data, bytes32(0), SALT); - - assertTrue(mockTarget.called()); - assertEq(mockTarget.lastValue(), 1 ether); - } - - function testExecuteRevertsIfTargetReverts() public { - bytes memory data = abi.encodeWithSelector(MockTarget.revertCall.selector); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - vm.expectRevert("MockTarget: intentional revert"); - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - } - - function testExecuteWithPredecessor() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); - - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("salt2")); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data2, id1, keccak256("salt2"), MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // Execute first operation - _execute(address(mockTarget), 0, data1, bytes32(0), SALT); - assertEq(mockTarget.value(), 1); - - // Execute second operation (requires predecessor to be done) - _execute(address(mockTarget), 0, data2, id1, keccak256("salt2")); - assertEq(mockTarget.value(), 2); - } - - function testExecuteRevertsIfPredecessorNotDone() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); - - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("salt2")); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data2, id1, keccak256("salt2"), MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // Try to execute second operation before first - vm.expectRevert(); - _execute(address(mockTarget), 0, data2, id1, keccak256("salt2")); - } - - function testExecuteBatchMultipleOperations() public { - address[] memory targets = new address[](2); - targets[0] = address(mockTarget); - targets[1] = address(mockTarget); - - uint256[] memory values = new uint256[](2); - values[0] = 0; - values[1] = 0; - - bytes[] memory payloads = new bytes[](2); - payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); - payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); - - bytes32 id = _hashOperationBatch(targets, values, payloads, bytes32(0), SALT); - - vm.prank(proposer); - timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - timelock.executeBatch(targets, values, payloads, bytes32(0), SALT); - - // Last operation sets value to 20 - assertEq(mockTarget.value(), 20); - } - - // ============================================================================ - // Cancellation Tests - // ============================================================================ - - function testCancelProposerCanCancel() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.prank(proposer); - vm.expectEmit(true, false, false, false); - emit Cancelled(id); - timelock.cancel(id); - - assertEq(uint256(timelock.getTimestamp(id)), 0); - } - - function testCancelCancellerCanCancel() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.prank(canceller); - timelock.cancel(id); - - assertEq(uint256(timelock.getTimestamp(id)), 0); - } - - function testCancelRevertsIfNotAuthorized() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.prank(other); - vm.expectRevert(); - timelock.cancel(id); - } - - function testCancelCannotCancelAfterExecution() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - - vm.prank(proposer); - vm.expectRevert(); - timelock.cancel(id); - } - - // ============================================================================ - // Pausing Tests - // ============================================================================ - - function testPausePauserCanPause() public { - vm.prank(pauser); - vm.expectEmit(true, false, false, false); - emit Paused(pauser); - timelock.pause(); - - assertTrue(timelock.paused()); - } - - function testPauseRevertsIfNotPauser() public { - vm.prank(other); - vm.expectRevert(); - timelock.pause(); - } - - function testUnpauseAdminCanUnpause() public { - vm.prank(pauser); - timelock.pause(); - - vm.prank(admin); - vm.expectEmit(true, false, false, false); - emit Unpaused(admin); - timelock.unpause(); - - assertFalse(timelock.paused()); - } - - function testUnpauseRevertsIfNotAdmin() public { - vm.prank(pauser); - timelock.pause(); - - vm.prank(other); - vm.expectRevert(); - timelock.unpause(); - } - - function testPauseBlocksScheduling() public { - vm.prank(pauser); - timelock.pause(); - - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - vm.expectRevert(); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - } - - function testPauseBlocksExecution() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - vm.prank(pauser); - timelock.pause(); - - vm.expectRevert(); - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - } - - function testPauseBlocksScheduleBatch() public { - vm.prank(pauser); - timelock.pause(); - - address[] memory targets = new address[](1); - targets[0] = address(mockTarget); - uint256[] memory values = new uint256[](1); - values[0] = 0; - bytes[] memory payloads = new bytes[](1); - payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); - - vm.prank(proposer); - vm.expectRevert(); - timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); - } - - function testPauseBlocksExecuteBatch() public { - address[] memory targets = new address[](1); - targets[0] = address(mockTarget); - uint256[] memory values = new uint256[](1); - values[0] = 0; - bytes[] memory payloads = new bytes[](1); - payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); - - vm.prank(proposer); - timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - vm.prank(pauser); - timelock.pause(); - - vm.expectRevert(); - timelock.executeBatch(targets, values, payloads, bytes32(0), SALT); - } - - // ============================================================================ - // Delay Management Tests - // ============================================================================ - - function testUpdateDelayImmediatelyAdminCanUpdate() public { - uint256 newDelay = 2 days; - - vm.prank(admin); - vm.expectEmit(true, false, false, false); - emit MinDelayChange(MIN_DELAY, newDelay); - timelock.updateDelayImmediately(newDelay); - - assertEq(timelock.getMinDelay(), newDelay); - } - - function testUpdateDelayImmediatelyRevertsIfNotAdmin() public { - vm.prank(other); - vm.expectRevert(); - timelock.updateDelayImmediately(2 days); - } - - function testUpdateDelayImmediatelyProposerCannotChangeDelay() public { - bytes memory data = abi.encodeWithSelector( - Timelock.updateDelayImmediately.selector, - 2 days - ); - - // Try to schedule a call to updateDelayImmediately - vm.prank(proposer); - vm.expectRevert("Timelock/self-calls-disabled"); - _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); - } - - function testUpdateDelayImmediatelyNewDelayAppliesToFutureOperations() public { - uint256 newDelay = 2 days; - - vm.prank(admin); - timelock.updateDelayImmediately(newDelay); - - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - // Old delay should fail - vm.expectRevert(); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - // New delay should work - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, newDelay); - } - - // ============================================================================ - // Security Tests - Self Call Prevention - // ============================================================================ - - function testSelfCallPreventionSchedule() public { - bytes memory data = abi.encodeWithSelector( - TimelockController.updateDelay.selector, - 2 days - ); - - vm.prank(proposer); - vm.expectRevert("Timelock/self-calls-disabled"); - _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); - } - - function testSelfCallPreventionScheduleBatch() public { - address[] memory targets = new address[](1); - targets[0] = address(timelock); - - uint256[] memory values = new uint256[](1); - values[0] = 0; - - bytes[] memory payloads = new bytes[](1); - payloads[0] = abi.encodeWithSelector( - TimelockController.updateDelay.selector, - 2 days - ); - - vm.prank(proposer); - vm.expectRevert("Timelock/self-calls-disabled"); - timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); - } - - function testSelfCallPreventionCannotGrantAdminRole() public { - bytes memory data = abi.encodeWithSelector( - AccessControl.grantRole.selector, - timelock.DEFAULT_ADMIN_ROLE(), - proposer - ); - - vm.prank(proposer); - vm.expectRevert("Timelock/self-calls-disabled"); - _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); - } - - // ============================================================================ - // Edge Cases and Integration Tests - // ============================================================================ - - function testOperationStateUnsetToWaitingToReady() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); - - // Initially unset - assertEq(uint256(timelock.getTimestamp(id)), 0); - - // Schedule - now waiting - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - assertGt(uint256(timelock.getTimestamp(id)), 0); - - // After delay - ready - vm.warp(block.timestamp + MIN_DELAY); - assertTrue(timelock.isOperationReady(id)); - - // Execute - done - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - assertTrue(timelock.isOperationDone(id)); - } - - function testMultipleOperationsSameTarget() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); - - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, bytes32(0), keccak256("salt2")); - - vm.startPrank(proposer); - _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); - _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("salt2"), MIN_DELAY); - vm.stopPrank(); - - vm.warp(block.timestamp + MIN_DELAY); - - _execute(address(mockTarget), 0, data1, bytes32(0), SALT); - assertEq(mockTarget.value(), 1); - - _execute(address(mockTarget), 0, data2, bytes32(0), keccak256("salt2")); - assertEq(mockTarget.value(), 2); - } - - function testFullWorkflowScheduleExecuteCancel() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); - - // Schedule - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - assertGt(uint256(timelock.getTimestamp(id)), 0); - - // Cancel - vm.prank(canceller); - timelock.cancel(id); - assertEq(uint256(timelock.getTimestamp(id)), 0); - - // Try to execute (should fail) - vm.warp(block.timestamp + MIN_DELAY); - vm.expectRevert(); - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - } - - function testPauseUnpauseResumeOperations() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - // Schedule - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - // Pause - vm.prank(pauser); - timelock.pause(); - - // Unpause - vm.prank(admin); - timelock.unpause(); - - // Execute should work now - vm.warp(block.timestamp + MIN_DELAY); - _execute(address(mockTarget), 0, data, bytes32(0), SALT); - assertTrue(mockTarget.called()); - } - - // ============================================================================ - // Helper Functions - // ============================================================================ - - function _hashOperation( - address target, - uint256 value, - bytes memory data, - bytes32 predecessor, - bytes32 salt - ) internal view returns (bytes32) { - // Convert to batch format for hashing - address[] memory targets = new address[](1); - targets[0] = target; - uint256[] memory values = new uint256[](1); - values[0] = value; - bytes[] memory payloads = new bytes[](1); - payloads[0] = data; - return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); - } - - function _hashOperationBatch( - address[] memory targets, - uint256[] memory values, - bytes[] memory payloads, - bytes32 predecessor, - bytes32 salt - ) internal pure returns (bytes32) { - return keccak256(abi.encode(targets, values, payloads, predecessor, salt)); - } -} - From 73981afed79f7ad91f47342594b86a399f804b4f Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 13 Jan 2026 17:50:04 -0300 Subject: [PATCH 042/150] Remove unused variable in test --- test/Configurator.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 419b9a2..05e8a57 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -770,7 +770,6 @@ contract ConfiguratorTest is DssTest { _setupCBeam(address(target1), CBEAM1); bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 123); - bytes32 key = keccak256(data); beamState.addInitControllerActions(data, address(0)); // Global whitelist // Stop the BeamState From da28bc37683e2d591e2ed7b7a3affb8ed0fc8f3c Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Thu, 15 Jan 2026 07:59:17 -0300 Subject: [PATCH 043/150] Minor fixes Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- src/BeamState.sol | 8 ++++---- test/BeamState.t.sol | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 8802af0..5e1598b 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -157,7 +157,7 @@ contract BeamState { } function setMaxChange(address rateLimits_, uint256 value) external roleAuth { - require(value >= WAD, "Configurator/maxChange-below-1x"); + require(value >= WAD, "BeamState/maxChange-below-1x"); maxChange[rateLimits_] = value; emit SetMaxChange(rateLimits_, value); } @@ -172,9 +172,9 @@ contract BeamState { emit DelRateLimits(rateLimits_); } - function addController(address rateLimits_) external roleAuth { - controllers[rateLimits_] = 1; - emit AddController(rateLimits_); + function addController(address controller) external roleAuth { + controllers[controller] = 1; + emit AddController(controller); } function delController(address controller) external roleAuth { diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 48a1f5c..1406e95 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -226,7 +226,7 @@ contract BeamStateTest is DssTest { } function testSetMaxChangeBelowWad() public { - vm.expectRevert("Configurator/maxChange-below-1x"); + vm.expectRevert("BeamState/maxChange-below-1x"); beamState.setMaxChange(TARGET1, WAD - 1); } From 58acb55742bd9f9fa1dbbcb169adf0b07a9a956c Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 15 Jan 2026 08:09:23 -0300 Subject: [PATCH 044/150] Update CI tests file --- .github/workflows/test.yml | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b79c8d4..90f1e7d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,38 +1,35 @@ -name: CI +name: test -permissions: {} - -on: - push: - pull_request: - workflow_dispatch: +on: [push, pull_request] env: FOUNDRY_PROFILE: ci jobs: check: + strategy: + fail-fast: true + name: Foundry project runs-on: ubuntu-latest - permissions: - contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v3 with: - persist-credentials: false submodules: recursive - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - - - name: Show Forge version - run: forge --version - - - name: Run Forge fmt - run: forge fmt --check + with: + version: stable - name: Run Forge build - run: forge build --sizes + run: | + forge --version + forge build --sizes + id: build - name: Run Forge tests - run: forge test -vvv + run: | + forge test -vvv + id: test + From a6d012c023c94000606794f698fe5d09a7087627 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 15 Jan 2026 21:48:29 +0700 Subject: [PATCH 045/150] Align functions and order to the linked controllers --- src/timelock/TimelockWrapper.sol | 114 +++++++++++++++---------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index 240da4e..9d2b885 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -39,14 +39,14 @@ interface ControllerLike { function setOTCBuffer(address exchange, address otcBuffer) external; function setOTCRechargeRate(address exchange, uint256 rechargeRate18) external; function setOTCWhitelistedAsset(address exchange, address asset, bool isWhitelisted) external; - function setUniswapV4TickLimits(bytes32 poolId, int24 tickLowerMin, int24 tickUpperMax, uint24 maxTickSpacing) external; function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; + function setUniswapV4TickLimits(bytes32 poolId, int24 tickLowerMin, int24 tickUpperMax, uint24 maxTickSpacing) external; // Grove-only functions - function setCentrifugeRecipient(uint16 centrifugeId, bytes32 recipient) external; - function setUniswapV3PoolLowerTick(address pool, int24 lowerTick) external; - function setUniswapV3PoolUpperTick(address pool, int24 upperTick) external; function setUniswapV3PoolMaxTickDelta(address pool, uint24 maxTickDelta) external; - function setUniswapV3PoolTwapSecondsAgo(address pool, uint32 twapSecondsAgo) external; + function setUniswapV3AddLiquidityLowerTickBound(address pool, int24 lowerTickBound) external; + function setUniswapV3AddLiquidityUpperTickBound(address pool, int24 upperTickBound) external; + function setUniswapV3TwapSecondsAgo(address pool, uint32 twapSecondsAgo) external; + function setCentrifugeRecipient(uint16 centrifugeId, bytes32 recipient) external; } struct RateLimitConfig { @@ -373,29 +373,6 @@ contract TimelockWrapper { emit ProposalSubmitted(operationId, "setOTCWhitelistedAsset"); } - function setUniswapV4TickLimits( - bytes32 poolId, - int24 tickLowerMin, - int24 tickUpperMax, - uint24 maxTickSpacing, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV4TickLimits.selector, - poolId, - tickLowerMin, - tickUpperMax, - maxTickSpacing - ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV4TickLimits"); - } - function setMaxExchangeRate( address token, uint256 shares, @@ -416,86 +393,90 @@ contract TimelockWrapper { operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxExchangeRate"); } - - // Grove-only functions - - function setCentrifugeRecipient( - uint16 centrifugeId, - bytes32 recipient, + + function setUniswapV4TickLimits( + bytes32 poolId, + int24 tickLowerMin, + int24 tickUpperMax, + uint24 maxTickSpacing, address controller, bytes32 predecessor, bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setCentrifugeRecipient.selector, - centrifugeId, - recipient + ControllerLike.setUniswapV4TickLimits.selector, + poolId, + tickLowerMin, + tickUpperMax, + maxTickSpacing ); bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setCentrifugeRecipient"); + emit ProposalSubmitted(operationId, "setUniswapV4TickLimits"); } - function setUniswapV3PoolLowerTick( + // Grove-only functions + + function setUniswapV3PoolMaxTickDelta( address pool, - int24 lowerTick, + uint24 maxTickDelta, address controller, bytes32 predecessor, bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3PoolLowerTick.selector, + ControllerLike.setUniswapV3PoolMaxTickDelta.selector, pool, - lowerTick + maxTickDelta ); bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3PoolLowerTick"); + emit ProposalSubmitted(operationId, "setUniswapV3PoolMaxTickDelta"); } - - function setUniswapV3PoolUpperTick( + + function setUniswapV3AddLiquidityLowerTickBound( address pool, - int24 upperTick, + int24 lowerTickBound, address controller, bytes32 predecessor, bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3PoolUpperTick.selector, + ControllerLike.setUniswapV3AddLiquidityLowerTickBound.selector, pool, - upperTick + lowerTickBound ); bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3PoolUpperTick"); + emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityLowerTickBound"); } - function setUniswapV3PoolMaxTickDelta( + function setUniswapV3AddLiquidityUpperTickBound( address pool, - uint24 maxTickDelta, + int24 upperTickBound, address controller, bytes32 predecessor, bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3PoolMaxTickDelta.selector, + ControllerLike.setUniswapV3AddLiquidityUpperTickBound.selector, pool, - maxTickDelta + upperTickBound ); bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3PoolMaxTickDelta"); + emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityUpperTickBound"); } - function setUniswapV3PoolTwapSecondsAgo( + function setUniswapV3TwapSecondsAgo( address pool, uint32 twapSecondsAgo, address controller, @@ -504,14 +485,33 @@ contract TimelockWrapper { uint256 delay ) external toll returns (bytes32 operationId) { bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3PoolTwapSecondsAgo.selector, + ControllerLike.setUniswapV3TwapSecondsAgo.selector, pool, twapSecondsAgo ); bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3PoolTwapSecondsAgo"); + emit ProposalSubmitted(operationId, "setUniswapV3TwapSecondsAgo"); + } + + function setCentrifugeRecipient( + uint16 centrifugeId, + bytes32 recipient, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setCentrifugeRecipient.selector, + centrifugeId, + recipient + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setCentrifugeRecipient"); } } From 3b45f50e16034bc7d8f1703542024f08aaa02145 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 15 Jan 2026 21:52:19 +0700 Subject: [PATCH 046/150] Handle small comments --- src/timelock/Timelock.sol | 4 ++-- src/timelock/TimelockWrapper.sol | 25 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 08012ec..a816c13 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -175,11 +175,11 @@ contract Timelock is TimelockController, Pausable { } - function getOperationCount() public view returns (uint256) { + function getOperationCount() external view returns (uint256) { return _operationIds.length(); } - function getOperation(bytes32 id) public view returns (Operation memory op) { + function getOperation(bytes32 id) external view returns (Operation memory op) { return operations[id]; } } diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index 9d2b885..240b7e7 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -16,7 +16,24 @@ pragma solidity ^0.8.21; -import { Timelock } from "src/timelock/Timelock.sol"; +interface TimelockLike { + function scheduleBatch( + address[] memory targets, + uint256[] memory values, + bytes[] memory payloads, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external; + + function hashOperationBatch( + address[] memory targets, + uint256[] memory values, + bytes[] memory payloads, + bytes32 predecessor, + bytes32 salt + ) external view returns (bytes32); +} interface BeamStateLike { function start() external; @@ -105,12 +122,12 @@ contract TimelockWrapper { event Kiss(address indexed usr); event Diss(address indexed usr); event ProposalSubmitted(bytes32 indexed operationId, string functionName); - - Timelock public immutable timelock; + + TimelockLike public immutable timelock; BeamStateLike public immutable beamState; constructor(address timelock_, address beamState_) { - timelock = Timelock(payable(timelock_)); + timelock = TimelockLike(payable(timelock_)); beamState = BeamStateLike(beamState_); wards[msg.sender] = 1; From afd992e3826046e4c25001989d046db28d60e2a1 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 15 Jan 2026 21:53:38 +0700 Subject: [PATCH 047/150] Update src/timelock/TimelockWrapper.sol Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- src/timelock/TimelockWrapper.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index 240b7e7..515e4a4 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -40,7 +40,7 @@ interface BeamStateLike { function setHop(address rateLimits_, uint256 value) external; function setMaxChange(address rateLimits_, uint256 value) external; function addRateLimits(address rateLimits_) external; - function addController(address rateLimits_) external; + function addController(address controller) external; function addCBeam(address cBeam) external; function addInitRateLimits(bytes32 key, address rateLimits_, uint256 maxAmount, uint256 slope) external; function addInitControllerActions(bytes calldata data, address controller) external returns (bytes32 key); From 996eff8295cee97bcd50d45dc1c11406fd88d681 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:39:47 +0700 Subject: [PATCH 048/150] Add maxIterations and tests --- src/timelock/Timelock.sol | 19 +- src/timelock/TimelockKeeperJob.sol | 16 +- test/timelock/Timelock.t.sol | 1125 +++++++++++++++++++++++++ test/timelock/TimelockKeeperJob.t.sol | 433 ++++++++++ 4 files changed, 1580 insertions(+), 13 deletions(-) create mode 100644 test/timelock/Timelock.t.sol create mode 100644 test/timelock/TimelockKeeperJob.t.sol diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index a816c13..57fe489 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -153,24 +153,31 @@ contract Timelock is TimelockController, Pausable { // Keeper job helpers // ------------------------------------------------------------------------ - // Operations may still not be executable due to various downstream conditions. + // Operations may still not be executable due to various downstream conditions. // It is assumed that this is not a perfect fetching mechanism and that if needed proposals // can be executed without cron keepers, or canceled in case they are jamming this mechanism. function getNextExecutableOperation() public view returns (bytes32 id) { + return getNextExecutableOperation(0); + } + + /// Returns the next executable operation, limited by maxIterations (0 = no limit) + function getNextExecutableOperation(uint256 maxIterations) public view returns (bytes32 id) { uint256 length = _operationIds.length(); - for (uint256 i = 0; i < length; ++i) { + uint256 limit = maxIterations == 0 ? length : (maxIterations < length ? maxIterations : length); + + for (uint256 i = 0; i < limit; ++i) { bytes32 operationId = _operationIds.at(i); - + // Check if operation is ready if (!isOperationReady(operationId)) continue; - + // Check if predecessor is done, if any Operation memory op = operations[operationId]; if (op.predecessor != bytes32(0) && !isOperationDone(op.predecessor)) continue; - + return operationId; } - + return bytes32(0); } diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol index ffd6a42..6367988 100644 --- a/src/timelock/TimelockKeeperJob.sol +++ b/src/timelock/TimelockKeeperJob.sol @@ -33,7 +33,7 @@ interface TimelockLike { bytes32 salt; } - function getNextExecutableOperation() external view returns (bytes32 id); + function getNextExecutableOperation(uint256 maxIterations) external view returns (bytes32 id); function getOperation(bytes32 id) external view returns (Operation memory op); function executeBatch( address[] calldata targets, @@ -46,8 +46,9 @@ interface TimelockLike { contract TimelockKeeperJob is IJob { - SequencerLike public immutable sequencer; - TimelockLike public immutable timelock; + SequencerLike public immutable sequencer; + TimelockLike public immutable timelock; + uint256 public immutable maxIterations; // --- Errors --- error NotMaster(bytes32 network); @@ -56,15 +57,16 @@ contract TimelockKeeperJob is IJob { // --- Events --- event Work(bytes32 indexed network, bytes32 indexed operationId); - constructor(address _sequencer, address _timelock) { - sequencer = SequencerLike(_sequencer); - timelock = TimelockLike(_timelock); + constructor(address _sequencer, address _timelock, uint256 _maxIterations) { + sequencer = SequencerLike(_sequencer); + timelock = TimelockLike(_timelock); + maxIterations = _maxIterations; } function work(bytes32 network, bytes calldata) external { if (!sequencer.isMaster(network)) revert NotMaster(network); - bytes32 id = timelock.getNextExecutableOperation(); + bytes32 id = timelock.getNextExecutableOperation(maxIterations); if (id == bytes32(0)) revert NoExecutableOperation(); TimelockLike.Operation memory op = timelock.getOperation(id); diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol new file mode 100644 index 0000000..58020a4 --- /dev/null +++ b/test/timelock/Timelock.t.sol @@ -0,0 +1,1125 @@ +// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; +import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol"; +import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; +import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; + +contract MockTarget { + uint256 public value; + bool public called; + uint256 public callCount; + uint256 public lastEthReceived; + + function setValue(uint256 _value) external { + value = _value; + called = true; + callCount++; + } + + function receiveEther() external payable { + lastEthReceived = msg.value; + called = true; + callCount++; + } + + function revertCall() external pure { + revert("MockTarget: intentional revert"); + } +} + +contract TimelockTest is Test { + Timelock public timelock; + MockTarget public mockTarget; + MockTarget public mockTarget2; + + address public admin; + address public proposer; + address public proposer2; + address public canceller; + address public pauser; + address public other; + + uint256 public constant MIN_DELAY = 1 days; + bytes32 public constant SALT = keccak256("test-salt"); + + event CallScheduled( + bytes32 indexed id, + uint256 indexed index, + address target, + uint256 value, + bytes data, + bytes32 predecessor, + uint256 delay + ); + event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data); + event Cancelled(bytes32 indexed id); + event MinDelayChange(uint256 oldDuration, uint256 newDuration); + event Paused(address account); + event Unpaused(address account); + + function setUp() public { + admin = address(0x1); + proposer = address(0x2); + proposer2 = address(0x22); + canceller = address(0x3); + pauser = address(0x4); + other = address(0x5); + + address[] memory proposers = new address[](2); + proposers[0] = proposer; + proposers[1] = proposer2; + + address[] memory cancellers = new address[](1); + cancellers[0] = canceller; + + address[] memory pausers = new address[](1); + pausers[0] = pauser; + + timelock = new Timelock(MIN_DELAY, admin, proposers, cancellers, pausers); + mockTarget = new MockTarget(); + mockTarget2 = new MockTarget(); + + vm.deal(address(timelock), 100 ether); + } + + // ============================================================================ + // Helper Functions + // ============================================================================ + + function _schedule( + address target, + uint256 value, + bytes memory data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) internal returns (bytes32) { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory values = new uint256[](1); + values[0] = value; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + } + + function _execute( + address target, + uint256 value, + bytes memory data, + bytes32 predecessor, + bytes32 salt + ) internal { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory values = new uint256[](1); + values[0] = value; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + timelock.executeBatch{value: value}(targets, values, payloads, predecessor, salt); + } + + function _hashOperation( + address target, + uint256 value, + bytes memory data, + bytes32 predecessor, + bytes32 salt + ) internal view returns (bytes32) { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory values = new uint256[](1); + values[0] = value; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + } + + // ============================================================================ + // Constructor and Initialization Tests + // ============================================================================ + + function testConstructorInitialization() public { + assertEq(timelock.getMinDelay(), MIN_DELAY); + assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), admin)); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), proposer)); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), proposer2)); + assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), proposer)); + assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller)); + assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser)); + assertTrue(timelock.hasRole(timelock.EXECUTOR_ROLE(), address(0))); + assertFalse(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), address(timelock))); + } + + function testConstructorRevertsIfAdminIsZero() public { + address[] memory proposers = new address[](1); + proposers[0] = proposer; + address[] memory cancellers = new address[](0); + address[] memory pausers = new address[](0); + + vm.expectRevert("Timelock/admin-zero-address"); + new Timelock(MIN_DELAY, address(0), proposers, cancellers, pausers); + } + + // ============================================================================ + // Role Management Tests + // ============================================================================ + + function testAdminCanGrantRole() public { + address newProposer = address(0x999); + bytes32 proposerRole = timelock.PROPOSER_ROLE(); + + assertFalse(timelock.hasRole(proposerRole, newProposer)); + + vm.prank(admin); + timelock.grantRole(proposerRole, newProposer); + + assertTrue(timelock.hasRole(proposerRole, newProposer)); + + // Verify the new proposer can actually schedule + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + vm.prank(newProposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + assertEq(timelock.getOperationCount(), 1); + } + + function testAdminCanRevokeRole() public { + bytes32 proposerRole = timelock.PROPOSER_ROLE(); + + assertTrue(timelock.hasRole(proposerRole, proposer)); + + vm.prank(admin); + timelock.revokeRole(proposerRole, proposer); + + assertFalse(timelock.hasRole(proposerRole, proposer)); + + // Verify the revoked proposer can no longer schedule + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + vm.prank(proposer); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, proposer, proposerRole)); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testNonAdminCannotGrantRole() public { + address newProposer = address(0x999); + bytes32 proposerRole = timelock.PROPOSER_ROLE(); + bytes32 adminRole = timelock.DEFAULT_ADMIN_ROLE(); + + vm.prank(other); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, other, adminRole)); + timelock.grantRole(proposerRole, newProposer); + } + + function testNonAdminCannotRevokeRole() public { + bytes32 proposerRole = timelock.PROPOSER_ROLE(); + bytes32 adminRole = timelock.DEFAULT_ADMIN_ROLE(); + + vm.prank(other); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, other, adminRole)); + timelock.revokeRole(proposerRole, proposer); + } + + function testUserCanRenounceOwnRole() public { + bytes32 proposerRole = timelock.PROPOSER_ROLE(); + + assertTrue(timelock.hasRole(proposerRole, proposer)); + + vm.prank(proposer); + timelock.renounceRole(proposerRole, proposer); + + assertFalse(timelock.hasRole(proposerRole, proposer)); + + // Verify the user can no longer schedule + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + vm.prank(proposer); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, proposer, proposerRole)); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testUserCannotRenounceOthersRole() public { + bytes32 proposerRole = timelock.PROPOSER_ROLE(); + + vm.prank(other); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlBadConfirmation.selector)); + timelock.renounceRole(proposerRole, proposer); + + // Proposer still has their role + assertTrue(timelock.hasRole(proposerRole, proposer)); + } + + function testAdminRoleTransfer() public { + address newAdmin = address(0x888); + bytes32 adminRole = timelock.DEFAULT_ADMIN_ROLE(); + bytes32 proposerRole = timelock.PROPOSER_ROLE(); + + // Admin grants admin role to new address + vm.prank(admin); + timelock.grantRole(adminRole, newAdmin); + + assertTrue(timelock.hasRole(adminRole, newAdmin)); + + // New admin can grant roles + address newProposer = address(0x999); + vm.prank(newAdmin); + timelock.grantRole(proposerRole, newProposer); + + assertTrue(timelock.hasRole(proposerRole, newProposer)); + + // Old admin renounces + vm.prank(admin); + timelock.renounceRole(adminRole, admin); + + assertFalse(timelock.hasRole(adminRole, admin)); + + // Old admin can no longer grant roles + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, admin, adminRole)); + timelock.grantRole(proposerRole, address(0x777)); + } + + // ============================================================================ + // Scheduling Tests + // ============================================================================ + + function testScheduleSingleReverts() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + vm.expectRevert("Timelock/use-scheduleBatch"); + timelock.schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testScheduleSingleOperation() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + vm.expectEmit(true, true, true, true); + emit CallScheduled(id, 0, address(mockTarget), 0, data, bytes32(0), MIN_DELAY); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + assertEq(uint256(timelock.getTimestamp(id)), block.timestamp + MIN_DELAY); + } + + function testScheduleRevertsIfNotProposer() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 proposerRole = timelock.PROPOSER_ROLE(); + + vm.prank(other); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, other, proposerRole)); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testScheduleRevertsIfDelayTooShort() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockInsufficientDelay.selector, MIN_DELAY - 1, MIN_DELAY)); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY - 1); + } + + function testScheduleRevertsIfPaused() public { + vm.prank(pauser); + timelock.pause(); + + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + vm.expectRevert(Pausable.EnforcedPause.selector); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testScheduleRevertsOnSelfCall() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + vm.expectRevert("Timelock/self-calls-disabled"); + _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + function testScheduleBatchRevertsOnSelfCall() public { + address[] memory targets = new address[](2); + targets[0] = address(mockTarget); + targets[1] = address(timelock); + + uint256[] memory values = new uint256[](2); + bytes[] memory payloads = new bytes[](2); + payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); + payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); + + vm.prank(proposer); + vm.expectRevert("Timelock/self-calls-disabled"); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + } + + function testCannotScheduleDuplicate() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.startPrank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockUnexpectedOperationState.selector, id, bytes32(1 << uint8(TimelockController.OperationState.Unset)))); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + vm.stopPrank(); + } + + function testDifferentSaltCreatesDifferentId() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.startPrank(proposer); + bytes32 id1 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s1"), MIN_DELAY); + bytes32 id2 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), MIN_DELAY); + vm.stopPrank(); + + assertTrue(id1 != id2); + assertEq(timelock.getOperationCount(), 2); + } + + function testMultipleProposersCanSchedule() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + + vm.prank(proposer2); + _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("s2"), MIN_DELAY); + + assertEq(timelock.getOperationCount(), 2); + } + + // ============================================================================ + // Execution Tests + // ============================================================================ + + function testExecuteSingleReverts() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectRevert("Timelock/use-executeBatch"); + timelock.execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testExecuteSingleOperation() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectEmit(true, true, true, true); + emit CallExecuted(id, 0, address(mockTarget), 0, data); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + + assertTrue(mockTarget.called()); + assertEq(mockTarget.value(), 42); + } + + function testExecutePermissionless() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.prank(other); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + + assertTrue(mockTarget.called()); + } + + function testExecuteRevertsIfNotReady() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockUnexpectedOperationState.selector, id, bytes32(1 << uint8(TimelockController.OperationState.Ready)))); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testExecuteRevertsIfPaused() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.prank(pauser); + timelock.pause(); + + vm.expectRevert(Pausable.EnforcedPause.selector); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testExecuteRevertsIfTargetReverts() public { + bytes memory data = abi.encodeWithSelector(MockTarget.revertCall.selector); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectRevert("MockTarget: intentional revert"); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + } + + function testExecuteBatchMultipleOperations() public { + address[] memory targets = new address[](2); + targets[0] = address(mockTarget); + targets[1] = address(mockTarget); + + uint256[] memory values = new uint256[](2); + bytes[] memory payloads = new bytes[](2); + payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 10); + payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 20); + + vm.prank(proposer); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + timelock.executeBatch(targets, values, payloads, bytes32(0), SALT); + + assertEq(mockTarget.value(), 20); + } + + function testLargeBatch() public { + uint256 batchSize = 50; + + address[] memory targets = new address[](batchSize); + uint256[] memory values = new uint256[](batchSize); + bytes[] memory payloads = new bytes[](batchSize); + + for (uint256 i = 0; i < batchSize; i++) { + targets[i] = address(mockTarget); + payloads[i] = abi.encodeWithSelector(MockTarget.setValue.selector, i); + } + + vm.prank(proposer); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + timelock.executeBatch(targets, values, payloads, bytes32(0), SALT); + + assertEq(mockTarget.value(), batchSize - 1); + assertEq(mockTarget.callCount(), batchSize); + } + + // ============================================================================ + // Predecessor Chain Tests + // ============================================================================ + + function testExecuteWithPredecessor() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); + + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data2, id1, keccak256("salt2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + _execute(address(mockTarget), 0, data1, bytes32(0), SALT); + assertEq(mockTarget.value(), 1); + + _execute(address(mockTarget), 0, data2, id1, keccak256("salt2")); + assertEq(mockTarget.value(), 2); + } + + function testExecuteRevertsIfPredecessorNotDone() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); + + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data2, id1, keccak256("salt2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockUnexecutedPredecessor.selector, id1)); + _execute(address(mockTarget), 0, data2, id1, keccak256("salt2")); + } + + function testCancelledPredecessorBlocksDependent() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + + vm.startPrank(proposer); + bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + _schedule(address(mockTarget), 0, data2, id1, keccak256("s2"), MIN_DELAY); + vm.stopPrank(); + + vm.prank(canceller); + timelock.cancel(id1); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockUnexecutedPredecessor.selector, id1)); + _execute(address(mockTarget), 0, data2, id1, keccak256("s2")); + } + + // ============================================================================ + // Cancellation Tests + // ============================================================================ + + function testCancelProposerCanCancel() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(proposer); + vm.expectEmit(true, false, false, false); + emit Cancelled(id); + timelock.cancel(id); + + assertEq(uint256(timelock.getTimestamp(id)), 0); + } + + function testCancelCancellerCanCancel() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(canceller); + timelock.cancel(id); + + assertEq(uint256(timelock.getTimestamp(id)), 0); + } + + function testCancelRevertsIfNotAuthorized() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + bytes32 cancellerRole = timelock.CANCELLER_ROLE(); + vm.prank(other); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, other, cancellerRole)); + timelock.cancel(id); + } + + function testCancelCannotCancelAfterExecution() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(proposer); + // Cancel expects operation to be Waiting OR Ready + bytes32 expectedStates = bytes32((1 << uint8(TimelockController.OperationState.Waiting)) | (1 << uint8(TimelockController.OperationState.Ready))); + vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockUnexpectedOperationState.selector, id, expectedStates)); + timelock.cancel(id); + } + + function testCancelRevertsWhilePaused() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(pauser); + timelock.pause(); + + vm.prank(canceller); + vm.expectRevert(Pausable.EnforcedPause.selector); + timelock.cancel(id); + } + + function testCancelWorksAfterUnpause() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(pauser); + timelock.pause(); + + vm.prank(admin); + timelock.unpause(); + + vm.prank(canceller); + timelock.cancel(id); + + assertEq(timelock.getOperationCount(), 0); + } + + function testProposerCanCancelOtherProposersOperation() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(proposer2); + timelock.cancel(id); + + assertEq(timelock.getOperationCount(), 0); + } + + // ============================================================================ + // Pausing Tests + // ============================================================================ + + function testPausePauserCanPause() public { + vm.prank(pauser); + vm.expectEmit(true, false, false, false); + emit Paused(pauser); + timelock.pause(); + + assertTrue(timelock.paused()); + } + + function testPauseRevertsIfNotPauser() public { + bytes32 pauserRole = timelock.PAUSER_ROLE(); + vm.prank(other); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, other, pauserRole)); + timelock.pause(); + } + + function testUnpauseAdminCanUnpause() public { + vm.prank(pauser); + timelock.pause(); + + vm.prank(admin); + vm.expectEmit(true, false, false, false); + emit Unpaused(admin); + timelock.unpause(); + + assertFalse(timelock.paused()); + } + + function testUnpauseRevertsIfNotAdmin() public { + vm.prank(pauser); + timelock.pause(); + + bytes32 adminRole = timelock.DEFAULT_ADMIN_ROLE(); + vm.prank(other); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, other, adminRole)); + timelock.unpause(); + } + + function testPauserCannotUnpause() public { + vm.prank(pauser); + timelock.pause(); + + bytes32 adminRole = timelock.DEFAULT_ADMIN_ROLE(); + vm.prank(pauser); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, pauser, adminRole)); + timelock.unpause(); + + vm.prank(admin); + timelock.unpause(); + assertFalse(timelock.paused()); + } + + function testPauseUnpauseResumesOperations() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(pauser); + timelock.pause(); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectRevert(Pausable.EnforcedPause.selector); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + + vm.prank(admin); + timelock.unpause(); + + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + assertTrue(mockTarget.called()); + } + + // ============================================================================ + // Delay Management Tests + // ============================================================================ + + function testUpdateDelayImmediatelyAdminCanUpdate() public { + uint256 newDelay = 2 days; + + vm.prank(admin); + vm.expectEmit(true, false, false, false); + emit MinDelayChange(MIN_DELAY, newDelay); + timelock.updateDelayImmediately(newDelay); + + assertEq(timelock.getMinDelay(), newDelay); + } + + function testUpdateDelayImmediatelyRevertsIfNotAdmin() public { + bytes32 adminRole = timelock.DEFAULT_ADMIN_ROLE(); + vm.prank(other); + vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, other, adminRole)); + timelock.updateDelayImmediately(2 days); + } + + function testNewDelayAppliesImmediately() public { + vm.prank(admin); + timelock.updateDelayImmediately(2 days); + + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockInsufficientDelay.selector, MIN_DELAY, 2 days)); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, 2 days); + assertEq(timelock.getOperationCount(), 1); + } + + function testUpdateDelayImmediatelyProposerCannotChangeDelay() public { + bytes memory data = abi.encodeWithSelector(Timelock.updateDelayImmediately.selector, 2 days); + + vm.prank(proposer); + vm.expectRevert("Timelock/self-calls-disabled"); + _schedule(address(timelock), 0, data, bytes32(0), SALT, MIN_DELAY); + } + + // ============================================================================ + // ETH Handling Tests + // ============================================================================ + + function testExecuteWithETHFromTimelockBalance() public { + bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); + + vm.prank(proposer); + _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + uint256 balanceBefore = address(timelock).balance; + + address[] memory targets = new address[](1); + targets[0] = address(mockTarget); + uint256[] memory values = new uint256[](1); + values[0] = 1 ether; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + timelock.executeBatch(targets, values, payloads, bytes32(0), SALT); + + assertEq(mockTarget.lastEthReceived(), 1 ether); + assertEq(address(timelock).balance, balanceBefore - 1 ether); + } + + function testExecuteWithMsgValue() public { + bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); + + vm.prank(proposer); + _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.prank(address(timelock)); + (bool success,) = other.call{value: address(timelock).balance}(""); + require(success); + assertEq(address(timelock).balance, 0); + + vm.deal(other, 1 ether); + vm.prank(other); + address[] memory targets = new address[](1); + targets[0] = address(mockTarget); + uint256[] memory values = new uint256[](1); + values[0] = 1 ether; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + timelock.executeBatch{value: 1 ether}(targets, values, payloads, bytes32(0), SALT); + + assertEq(mockTarget.lastEthReceived(), 1 ether); + } + + function testExecuteMultipleTargetsWithETH() public { + address[] memory targets = new address[](2); + targets[0] = address(mockTarget); + targets[1] = address(mockTarget2); + + uint256[] memory values = new uint256[](2); + values[0] = 1 ether; + values[1] = 2 ether; + + bytes[] memory payloads = new bytes[](2); + payloads[0] = abi.encodeWithSelector(MockTarget.receiveEther.selector); + payloads[1] = abi.encodeWithSelector(MockTarget.receiveEther.selector); + + vm.prank(proposer); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + timelock.executeBatch(targets, values, payloads, bytes32(0), SALT); + + assertEq(mockTarget.lastEthReceived(), 1 ether); + assertEq(mockTarget2.lastEthReceived(), 2 ether); + } + + // ============================================================================ + // Keeper Helper Function Tests + // ============================================================================ + + function testGetNextExecutableOperationEmpty() public view { + bytes32 id = timelock.getNextExecutableOperation(); + assertEq(id, bytes32(0)); + } + + function testGetNextExecutableOperationNotReady() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + bytes32 id = timelock.getNextExecutableOperation(); + assertEq(id, bytes32(0)); + } + + function testGetNextExecutableOperationReady() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + bytes32 expectedId = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + bytes32 id = timelock.getNextExecutableOperation(); + assertEq(id, expectedId); + } + + function testGetNextExecutableOperationMultipleReturnsFirst() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + bytes memory data3 = abi.encodeWithSelector(MockTarget.setValue.selector, 3); + + vm.startPrank(proposer); + bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + + vm.warp(block.timestamp + 1 hours); + _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("s2"), MIN_DELAY); + + vm.warp(block.timestamp + 1 hours); + _schedule(address(mockTarget), 0, data3, bytes32(0), keccak256("s3"), MIN_DELAY); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + bytes32 nextId = timelock.getNextExecutableOperation(); + assertEq(nextId, id1); + } + + function testGetNextExecutableOperationSkipsPendingPredecessor() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + + vm.startPrank(proposer); + bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + _schedule(address(mockTarget), 0, data2, id1, keccak256("s2"), MIN_DELAY); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + bytes32 nextId = timelock.getNextExecutableOperation(); + assertEq(nextId, id1); + + _execute(address(mockTarget), 0, data1, bytes32(0), keccak256("s1")); + + nextId = timelock.getNextExecutableOperation(); + bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("s2")); + assertEq(nextId, id2); + } + + function testGetNextExecutableOperationWithMaxIterations() public { + // Schedule 5 operations, only the 4th one is ready + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.startPrank(proposer); + // Schedule 3 operations with long delay (not ready) + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s1"), 10 days); + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), 10 days); + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s3"), 10 days); + // Schedule 1 operation with short delay (will be ready) + bytes32 readyId = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s4"), MIN_DELAY); + // Schedule 1 more with long delay + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s5"), 10 days); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + // With maxIterations=3, should not find the ready operation (it's at index 3) + bytes32 id = timelock.getNextExecutableOperation(3); + assertEq(id, bytes32(0), "Should not find with limit 3"); + + // With maxIterations=4, should find it + id = timelock.getNextExecutableOperation(4); + assertEq(id, readyId, "Should find with limit 4"); + + // With maxIterations=100, should also find it + id = timelock.getNextExecutableOperation(100); + assertEq(id, readyId, "Should find with limit 100"); + } + + function testGetNextExecutableOperationZeroMeansNoLimit() public { + // Schedule many operations, put the ready one at the end + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.startPrank(proposer); + // Schedule 10 operations with long delay + for (uint256 i = 0; i < 10; i++) { + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256(abi.encodePacked("long", i)), 10 days); + } + // Schedule 1 ready operation at the end + bytes32 readyId = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("ready"), MIN_DELAY); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + // With maxIterations=0 (no limit), should find it even though it's at index 10 + bytes32 id = timelock.getNextExecutableOperation(0); + assertEq(id, readyId, "Zero should mean no limit"); + + // The no-param version should also work (it calls with 0) + id = timelock.getNextExecutableOperation(); + assertEq(id, readyId, "No-param version should also find it"); + } + + function testGetOperationNonExistent() public view { + Timelock.Operation memory op = timelock.getOperation(keccak256("fake")); + assertEq(op.targets.length, 0); + assertEq(op.values.length, 0); + assertEq(op.payloads.length, 0); + } + + function testGetOperationAfterExecution() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + Timelock.Operation memory opBefore = timelock.getOperation(id); + assertEq(opBefore.targets.length, 1); + + vm.warp(block.timestamp + MIN_DELAY); + _execute(address(mockTarget), 0, data, bytes32(0), SALT); + + Timelock.Operation memory opAfter = timelock.getOperation(id); + assertEq(opAfter.targets.length, 0); + } + + function testGetOperationAfterCancel() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.prank(proposer); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.prank(canceller); + timelock.cancel(id); + + Timelock.Operation memory op = timelock.getOperation(id); + assertEq(op.targets.length, 0); + } + + function testOperationCountTracking() public { + assertEq(timelock.getOperationCount(), 0); + + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.startPrank(proposer); + bytes32 id1 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s1"), MIN_DELAY); + assertEq(timelock.getOperationCount(), 1); + + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), MIN_DELAY); + assertEq(timelock.getOperationCount(), 2); + + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s3"), MIN_DELAY); + assertEq(timelock.getOperationCount(), 3); + vm.stopPrank(); + + vm.prank(canceller); + timelock.cancel(id1); + assertEq(timelock.getOperationCount(), 2); + + vm.warp(block.timestamp + MIN_DELAY); + _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s2")); + assertEq(timelock.getOperationCount(), 1); + } + + function testOperationDataIntegrity() public { + address[] memory targets = new address[](2); + targets[0] = address(mockTarget); + targets[1] = address(mockTarget2); + + uint256[] memory values = new uint256[](2); + values[0] = 1 ether; + values[1] = 2 ether; + + bytes[] memory payloads = new bytes[](2); + payloads[0] = abi.encodeWithSelector(MockTarget.setValue.selector, 100); + payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 200); + + bytes32 predecessor = keccak256("pred"); + bytes32 salt = keccak256("salt"); + + vm.prank(proposer); + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, MIN_DELAY); + + bytes32 id = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + Timelock.Operation memory op = timelock.getOperation(id); + + assertEq(op.targets.length, 2); + assertEq(op.targets[0], address(mockTarget)); + assertEq(op.targets[1], address(mockTarget2)); + assertEq(op.values[0], 1 ether); + assertEq(op.values[1], 2 ether); + assertEq(op.payloads[0], payloads[0]); + assertEq(op.payloads[1], payloads[1]); + assertEq(op.predecessor, predecessor); + assertEq(op.salt, salt); + } +} diff --git a/test/timelock/TimelockKeeperJob.t.sol b/test/timelock/TimelockKeeperJob.t.sol new file mode 100644 index 0000000..a982328 --- /dev/null +++ b/test/timelock/TimelockKeeperJob.t.sol @@ -0,0 +1,433 @@ +// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { TimelockKeeperJob } from "src/timelock/TimelockKeeperJob.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; + +contract MockSequencer { + mapping(bytes32 => bool) public masterNetworks; + + function setMaster(bytes32 network, bool isMaster_) external { + masterNetworks[network] = isMaster_; + } + + function isMaster(bytes32 network) external view returns (bool) { + return masterNetworks[network]; + } +} + +contract MockTarget { + uint256 public value; + bool public called; + + function setValue(uint256 _value) external { + value = _value; + called = true; + } + + function revertCall() external pure { + revert("MockTarget: revert"); + } + + function receiveEther() external payable { + called = true; + } + + // Fallback to accept empty payload calls + fallback() external payable { + called = true; + } + + receive() external payable { + called = true; + } +} + + +contract TimelockKeeperJobTest is Test { + TimelockKeeperJob public keeper; + Timelock public timelock; + MockSequencer public sequencer; + MockTarget public mockTarget; + + address public admin; + address public proposer; + + uint256 public constant MIN_DELAY = 1 days; + bytes32 public constant NETWORK = keccak256("mainnet"); + bytes32 public constant SALT = keccak256("test-salt"); + + event Work(bytes32 indexed network, bytes32 indexed operationId); + + function setUp() public { + admin = address(this); + proposer = address(0x2); + + // Deploy sequencer mock + sequencer = new MockSequencer(); + sequencer.setMaster(NETWORK, true); + + // Deploy timelock + address[] memory proposers = new address[](1); + proposers[0] = proposer; + address[] memory cancellers = new address[](0); + address[] memory pausers = new address[](0); + + timelock = new Timelock(MIN_DELAY, admin, proposers, cancellers, pausers); + + // Deploy keeper + keeper = new TimelockKeeperJob(address(sequencer), address(timelock), 100); + + // Deploy mock target + mockTarget = new MockTarget(); + + // Fund timelock for ETH operations + vm.deal(address(timelock), 100 ether); + } + + // ============================================================================ + // Helper Functions + // ============================================================================ + + function _schedule( + address target, + uint256 value, + bytes memory data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) internal returns (bytes32) { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory values = new uint256[](1); + values[0] = value; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + + vm.prank(proposer); + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + + return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + } + + function _toArray(address addr) internal pure returns (address[] memory) { + address[] memory arr = new address[](1); + arr[0] = addr; + return arr; + } + + function _toArray(uint256 val) internal pure returns (uint256[] memory) { + uint256[] memory arr = new uint256[](1); + arr[0] = val; + return arr; + } + + function _toArrayBytes(bytes memory data) internal pure returns (bytes[] memory) { + bytes[] memory arr = new bytes[](1); + arr[0] = data; + return arr; + } + + function testConstructor() public view { + assertEq(address(keeper.sequencer()), address(sequencer), "Sequencer set"); + assertEq(address(keeper.timelock()), address(timelock), "Timelock set"); + assertEq(keeper.maxIterations(), 100, "maxIterations set"); + } + + // ============================================================================ + // Work Function Tests + // ============================================================================ + + function testWorkExecutesOperation() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + bytes32 opId = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectEmit(true, true, false, false); + emit Work(NETWORK, opId); + keeper.work(NETWORK, ""); + + assertTrue(mockTarget.called(), "Target was called"); + assertEq(mockTarget.value(), 42, "Value was set"); + assertEq(timelock.getOperationCount(), 0, "Operation removed from queue"); + } + + function testWorkRevertsIfNotMaster() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + bytes32 wrongNetwork = keccak256("wrong-network"); + + vm.expectRevert(abi.encodeWithSelector(TimelockKeeperJob.NotMaster.selector, wrongNetwork)); + keeper.work(wrongNetwork, ""); + } + + function testWorkRevertsIfNoExecutableOperation() public { + // No operations scheduled + vm.expectRevert(TimelockKeeperJob.NoExecutableOperation.selector); + keeper.work(NETWORK, ""); + } + + function testWorkRevertsIfOperationNotReady() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + // Don't warp time - operation not ready + vm.expectRevert(TimelockKeeperJob.NoExecutableOperation.selector); + keeper.work(NETWORK, ""); + } + + function testWorkRevertsIfTargetReverts() public { + bytes memory data = abi.encodeWithSelector(MockTarget.revertCall.selector); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectRevert("MockTarget: revert"); + keeper.work(NETWORK, ""); + } + + function testWorkMultipleOperationsExecutesFirst() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + + bytes32 opId1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("s2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + vm.expectEmit(true, true, false, false); + emit Work(NETWORK, opId1); + keeper.work(NETWORK, ""); + + assertEq(mockTarget.value(), 1, "First operation executed"); + assertEq(timelock.getOperationCount(), 1, "One operation remaining"); + + // Execute second + keeper.work(NETWORK, ""); + assertEq(mockTarget.value(), 2, "Second operation executed"); + assertEq(timelock.getOperationCount(), 0, "No operations remaining"); + } + + function testWorkSkipsNotReadyOperations() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + + // First operation with longer delay (not ready yet) + _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), 10 days); + // Second operation with shorter delay (will be ready) + bytes32 opId2 = _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("s2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Keeper should skip first (not ready) and execute second + vm.expectEmit(true, true, false, false); + emit Work(NETWORK, opId2); + keeper.work(NETWORK, ""); + + assertEq(mockTarget.value(), 2, "Second operation executed"); + assertEq(timelock.getOperationCount(), 1, "First operation still pending"); + } + + function testWorkWithPredecessor() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + + bytes32 opId1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + _schedule(address(mockTarget), 0, data2, opId1, keccak256("s2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // First work executes first operation + keeper.work(NETWORK, ""); + assertEq(mockTarget.value(), 1, "First operation executed"); + + // Second work executes second operation (predecessor now done) + keeper.work(NETWORK, ""); + assertEq(mockTarget.value(), 2, "Second operation executed"); + } + + function testWorkRespectsMaxIterations() public { + // Schedule more than maxIterations operations, with the ready one beyond the limit + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + // Schedule 101 operations with long delay (not ready) + for (uint256 i = 0; i < 101; i++) { + vm.prank(proposer); + timelock.scheduleBatch( + _toArray(address(mockTarget)), + _toArray(uint256(0)), + _toArrayBytes(data), + bytes32(0), + keccak256(abi.encodePacked("notready", i)), + 10 days + ); + } + + // Schedule 1 ready operation (will be at index 101, beyond maxIterations=100) + vm.prank(proposer); + timelock.scheduleBatch( + _toArray(address(mockTarget)), + _toArray(uint256(0)), + _toArrayBytes(data), + bytes32(0), + keccak256("ready"), + MIN_DELAY + ); + + vm.warp(block.timestamp + MIN_DELAY); + + // Keeper should not find the operation because it's beyond maxIterations + vm.expectRevert(TimelockKeeperJob.NoExecutableOperation.selector); + keeper.work(NETWORK, ""); + } + + // ============================================================================ + // Workable Function Tests + // ============================================================================ + + function testWorkableReturnsFalseIfNotMaster() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + bytes32 wrongNetwork = keccak256("wrong-network"); + (bool canWork, bytes memory reason) = keeper.workable(wrongNetwork); + + assertFalse(canWork, "Should not be workable on wrong network"); + assertEq(string(reason), "Network is not master", "Correct reason"); + } + + function testWorkableReturnsFalseIfNoOperation() public { + (bool canWork, bytes memory reason) = keeper.workable(NETWORK); + + assertFalse(canWork, "Should not be workable with no operations"); + assertEq(string(reason), "No executable operation", "Correct reason"); + } + + function testWorkableReturnsFalseIfOperationNotReady() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + // Don't warp - not ready + (bool canWork, bytes memory reason) = keeper.workable(NETWORK); + + assertFalse(canWork, "Should not be workable when not ready"); + assertEq(string(reason), "No executable operation", "Correct reason"); + } + + function testWorkableReturnsTrueIfOperationReady() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Use snapshot to prevent workable() from persisting state changes + uint256 snapshot = vm.snapshot(); + (bool canWork, bytes memory args) = keeper.workable(NETWORK); + vm.revertTo(snapshot); + + assertTrue(canWork, "Should be workable when ready"); + assertEq(args, "", "Args is empty"); + + // Verify state was not changed by workable check + assertFalse(mockTarget.called(), "Target should not be called by workable check"); + assertEq(timelock.getOperationCount(), 1, "Operation should still exist"); + } + + function testWorkableReturnsFalseIfTargetReverts() public { + bytes memory data = abi.encodeWithSelector(MockTarget.revertCall.selector); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + (bool canWork, bytes memory reason) = keeper.workable(NETWORK); + + assertFalse(canWork, "Should not be workable when target reverts"); + assertEq(string(reason), "No executable operation", "Correct reason"); + } + + // ============================================================================ + // ETH Handling Tests + // ============================================================================ + + /// Operations requiring ETH will fail via keeper since value is always 0 + function testWorkFailsIfOperationNeedsETHAndTimelockNotFunded() public { + // Clear timelock balance + vm.prank(address(timelock)); + (bool success,) = address(0xdead).call{value: address(timelock).balance}(""); + require(success); + + bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); + _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Will fail because timelock has no ETH and keeper sends value: 0 + vm.expectRevert(); + keeper.work(NETWORK, ""); + } + + function testWorkSucceedsIfTimelockPreFunded() public { + // Timelock is already funded in setUp + bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); + _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + keeper.work(NETWORK, ""); + + assertTrue(mockTarget.called(), "Target received ETH from pre-funded timelock"); + } + + // ============================================================================ + // Access Control Tests + // ============================================================================ + + function testAnyoneCanCallWork() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Random address can call work + address randomCaller = address(0x12345); + vm.prank(randomCaller); + keeper.work(NETWORK, ""); + + assertTrue(mockTarget.called(), "Anyone can execute via keeper"); + } + + function testAnyoneCanCallWorkable() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + address randomCaller = address(0x12345); + vm.prank(randomCaller); + (bool canWork,) = keeper.workable(NETWORK); + + assertTrue(canWork, "Anyone can call workable"); + } +} From 22ed34667874d165ba144b8c58cf652bab98c7cd Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 16 Jan 2026 13:03:34 -0300 Subject: [PATCH 049/150] Change visibility function --- src/BeamState.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 5e1598b..0ba457b 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -119,7 +119,7 @@ contract BeamState { emit Deny(usr); } - function setUserRole(address who, uint8 role, bool enabled) public auth { + function setUserRole(address who, uint8 role, bool enabled) external auth { bytes32 mask = bytes32(uint256(1) << role); if (enabled) { userRoles[who] |= mask; From 8b10cef27439e6862f66f64efd43be6cd82735c6 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 16 Jan 2026 13:06:54 -0300 Subject: [PATCH 050/150] Simplify Timelock constructor --- src/timelock/Timelock.sol | 19 +++------------ test/timelock/Timelock.t.sol | 33 ++++++++++++--------------- test/timelock/TimelockKeeperJob.t.sol | 8 +++---- 3 files changed, 21 insertions(+), 39 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 57fe489..64cbfc3 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -51,24 +51,11 @@ contract Timelock is TimelockController, Pausable { constructor( uint256 minDelay, - address admin, - address[] memory proposers, - address[] memory cancellers, // by default all proposers are added as cancellers, so no need to include them here - address[] memory pausers - ) TimelockController(minDelay, proposers, new address[](0), admin) { - - _revokeRole(DEFAULT_ADMIN_ROLE, address(this)); // do not allow proposers to change admin-only configurations + address admin + ) TimelockController(minDelay, new address[](0), new address[](0), admin) { require(admin != address(0), "Timelock/admin-zero-address"); - // add cancellers which are not necessarily proposers - for (uint256 i = 0; i < cancellers.length; ++i) { - _grantRole(CANCELLER_ROLE, cancellers[i]); - } - - for (uint256 i = 0; i < pausers.length; ++i) { - _grantRole(PAUSER_ROLE, pausers[i]); - } - + _revokeRole(DEFAULT_ADMIN_ROLE, address(this)); // do not allow proposers to change admin-only configurations _grantRole(EXECUTOR_ROLE, address(0)); // allow anyone to execute } diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol index 58020a4..935a78a 100644 --- a/test/timelock/Timelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -83,20 +83,21 @@ contract TimelockTest is Test { pauser = address(0x4); other = address(0x5); - address[] memory proposers = new address[](2); - proposers[0] = proposer; - proposers[1] = proposer2; - - address[] memory cancellers = new address[](1); - cancellers[0] = canceller; - - address[] memory pausers = new address[](1); - pausers[0] = pauser; - - timelock = new Timelock(MIN_DELAY, admin, proposers, cancellers, pausers); + timelock = new Timelock(MIN_DELAY, admin); mockTarget = new MockTarget(); mockTarget2 = new MockTarget(); + // Grant roles after deployment + vm.startPrank(admin); + timelock.grantRole(timelock.PROPOSER_ROLE(), proposer); + timelock.grantRole(timelock.PROPOSER_ROLE(), proposer2); + // Proposers also get canceller role (matches original TimelockController behavior) + timelock.grantRole(timelock.CANCELLER_ROLE(), proposer); + timelock.grantRole(timelock.CANCELLER_ROLE(), proposer2); + timelock.grantRole(timelock.CANCELLER_ROLE(), canceller); + timelock.grantRole(timelock.PAUSER_ROLE(), pauser); + vm.stopPrank(); + vm.deal(address(timelock), 100 ether); } @@ -158,12 +159,13 @@ contract TimelockTest is Test { // Constructor and Initialization Tests // ============================================================================ - function testConstructorInitialization() public { + function testConstructorInitialization() public view { assertEq(timelock.getMinDelay(), MIN_DELAY); assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), admin)); assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), proposer)); assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), proposer2)); assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), proposer)); + assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), proposer2)); assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller)); assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser)); assertTrue(timelock.hasRole(timelock.EXECUTOR_ROLE(), address(0))); @@ -171,13 +173,8 @@ contract TimelockTest is Test { } function testConstructorRevertsIfAdminIsZero() public { - address[] memory proposers = new address[](1); - proposers[0] = proposer; - address[] memory cancellers = new address[](0); - address[] memory pausers = new address[](0); - vm.expectRevert("Timelock/admin-zero-address"); - new Timelock(MIN_DELAY, address(0), proposers, cancellers, pausers); + new Timelock(MIN_DELAY, address(0)); } // ============================================================================ diff --git a/test/timelock/TimelockKeeperJob.t.sol b/test/timelock/TimelockKeeperJob.t.sol index a982328..d714269 100644 --- a/test/timelock/TimelockKeeperJob.t.sol +++ b/test/timelock/TimelockKeeperJob.t.sol @@ -84,12 +84,10 @@ contract TimelockKeeperJobTest is Test { sequencer.setMaster(NETWORK, true); // Deploy timelock - address[] memory proposers = new address[](1); - proposers[0] = proposer; - address[] memory cancellers = new address[](0); - address[] memory pausers = new address[](0); + timelock = new Timelock(MIN_DELAY, admin); - timelock = new Timelock(MIN_DELAY, admin, proposers, cancellers, pausers); + // Grant proposer role after deployment + timelock.grantRole(timelock.PROPOSER_ROLE(), proposer); // Deploy keeper keeper = new TimelockKeeperJob(address(sequencer), address(timelock), 100); From 05bd0cc737c9a880a3bba3f6d06f82c8a9c1b747 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 16 Jan 2026 17:52:57 -0300 Subject: [PATCH 051/150] Fix license in files --- src/BeamState.sol | 15 +++++++++++++++ src/Configurator.sol | 15 +++++++++++++++ src/timelock/Timelock.sol | 2 +- src/timelock/TimelockKeeperJob.sol | 2 +- src/timelock/TimelockWrapper.sol | 2 +- test/BeamState.t.sol | 15 +++++++++++++++ test/Configurator.t.sol | 15 +++++++++++++++ test/timelock/Timelock.t.sol | 2 +- test/timelock/TimelockKeeperJob.t.sol | 2 +- 9 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 0ba457b..b830c13 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -1,4 +1,19 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + pragma solidity ^0.8.21; contract BeamState { diff --git a/src/Configurator.sol b/src/Configurator.sol index 6a2d40a..4c68ff4 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -1,4 +1,19 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + pragma solidity ^0.8.21; interface BeamStateLike { diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 64cbfc3..79b72cb 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-FileCopyrightText: © 2026 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol index 6367988..54adb79 100644 --- a/src/timelock/TimelockKeeperJob.sol +++ b/src/timelock/TimelockKeeperJob.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-FileCopyrightText: © 2026 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index 515e4a4..5f9fef3 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-FileCopyrightText: © 2026 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 1406e95..ff2025e 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -1,4 +1,19 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + pragma solidity ^0.8.21; import "dss-test/DssTest.sol"; diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 05e8a57..418f3f0 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -1,4 +1,19 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + pragma solidity ^0.8.21; import "dss-test/DssTest.sol"; diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol index 935a78a..564f03b 100644 --- a/test/timelock/Timelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-FileCopyrightText: © 2026 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify diff --git a/test/timelock/TimelockKeeperJob.t.sol b/test/timelock/TimelockKeeperJob.t.sol index d714269..3055397 100644 --- a/test/timelock/TimelockKeeperJob.t.sol +++ b/test/timelock/TimelockKeeperJob.t.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: © 2025 Dai Foundation +// SPDX-FileCopyrightText: © 2026 Dai Foundation // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify From 4b431c31219cf92ad44b2c0a260252720dd40b9f Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 16 Jan 2026 17:53:20 -0300 Subject: [PATCH 052/150] Add deploy scripts + some integration tests --- deploy/PASDeploy.sol | 50 +++ deploy/PASInit.sol | 125 ++++++ deploy/PASInstance.sol | 24 ++ test/Integration.t.sol | 836 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1035 insertions(+) create mode 100644 deploy/PASDeploy.sol create mode 100644 deploy/PASInit.sol create mode 100644 deploy/PASInstance.sol create mode 100644 test/Integration.t.sol diff --git a/deploy/PASDeploy.sol b/deploy/PASDeploy.sol new file mode 100644 index 0000000..26f459b --- /dev/null +++ b/deploy/PASDeploy.sol @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { PASInstance } from "./PASInstance.sol"; +import { BeamState } from "src/BeamState.sol"; +import { Configurator } from "src/Configurator.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; +import { TimelockWrapper } from "src/timelock/TimelockWrapper.sol"; + +library PASDeploy { + + function deploy( + address deployer, + address owner, + uint256 minDelay + ) internal returns (PASInstance memory pasInstance) { + pasInstance.beamState = address(new BeamState()); + + pasInstance.configurator = address(new Configurator(pasInstance.beamState)); + + pasInstance.timelock = address(new Timelock( + minDelay, + owner // admin + )); + + pasInstance.timelockWrapper = address(new TimelockWrapper( + pasInstance.timelock, + pasInstance.beamState + )); + + ScriptTools.switchOwner(pasInstance.beamState, deployer, owner); + ScriptTools.switchOwner(pasInstance.timelockWrapper, deployer, owner); + } +} diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol new file mode 100644 index 0000000..f165d1f --- /dev/null +++ b/deploy/PASInit.sol @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +import { PASInstance } from "./PASInstance.sol"; + +interface BeamStateLike { + function setUserRole(address, uint8, bool) external; + function setRoleAction(uint8, bytes4, bool) external; + function stop() external; + function start() external; + function setHop(address, uint256) external; + function setMaxChange(address, uint256) external; + function addRateLimits(address) external; + function delRateLimits(address) external; + function addController(address) external; + function delController(address) external; + function addCBeam(address) external; + function delCBeam(address) external; + function setCBeamForRateLimits(address, address) external; + function unsetCBeamForRateLimits(address, address) external; + function setCBeamForController(address, address) external; + function unsetCBeamForController(address, address) external; + function addInitRateLimits(bytes32, address, uint256, uint256) external; + function delInitRateLimits(bytes32, address) external; + function addInitControllerActions(bytes calldata data, address) external; + function delInitControllerActions(bytes32, address) external; +} + +interface ConfiguratorLike { + function beamState() external view returns (address); +} + +interface TimelockLike { + function getMinDelay() external view returns (uint256); + function PROPOSER_ROLE() external view returns (bytes32); + function CANCELLER_ROLE() external view returns (bytes32); + function PAUSER_ROLE() external view returns (bytes32); + function grantRole(bytes32, address) external; +} + +interface TimelockWrapperLike { + function timelock() external view returns (address); + function beamState() external view returns (address); + function kiss(address) external; +} + +library PASInit { + uint256 constant internal WAD = 10**18; + + function init( + PASInstance memory pasInstance, + uint256 minDelay, + address coreCouncil, + address[] memory cancellers, + address[] memory pausers + ) internal { + BeamStateLike beamState = BeamStateLike(pasInstance.beamState); + ConfiguratorLike configurator = ConfiguratorLike(pasInstance.configurator); + TimelockLike timelock = TimelockLike(pasInstance.timelock); + TimelockWrapperLike timelockWrapper = TimelockWrapperLike(pasInstance.timelockWrapper); + + // --- Sanity checks --- + + require(configurator.beamState() == address(beamState), "PASInit/configurator-beamState-mismatch"); + require(timelock.getMinDelay() == minDelay, "PASInit/timelock-minDelay-mismatch"); + require(timelockWrapper.timelock() == address(timelock), "PASInit/wrapper-timelock-mismatch"); + require(timelockWrapper.beamState() == address(beamState), "PASInit/wrapper-beamState-mismatch"); + + // --- Configure BeamState --- + + // Define Beam actions that are accessed through timelock (role 1) and directly (role 2) + beamState.setRoleAction(1, BeamStateLike.start.selector, true); + beamState.setRoleAction(1, BeamStateLike.setHop.selector, true); + beamState.setRoleAction(1, BeamStateLike.setMaxChange.selector, true); + beamState.setRoleAction(1, BeamStateLike.addRateLimits.selector, true); + beamState.setRoleAction(1, BeamStateLike.addController.selector, true); + beamState.setRoleAction(1, BeamStateLike.addCBeam.selector, true); + beamState.setRoleAction(1, BeamStateLike.addInitRateLimits.selector, true); + beamState.setRoleAction(1, BeamStateLike.addInitControllerActions.selector, true); + beamState.setRoleAction(2, BeamStateLike.stop.selector, true); + beamState.setRoleAction(2, BeamStateLike.delRateLimits.selector, true); + beamState.setRoleAction(2, BeamStateLike.delController.selector, true); + beamState.setRoleAction(2, BeamStateLike.delCBeam.selector, true); + beamState.setRoleAction(2, BeamStateLike.setCBeamForRateLimits.selector, true); + beamState.setRoleAction(2, BeamStateLike.unsetCBeamForRateLimits.selector, true); + beamState.setRoleAction(2, BeamStateLike.setCBeamForController.selector, true); + beamState.setRoleAction(2, BeamStateLike.unsetCBeamForController.selector, true); + beamState.setRoleAction(2, BeamStateLike.delInitRateLimits.selector, true); + beamState.setRoleAction(2, BeamStateLike.delInitControllerActions.selector, true); + + // Set timelock as the user with role 1 and coreCouncil as the one with role 2 + beamState.setUserRole(address(timelock), 1, true); + beamState.setUserRole(coreCouncil, 2, true); + + // --- Configure Timelock and Wrapper --- + + // Grant the coreCouncil as the proposer in the timelock either directly or through the wrapper + // Grant cancellers and pausers in timelock with their respective roles + timelock.grantRole(timelock.PROPOSER_ROLE(), coreCouncil); + timelock.grantRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)); + timelock.grantRole(timelock.CANCELLER_ROLE(), coreCouncil); + for (uint256 i = 0; i < cancellers.length; ++i) { + timelock.grantRole(timelock.CANCELLER_ROLE(), cancellers[i]); + } + for (uint256 i = 0; i < pausers.length; ++i) { + timelock.grantRole(timelock.PAUSER_ROLE(), pausers[i]); + } + timelockWrapper.kiss(coreCouncil); + } +} diff --git a/deploy/PASInstance.sol b/deploy/PASInstance.sol new file mode 100644 index 0000000..b9ecd8e --- /dev/null +++ b/deploy/PASInstance.sol @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +struct PASInstance { + address beamState; + address configurator; + address timelock; + address timelockWrapper; +} diff --git a/test/Integration.t.sol b/test/Integration.t.sol new file mode 100644 index 0000000..edd6791 --- /dev/null +++ b/test/Integration.t.sol @@ -0,0 +1,836 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { PASInstance } from "deploy/PASInstance.sol"; +import { PASDeploy } from "deploy/PASDeploy.sol"; +import { PASInit } from "deploy/PASInit.sol"; +import { BeamState } from "src/BeamState.sol"; +import { Configurator, RateLimitsLike } from "src/Configurator.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; +import { TimelockWrapper, RateLimitConfig } from "src/timelock/TimelockWrapper.sol"; + +// Mock contract implementing RateLimits interface and controller functionality +contract MockTarget { + RateLimitsLike.RateLimitData public rateLimitData; + uint256 public currentRateLimit; + bool public shouldFail; + bytes public lastCallData; + + function setRateLimitData( + bytes32, + uint256 maxAmount, + uint256 slope, + uint256 lastAmount, + uint256 lastUpdated + ) external { + rateLimitData = RateLimitsLike.RateLimitData(maxAmount, slope, lastAmount, lastUpdated); + } + + function setUnlimitedRateLimitData(bytes32) external { + rateLimitData = RateLimitsLike.RateLimitData(type(uint256).max, 0, type(uint256).max, block.timestamp); + } + + function getRateLimitData(bytes32) external view returns (RateLimitsLike.RateLimitData memory) { + return rateLimitData; + } + + function getCurrentRateLimit(bytes32) external view returns (uint256) { + return currentRateLimit; + } + + function setCurrentRateLimit(uint256 _current) external { + currentRateLimit = _current; + } + + function setShouldFail(bool _fail) external { + shouldFail = _fail; + } + + // Mock controller function for testing callControllerAction + function controllerFunction(uint256 value) external returns (uint256) { + lastCallData = msg.data; + require(!shouldFail, "MockTarget/controller-function-failed"); + return value * 2; + } + + fallback() external { + lastCallData = msg.data; + require(!shouldFail, "MockTarget/fallback-failed"); + } +} + +contract IntegrationTest is Test { + + PASInstance public pas; + BeamState public beamState; + Configurator public configurator; + Timelock public timelock; + TimelockWrapper public timelockWrapper; + + address public deployer; + address public owner; // Timelock admin + address public coreCouncil; // Has role 2 on BeamState, proposer/canceller on Timelock + address public canceller; + address public pauser; + address public cBeam; // Mock cBeam address + address public rateLimits; // Mock rate limits address + address public controller; // Mock controller address + + uint256 public constant MIN_DELAY = 1 days; + bytes32 public constant SALT = keccak256("integration-test"); + + // Events + event CallScheduled( + bytes32 indexed id, + uint256 indexed index, + address target, + uint256 value, + bytes data, + bytes32 predecessor, + uint256 delay + ); + event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data); + event Cancelled(bytes32 indexed id); + event Paused(address account); + event Unpaused(address account); + event ProposalSubmitted(bytes32 indexed operationId, string functionName); + + function setUp() public { + deployer = address(this); + owner = address(0x1); + coreCouncil = address(0x2); + canceller = address(0x3); + pauser = address(0x4); + cBeam = address(0x5); + rateLimits = address(0x6); + controller = address(0x7); + + // Deploy using PASDeploy + pas = PASDeploy.deploy(deployer, owner, MIN_DELAY); + + // Cast to typed contracts + beamState = BeamState(pas.beamState); + configurator = Configurator(pas.configurator); + timelock = Timelock(payable(pas.timelock)); + timelockWrapper = TimelockWrapper(pas.timelockWrapper); + + // Initialize using PASInit (must be called by owner who has auth on BeamState and Timelock) + address[] memory cancellers = new address[](1); + cancellers[0] = canceller; + + address[] memory pausers = new address[](1); + pausers[0] = pauser; + + vm.startPrank(owner); + PASInit.init(pas, MIN_DELAY, coreCouncil, cancellers, pausers); + vm.stopPrank(); + } + + // ============================================================================ + // Deployment and Initialization Tests + // ============================================================================ + + function testDeploymentAddresses() public view { + assertTrue(pas.beamState != address(0), "beamState should be deployed"); + assertTrue(pas.configurator != address(0), "configurator should be deployed"); + assertTrue(pas.timelock != address(0), "timelock should be deployed"); + assertTrue(pas.timelockWrapper != address(0), "timelockWrapper should be deployed"); + } + + function testConfiguratorLinkedToBeamState() public view { + assertEq(address(configurator.beamState()), address(beamState), "configurator should reference beamState"); + } + + function testTimelockWrapperLinkedCorrectly() public view { + assertEq(address(timelockWrapper.timelock()), address(timelock), "wrapper should reference timelock"); + assertEq(address(timelockWrapper.beamState()), address(beamState), "wrapper should reference beamState"); + } + + function testTimelockRolesAfterInit() public view { + assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), owner), "owner should be admin"); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should be proposer"); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)), "wrapper should be proposer"); + assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), coreCouncil), "coreCouncil should be canceller"); + assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller), "canceller should have role"); + assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser), "pauser should have role"); + } + + function testBeamStateRolesAfterInit() public view { + // Timelock has role 1 + assertTrue(beamState.hasUserRole(address(timelock), 1), "timelock should have role 1"); + // CoreCouncil has role 2 + assertTrue(beamState.hasUserRole(coreCouncil, 2), "coreCouncil should have role 2"); + } + + function testBeamStateActionsConfigured() public view { + // Role 1 actions (timelocked) + assertTrue(beamState.isActionInRole(BeamState.start.selector, 1), "start should be role 1"); + assertTrue(beamState.isActionInRole(BeamState.setHop.selector, 1), "setHop should be role 1"); + assertTrue(beamState.isActionInRole(BeamState.setMaxChange.selector, 1), "setMaxChange should be role 1"); + assertTrue(beamState.isActionInRole(BeamState.addRateLimits.selector, 1), "addRateLimits should be role 1"); + assertTrue(beamState.isActionInRole(BeamState.addController.selector, 1), "addController should be role 1"); + assertTrue(beamState.isActionInRole(BeamState.addCBeam.selector, 1), "addCBeam should be role 1"); + assertTrue(beamState.isActionInRole(BeamState.addInitRateLimits.selector, 1), "addInitRateLimits should be role 1"); + assertTrue(beamState.isActionInRole(BeamState.addInitControllerActions.selector, 1), "addInitControllerActions should be role 1"); + + // Role 2 actions (direct) + assertTrue(beamState.isActionInRole(BeamState.stop.selector, 2), "stop should be role 2"); + assertTrue(beamState.isActionInRole(BeamState.delRateLimits.selector, 2), "delRateLimits should be role 2"); + assertTrue(beamState.isActionInRole(BeamState.delController.selector, 2), "delController should be role 2"); + assertTrue(beamState.isActionInRole(BeamState.delCBeam.selector, 2), "delCBeam should be role 2"); + assertTrue(beamState.isActionInRole(BeamState.setCBeamForRateLimits.selector, 2), "setCBeamForRateLimits should be role 2"); + assertTrue(beamState.isActionInRole(BeamState.unsetCBeamForRateLimits.selector, 2), "unsetCBeamForRateLimits should be role 2"); + assertTrue(beamState.isActionInRole(BeamState.setCBeamForController.selector, 2), "setCBeamForController should be role 2"); + assertTrue(beamState.isActionInRole(BeamState.unsetCBeamForController.selector, 2), "unsetCBeamForController should be role 2"); + assertTrue(beamState.isActionInRole(BeamState.delInitRateLimits.selector, 2), "delInitRateLimits should be role 2"); + assertTrue(beamState.isActionInRole(BeamState.delInitControllerActions.selector, 2), "delInitControllerActions should be role 2"); + } + + function testTimelockWrapperBudsAfterInit() public view { + assertEq(timelockWrapper.buds(coreCouncil), 1, "coreCouncil should be whitelisted on wrapper"); + } + + // ============================================================================ + // CoreCouncil Direct Actions (Role 2) Tests + // ============================================================================ + + function testCoreCouncilCanStop() public { + assertFalse(beamState.stopped(), "should not be stopped initially"); + + vm.prank(coreCouncil); + beamState.stop(); + + assertTrue(beamState.stopped(), "should be stopped after coreCouncil calls stop"); + } + + function testCoreCouncilCanDelRateLimits() public { + // Setup: owner adds rateLimits + vm.prank(owner); + beamState.addRateLimits(rateLimits); + assertEq(beamState.rateLimits(rateLimits), 1, "rateLimits should be added"); + + // CoreCouncil can delete (role 2) + vm.prank(coreCouncil); + beamState.delRateLimits(rateLimits); + + assertEq(beamState.rateLimits(rateLimits), 0, "rateLimits should be deleted"); + } + + function testCoreCouncilCanDelController() public { + // Setup: owner adds controller + vm.prank(owner); + beamState.addController(controller); + assertEq(beamState.controllers(controller), 1, "controller should be added"); + + // CoreCouncil can delete (role 2) + vm.prank(coreCouncil); + beamState.delController(controller); + + assertEq(beamState.controllers(controller), 0, "controller should be deleted"); + } + + function testCoreCouncilCanDelCBeam() public { + // Owner adds cBeam directly (has auth/wards) + vm.prank(owner); + beamState.addCBeam(cBeam); + assertEq(beamState.cBeams(cBeam), 1, "cBeam should be added"); + + // CoreCouncil can delete (role 2 action) + vm.prank(coreCouncil); + beamState.delCBeam(cBeam); + assertEq(beamState.cBeams(cBeam), 0, "cBeam should be deleted"); + } + + function testCoreCouncilCanSetAndUnsetCBeamForRateLimits() public { + // Setup: owner adds rateLimits and cBeam, then sets association + vm.startPrank(owner); + beamState.addRateLimits(rateLimits); + beamState.addCBeam(cBeam); + vm.stopPrank(); + + vm.prank(coreCouncil); + beamState.setCBeamForRateLimits(rateLimits, cBeam); + assertEq(beamState.rateLimitsCBeams(rateLimits, cBeam), 1, "cBeam should be set"); + + // CoreCouncil can unset association (role 2) + vm.prank(coreCouncil); + beamState.unsetCBeamForRateLimits(rateLimits, cBeam); + assertEq(beamState.rateLimitsCBeams(rateLimits, cBeam), 0, "cBeam should be unset for rateLimits"); + } + + function testCoreCouncilCanSetAndUnsetCBeamForController() public { + // Setup: owner adds controller and cBeam, then sets association + vm.startPrank(owner); + beamState.addController(controller); + beamState.addCBeam(cBeam); + vm.stopPrank(); + + vm.prank(coreCouncil); + beamState.setCBeamForController(controller, cBeam); + assertEq(beamState.controllersCBeams(controller, cBeam), 1, "cBeam should be set"); + + // CoreCouncil can unset association (role 2) + vm.prank(coreCouncil); + beamState.unsetCBeamForController(controller, cBeam); + + assertEq(beamState.controllersCBeams(controller, cBeam), 0, "cBeam should be unset for controller"); + } + + function testCoreCouncilCanDelInitRateLimits() public { + bytes32 key = keccak256("test-rate-limit-key"); + + // Setup: owner adds init rate limits + vm.prank(owner); + beamState.addInitRateLimits(key, rateLimits, 1_000 ether, 100 ether); + + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, rateLimits); + assertEq(limits.maxAmount, 1_000 ether, "maxAmount should be set"); + assertEq(limits.slope, 100 ether, "slope should be set"); + + // CoreCouncil can delete (role 2) + vm.prank(coreCouncil); + beamState.delInitRateLimits(key, rateLimits); + limits = beamState.getInitRateLimits(key, rateLimits); + assertEq(limits.maxAmount, 0, "maxAmount should be deleted"); + assertEq(limits.slope, 0, "slope should be deleted"); + } + + function testCoreCouncilCanDelInitControllerActions() public { + bytes memory actionData = abi.encodeWithSignature("someAction(uint256)", 42); + + // Setup: owner adds init controller action + vm.prank(owner); + bytes32 key = beamState.addInitControllerActions(actionData, controller); + + assertTrue(beamState.isControllerActionEnabled(key, controller), "action should be enabled"); + + // CoreCouncil can delete (role 2) + vm.prank(coreCouncil); + beamState.delInitControllerActions(key, controller); + assertFalse(beamState.isControllerActionEnabled(key, controller), "action should be disabled"); + } + + function testNonAuthorizedCannotDoRole2Actions() public { + address unauthorized = address(0x999); + + vm.prank(unauthorized); + vm.expectRevert("BeamState/role-not-authorized"); + beamState.stop(); + } + + // ============================================================================ + // Timelock Flow Tests (Role 1 Actions) + // ============================================================================ + + function _scheduleAndExecute(bytes memory payload, bytes32 salt) internal { + address[] memory targets = new address[](1); + targets[0] = address(beamState); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + payloads[0] = payload; + + vm.prank(coreCouncil); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), salt, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + timelock.executeBatch(targets, values, payloads, bytes32(0), salt); + } + + function testTimelockCanStart() public { + // First stop via coreCouncil (role 2) + vm.prank(coreCouncil); + beamState.stop(); + assertTrue(beamState.stopped(), "should be stopped"); + + // Schedule and execute start via timelock (role 1) + _scheduleAndExecute(abi.encodeWithSelector(BeamState.start.selector), keccak256("start")); + + assertFalse(beamState.stopped(), "should be started via timelock"); + } + + function testTimelockCanSetHop() public { + uint256 newHop = 4 hours; + + _scheduleAndExecute( + abi.encodeWithSelector(BeamState.setHop.selector, address(0), newHop), + keccak256("setHop") + ); + + assertEq(beamState.getHop(address(0)), newHop, "hop should be set via timelock"); + } + + function testTimelockCanSetMaxChange() public { + uint256 newMaxChange = 2 ether; // 200% in WAD + + _scheduleAndExecute( + abi.encodeWithSelector(BeamState.setMaxChange.selector, address(0), newMaxChange), + keccak256("setMaxChange") + ); + + assertEq(beamState.getMaxChange(address(0)), newMaxChange, "maxChange should be set via timelock"); + } + + function testTimelockCanAddRateLimits() public { + address newRateLimits = address(0xA1); + + _scheduleAndExecute( + abi.encodeWithSelector(BeamState.addRateLimits.selector, newRateLimits), + keccak256("addRateLimits") + ); + + assertEq(beamState.rateLimits(newRateLimits), 1, "rateLimits should be added via timelock"); + } + + function testTimelockCanAddController() public { + address newController = address(0xC0); + + _scheduleAndExecute( + abi.encodeWithSelector(BeamState.addController.selector, newController), + keccak256("addController") + ); + + assertEq(beamState.controllers(newController), 1, "controller should be added via timelock"); + } + + function testTimelockCanAddCBeam() public { + _scheduleAndExecute( + abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), + keccak256("addCBeam") + ); + + assertEq(beamState.cBeams(cBeam), 1, "cBeam should be added via timelock"); + } + + function testTimelockCanAddInitRateLimits() public { + bytes32 key = keccak256("test-key"); + uint256 maxAmount = 1_000 ether; + uint256 slope = 100 ether; + + _scheduleAndExecute( + abi.encodeWithSelector(BeamState.addInitRateLimits.selector, key, rateLimits, maxAmount, slope), + keccak256("addInitRateLimits") + ); + + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, rateLimits); + assertEq(limits.maxAmount, maxAmount, "maxAmount should be set via timelock"); + assertEq(limits.slope, slope, "slope should be set via timelock"); + } + + function testTimelockCanAddInitControllerActions() public { + bytes memory actionData = abi.encodeWithSignature("someAction(uint256)", 42); + + _scheduleAndExecute( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, actionData, controller), + keccak256("addInitControllerActions") + ); + + bytes32 key = keccak256(actionData); + assertTrue(beamState.isControllerActionEnabled(key, controller), "action should be enabled via timelock"); + } + + // ============================================================================ + // TimelockWrapper Tests + // ============================================================================ + + function testWrapperCanSchedule() public { + // First stop via coreCouncil + vm.prank(coreCouncil); + beamState.stop(); + assertTrue(beamState.stopped(), "should be stopped"); + + // Schedule start via wrapper + vm.prank(coreCouncil); + bytes32 operationId = timelockWrapper.start(bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + Timelock.Operation memory op = timelock.getOperation(operationId); + timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); + + assertFalse(beamState.stopped(), "should be started via wrapper"); + } + + // ============================================================================ + // Cancellation Tests + // ============================================================================ + + function testCoreCouncilCanCancelOperation() public { + // Schedule operation + vm.prank(coreCouncil); + bytes32 operationId = timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + + assertTrue(timelock.isOperationPending(operationId), "operation should be pending"); + + // Cancel + vm.prank(coreCouncil); + timelock.cancel(operationId); + + assertFalse(timelock.isOperationPending(operationId), "operation should be cancelled"); + assertEq(timelock.getOperationCount(), 0, "operation count should be 0"); + } + + function testCancellerCanCancelOperation() public { + vm.prank(coreCouncil); + bytes32 operationId = timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + + vm.prank(canceller); + timelock.cancel(operationId); + + assertFalse(timelock.isOperationPending(operationId), "operation should be cancelled by canceller"); + } + + // ============================================================================ + // Pausing Tests + // ============================================================================ + + function testPauserCanPauseTimelock() public { + vm.prank(pauser); + timelock.pause(); + + assertTrue(timelock.paused(), "timelock should be paused"); + } + + function testPausedTimelockBlocksScheduling() public { + vm.prank(pauser); + timelock.pause(); + + vm.prank(coreCouncil); + vm.expectRevert(); + timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + } + + function testPausedTimelockBlocksExecution() public { + // Schedule first + vm.prank(coreCouncil); + bytes32 operationId = timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Pause + vm.prank(pauser); + timelock.pause(); + + // Try to execute + Timelock.Operation memory op = timelock.getOperation(operationId); + vm.expectRevert(); + timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); + } + + function testAdminCanUnpause() public { + vm.prank(pauser); + timelock.pause(); + + vm.prank(owner); + timelock.unpause(); + + assertFalse(timelock.paused(), "timelock should be unpaused"); + } + + function testPauserCannotUnpause() public { + vm.prank(pauser); + timelock.pause(); + + vm.prank(pauser); + vm.expectRevert(); + timelock.unpause(); + } + + // ============================================================================ + // Predecessor Chain Tests + // ============================================================================ + + function testOperationsWithPredecessor() public { + // Schedule first operation: addRateLimits + bytes memory payload1 = abi.encodeWithSelector(BeamState.addRateLimits.selector, rateLimits); + address[] memory targets = new address[](1); + targets[0] = address(beamState); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads1 = new bytes[](1); + payloads1[0] = payload1; + + vm.prank(coreCouncil); + timelock.scheduleBatch(targets, values, payloads1, bytes32(0), keccak256("op1"), MIN_DELAY); + bytes32 id1 = timelock.hashOperationBatch(targets, values, payloads1, bytes32(0), keccak256("op1")); + + // Schedule second operation: addController with predecessor + bytes memory payload2 = abi.encodeWithSelector(BeamState.addController.selector, controller); + bytes[] memory payloads2 = new bytes[](1); + payloads2[0] = payload2; + + vm.prank(coreCouncil); + timelock.scheduleBatch(targets, values, payloads2, id1, keccak256("op2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Try to execute second before first - should fail + vm.expectRevert(); + timelock.executeBatch(targets, values, payloads2, id1, keccak256("op2")); + + // Execute first + timelock.executeBatch(targets, values, payloads1, bytes32(0), keccak256("op1")); + assertEq(beamState.rateLimits(rateLimits), 1, "rateLimits should be added"); + + // Now execute second + timelock.executeBatch(targets, values, payloads2, id1, keccak256("op2")); + assertEq(beamState.controllers(controller), 1, "controller should be added"); + } + + // ============================================================================ + // Full Workflow Test + // ============================================================================ + + function testFullOnboardingWorkflow() public { + // 1. CoreCouncil schedules adding a cBeam via wrapper + vm.prank(coreCouncil); + bytes32 addCBeamOp = timelockWrapper.addCBeam(cBeam, bytes32(0), keccak256("step1"), MIN_DELAY); + + // 2. Schedule addRateLimits with predecessor + vm.prank(coreCouncil); + bytes32 addRateLimitsOp = timelockWrapper.addRateLimits(rateLimits, addCBeamOp, keccak256("step2"), MIN_DELAY); + + // 3. Schedule setHop with predecessor + vm.prank(coreCouncil); + bytes32 setHopOp = timelockWrapper.setHop(rateLimits, 4 hours, addRateLimitsOp, keccak256("step3"), MIN_DELAY); + + // Wait for delay + vm.warp(block.timestamp + MIN_DELAY); + + // 4. Execute in order + Timelock.Operation memory op1 = timelock.getOperation(addCBeamOp); + timelock.executeBatch(op1.targets, op1.values, op1.payloads, op1.predecessor, op1.salt); + assertEq(beamState.cBeams(cBeam), 1, "cBeam added"); + + Timelock.Operation memory op2 = timelock.getOperation(addRateLimitsOp); + timelock.executeBatch(op2.targets, op2.values, op2.payloads, op2.predecessor, op2.salt); + assertEq(beamState.rateLimits(rateLimits), 1, "rateLimits added"); + + Timelock.Operation memory op3 = timelock.getOperation(setHopOp); + timelock.executeBatch(op3.targets, op3.values, op3.payloads, op3.predecessor, op3.salt); + assertEq(beamState.getHop(rateLimits), 4 hours, "hop set"); + + // 5. CoreCouncil can now directly set cBeam for rateLimits (role 2) + vm.prank(coreCouncil); + beamState.setCBeamForRateLimits(rateLimits, cBeam); + assertEq(beamState.rateLimitsCBeams(rateLimits, cBeam), 1, "cBeam associated with rateLimits"); + + // 6. Emergency stop by coreCouncil (direct, no timelock) + vm.prank(coreCouncil); + beamState.stop(); + assertTrue(beamState.stopped(), "system stopped"); + + // 7. Restart requires timelock + vm.prank(coreCouncil); + bytes32 startOp = timelockWrapper.start(bytes32(0), keccak256("restart"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + Timelock.Operation memory restartOp = timelock.getOperation(startOp); + timelock.executeBatch(restartOp.targets, restartOp.values, restartOp.payloads, restartOp.predecessor, restartOp.salt); + assertFalse(beamState.stopped(), "system restarted"); + } + + // ============================================================================ + // Admin Delay Change Test + // ============================================================================ + + function testAdminCanChangeDelayImmediately() public { + uint256 newDelay = 2 days; + + vm.prank(owner); + timelock.updateDelayImmediately(newDelay); + + assertEq(timelock.getMinDelay(), newDelay, "delay should be updated immediately"); + } + + function testNonAdminCannotChangeDelay() public { + vm.prank(coreCouncil); + vm.expectRevert(); + timelock.updateDelayImmediately(2 days); + } + + // ============================================================================ + // Post-Onboarding cBeam Operations Test + // ============================================================================ + + function _scheduleDirect(bytes memory payload, bytes32 predecessor, bytes32 salt) internal returns (bytes32) { + address[] memory targets = new address[](1); + targets[0] = address(beamState); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + payloads[0] = payload; + + vm.prank(coreCouncil); + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, MIN_DELAY); + + return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + } + + function testCBeamCanOperateAfterOnboarding() public { + // Deploy mock targets for rateLimits and controller + MockTarget mockRateLimits = new MockTarget(); + MockTarget mockController = new MockTarget(); + + bytes32 rateLimitKey = keccak256("test-rate-limit"); + bytes memory controllerAction = abi.encodeWithSelector(MockTarget.controllerFunction.selector, 42); + + // ======================================== + // Phase 1: Onboard via Timelock (Role 1) + // ======================================== + bytes32[] memory opIds = new bytes32[](7); + { + // 1a. Schedule addCBeam + vm.prank(coreCouncil); + opIds[0] = timelockWrapper.addCBeam(cBeam, bytes32(0), keccak256("addCBeam"), MIN_DELAY); + + // 1b. Schedule addRateLimits for mock target + vm.prank(coreCouncil); + opIds[1] = timelockWrapper.addRateLimits(address(mockRateLimits), opIds[0], keccak256("addRateLimits"), MIN_DELAY); + + // 1c. Schedule addController for mock target + vm.prank(coreCouncil); + opIds[2] = timelockWrapper.addController(address(mockController), opIds[1], keccak256("addController"), MIN_DELAY); + + // 1d. Schedule setHop for rateLimits + vm.prank(coreCouncil); + opIds[3] = timelockWrapper.setHop(address(mockRateLimits), 1 hours, opIds[2], keccak256("setHop"), MIN_DELAY); + + // 1e. Schedule setMaxChange for rateLimits + vm.prank(coreCouncil); + opIds[4] = timelockWrapper.setMaxChange(address(mockRateLimits), 2 ether, opIds[3], keccak256("setMaxChange"), MIN_DELAY); + + // 1f. Schedule addInitRateLimits (via wrapper with RateLimitConfig) + vm.prank(coreCouncil); + opIds[5] = timelockWrapper.addInitRateLimits( + RateLimitConfig({key: rateLimitKey, rateLimits_: address(mockRateLimits), maxAmount: 1__000 ether, slope: 100 ether}), + opIds[4], keccak256("addInitRateLimits"), MIN_DELAY + ); + + // 1g. Schedule addInitControllerActions (directly via timelock - no wrapper for generic actions) + opIds[6] = _scheduleDirect( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, controllerAction, address(mockController)), + opIds[5], keccak256("addInitControllerActions") + ); + } + + // Wait for delay + vm.warp(block.timestamp + MIN_DELAY); + + // Execute all operations in order + for (uint256 i = 0; i < opIds.length; i++) { + Timelock.Operation memory op = timelock.getOperation(opIds[i]); + timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); + } + + // Verify onboarding results + assertEq(beamState.cBeams(cBeam), 1, "cBeam should be added"); + assertEq(beamState.rateLimits(address(mockRateLimits)), 1, "rateLimits should be added"); + assertEq(beamState.controllers(address(mockController)), 1, "controller should be added"); + assertEq(beamState.getHop(address(mockRateLimits)), 1 hours, "hop should be set"); + assertEq(beamState.getMaxChange(address(mockRateLimits)), 2 ether, "maxChange should be set"); + { + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(rateLimitKey, address(mockRateLimits)); + assertEq(limits.maxAmount, 1_000 ether, "init rate limit maxAmount should be set"); + assertEq(limits.slope, 100 ether, "init rate limit slope should be set"); + } + assertTrue(beamState.isControllerActionEnabled(keccak256(controllerAction), address(mockController)), "controller action should be enabled"); + + // ======================================== + // Phase 2: Associate cBeam (Role 2 - direct) + // ======================================== + + vm.startPrank(coreCouncil); + beamState.setCBeamForRateLimits(address(mockRateLimits), cBeam); + beamState.setCBeamForController(address(mockController), cBeam); + vm.stopPrank(); + + assertEq(beamState.rateLimitsCBeams(address(mockRateLimits), cBeam), 1, "cBeam should be associated with rateLimits"); + assertEq(beamState.controllersCBeams(address(mockController), cBeam), 1, "cBeam should be associated with controller"); + + // ======================================== + // Phase 3: cBeam operates via Configurator + // ======================================== + + // 3a. cBeam sets rate limit (within default limits - no hop needed) + vm.prank(cBeam); + configurator.setRateLimit(address(mockRateLimits), rateLimitKey, 500 ether, 50 ether); + { + RateLimitsLike.RateLimitData memory data = mockRateLimits.getRateLimitData(rateLimitKey); + assertEq(data.maxAmount, 500 ether, "rate limit maxAmount should be set by cBeam"); + assertEq(data.slope, 50 ether, "rate limit slope should be set by cBeam"); + } + + // 3b. cBeam calls controller action + vm.prank(cBeam); + configurator.callControllerAction(address(mockController), controllerAction); + assertEq(mockController.lastCallData(), controllerAction, "controller should have received the action"); + + // ======================================== + // Phase 4: Verify restrictions + // ======================================== + + // 4a. Unauthorized cBeam cannot set rate limit + address unauthorizedCBeam = address(0x999); + vm.prank(unauthorizedCBeam); + vm.expectRevert("Configurator/not-authorized-ratelimits-cBeam"); + configurator.setRateLimit(address(mockRateLimits), rateLimitKey, 100 ether, 10 ether); + + // 4b. Unauthorized cBeam cannot call controller action + vm.prank(unauthorizedCBeam); + vm.expectRevert("Configurator/not-authorized-controller-cBeam"); + configurator.callControllerAction(address(mockController), controllerAction); + + // 4c. cBeam cannot call non-whitelisted action + bytes memory nonWhitelistedAction = abi.encodeWithSelector(MockTarget.controllerFunction.selector, 999); + vm.prank(cBeam); + vm.expectRevert("Configurator/not-valid-data"); + configurator.callControllerAction(address(mockController), nonWhitelistedAction); + + // 4d. Operations blocked when stopped + vm.prank(coreCouncil); + beamState.stop(); + + vm.prank(cBeam); + vm.expectRevert("Configurator/stopped"); + configurator.setRateLimit(address(mockRateLimits), rateLimitKey, 400 ether, 40 ether); + + vm.prank(cBeam); + vm.expectRevert("Configurator/stopped"); + configurator.callControllerAction(address(mockController), controllerAction); + + // 4e. Operations resume after restart (via timelock) + vm.prank(coreCouncil); + bytes32 startOp = timelockWrapper.start(bytes32(0), keccak256("restart"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + { + Timelock.Operation memory op = timelock.getOperation(startOp); + timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); + } + + assertFalse(beamState.stopped(), "system should be running"); + + // cBeam can operate again + vm.prank(cBeam); + configurator.setRateLimit(address(mockRateLimits), rateLimitKey, 400 ether, 40 ether); + { + RateLimitsLike.RateLimitData memory data = mockRateLimits.getRateLimitData(rateLimitKey); + assertEq(data.maxAmount, 400 ether, "rate limit should be updated after restart"); + } + } +} From 083e9772c5b87432576aff4094e7af81def9701c Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 16 Jan 2026 17:54:55 -0300 Subject: [PATCH 053/150] Fix typo in test --- test/Integration.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Integration.t.sol b/test/Integration.t.sol index edd6791..a76e772 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -717,7 +717,7 @@ contract IntegrationTest is Test { // 1f. Schedule addInitRateLimits (via wrapper with RateLimitConfig) vm.prank(coreCouncil); opIds[5] = timelockWrapper.addInitRateLimits( - RateLimitConfig({key: rateLimitKey, rateLimits_: address(mockRateLimits), maxAmount: 1__000 ether, slope: 100 ether}), + RateLimitConfig({key: rateLimitKey, rateLimits_: address(mockRateLimits), maxAmount: 1_000 ether, slope: 100 ether}), opIds[4], keccak256("addInitRateLimits"), MIN_DELAY ); From a7108d16583de8221ffce7c310538556f8eca65b Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 19 Jan 2026 00:01:14 +0700 Subject: [PATCH 054/150] TimelockWrapper integration testing - wip --- test/timelock/TimelockWrapper.t.sol | 687 ++++++++++++++++++++++++++++ 1 file changed, 687 insertions(+) create mode 100644 test/timelock/TimelockWrapper.t.sol diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol new file mode 100644 index 0000000..20336b0 --- /dev/null +++ b/test/timelock/TimelockWrapper.t.sol @@ -0,0 +1,687 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.21; + +import "dss-test/DssTest.sol"; +import { TimelockWrapper, RateLimitConfig } from "src/timelock/TimelockWrapper.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; +import { BeamState } from "src/BeamState.sol"; +import { Configurator } from "src/Configurator.sol"; +import { PASDeploy } from "deploy/PASDeploy.sol"; +import { PASInit } from "deploy/PASInit.sol"; +import { PASInstance } from "deploy/PASInstance.sol"; + +interface ControllerLike { + function hasRole(bytes32 role, address account) external view returns (bool); + function grantRole(bytes32 role, address account) external; + function revokeRole(bytes32 role, address account) external; + function RELAYER() external view returns (bytes32); + function mintRecipients(uint32) external view returns (bytes32); + function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; + function rateLimits() external view returns (address); + function proxy() external view returns (address); + // Shared getters + function layerZeroRecipients(uint32) external view returns (bytes32); + function maxSlippages(address) external view returns (uint256); + function maxExchangeRates(address) external view returns (uint256); + // Spark-specific getters + // otcs returns: (buffer, rechargeRate18, sent18, sentTimestamp, claimed18) + function otcs(address) external view returns (address, uint256, uint256, uint256, uint256); + function otcWhitelistedAssets(address, address) external view returns (bool); + // Grove-specific getters + function centrifugeRecipients(uint16) external view returns (bytes32); + // uniswapV3PoolParams returns: (swapMaxTickDelta, addLiquidityLowerTick, addLiquidityUpperTick, twapSecondsAgo) + function uniswapV3PoolParams(address) external view returns (uint24, int24, int24, uint32); +} + +interface RateLimitsLike { + function grantRole(bytes32 role, address account) external; + function getRateLimitData(bytes32 key) external view returns (uint256, uint256, uint256, uint256); +} + +contract TimelockWrapperTest is DssTest { + // --- Events --- + event Kiss(address indexed usr); + event Diss(address indexed usr); + + // --- Mainnet Addresses --- + address constant SPARK_CONTROLLER = 0xE52d643B27601D4d2BAB2052f30cf936ed413cec; + address constant SPARK_ADMIN = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; + address constant GROVE_CONTROLLER = 0xfd9dEA9a8D5B955649579Af482DB7198A392A9F5; + address constant GROVE_ADMIN = 0x1369f7b2b38c76B6478c0f0E66D94923421891Ba; + + uint256 constant FORK_BLOCK = 24250000; + uint256 constant MIN_DELAY = 1 days; + + // --- Fetched from controllers --- + address public SPARK_ALM_PROXY; + address public SPARK_RATE_LIMITS; + address public GROVE_RATE_LIMITS; + + // --- PAS Instance --- + BeamState public beamState; + Configurator public configurator; + Timelock public timelock; + TimelockWrapper public wrapper; + + address public coreCouncil; + address public cBeam; + + function setUp() public { + vm.createSelectFork(vm.envString("ETH_RPC_URL"), FORK_BLOCK); + + // Fetch proxy and rate limits from controllers + SPARK_ALM_PROXY = ControllerLike(SPARK_CONTROLLER).proxy(); + SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); + GROVE_RATE_LIMITS = ControllerLike(GROVE_CONTROLLER).rateLimits(); + + coreCouncil = makeAddr("coreCouncil"); + cBeam = makeAddr("cBeam"); + + PASInstance memory pas = PASDeploy.deploy(address(this), address(this), MIN_DELAY); + PASInit.init(pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); + + beamState = BeamState(pas.beamState); + configurator = Configurator(pas.configurator); + timelock = Timelock(payable(pas.timelock)); + wrapper = TimelockWrapper(pas.timelockWrapper); + + // Grant configurator admin role on mainnet controllers and rate limiters + vm.startPrank(SPARK_ADMIN); + ControllerLike(SPARK_CONTROLLER).grantRole(bytes32(0), address(configurator)); + RateLimitsLike(SPARK_RATE_LIMITS).grantRole(bytes32(0), address(configurator)); + vm.stopPrank(); + + vm.startPrank(GROVE_ADMIN); + ControllerLike(GROVE_CONTROLLER).grantRole(bytes32(0), address(configurator)); + RateLimitsLike(GROVE_RATE_LIMITS).grantRole(bytes32(0), address(configurator)); + vm.stopPrank(); + + // Setup controllers, rate limiters, and cBeam in BeamState + vm.startPrank(coreCouncil); + bytes32 op1 = wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("spark-ctrl"), MIN_DELAY); + _execute(op1, abi.encodeWithSelector(BeamState.addController.selector, SPARK_CONTROLLER), bytes32(0), keccak256("spark-ctrl")); + bytes32 op2 = wrapper.addController(GROVE_CONTROLLER, bytes32(0), keccak256("grove-ctrl"), MIN_DELAY); + _execute(op2, abi.encodeWithSelector(BeamState.addController.selector, GROVE_CONTROLLER), bytes32(0), keccak256("grove-ctrl")); + bytes32 op3 = wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("spark-rl"), MIN_DELAY); + _execute(op3, abi.encodeWithSelector(BeamState.addRateLimits.selector, SPARK_RATE_LIMITS), bytes32(0), keccak256("spark-rl")); + bytes32 op4 = wrapper.addRateLimits(GROVE_RATE_LIMITS, bytes32(0), keccak256("grove-rl"), MIN_DELAY); + _execute(op4, abi.encodeWithSelector(BeamState.addRateLimits.selector, GROVE_RATE_LIMITS), bytes32(0), keccak256("grove-rl")); + bytes32 op5 = wrapper.addCBeam(cBeam, bytes32(0), keccak256("cbeam"), MIN_DELAY); + _execute(op5, abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), bytes32(0), keccak256("cbeam")); + + // Verify wrapper correctly configured BeamState + assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "Spark controller not added"); + assertEq(beamState.controllers(GROVE_CONTROLLER), 1, "Grove controller not added"); + assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1, "Spark rate limits not added"); + assertEq(beamState.rateLimits(GROVE_RATE_LIMITS), 1, "Grove rate limits not added"); + assertEq(beamState.cBeams(cBeam), 1, "cBeam not added"); + + // Link cBeam to controllers/rate limiters + beamState.setCBeamForController(SPARK_CONTROLLER, cBeam); + beamState.setCBeamForController(GROVE_CONTROLLER, cBeam); + beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); + beamState.setCBeamForRateLimits(GROVE_RATE_LIMITS, cBeam); + vm.stopPrank(); + } + + // ============================================================================ + // Helpers + // ============================================================================ + + function _execute(bytes32 opId, bytes memory payload, bytes32 predecessor, bytes32 salt) internal { + vm.warp(block.timestamp + MIN_DELAY); + address[] memory targets = new address[](1); + targets[0] = address(beamState); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + payloads[0] = payload; + timelock.executeBatch(targets, values, payloads, predecessor, salt); + } + + // ============================================================================ + // Authorization Tests + // ============================================================================ + + function testConstructor() public view { + assertEq(address(wrapper.timelock()), address(timelock), "Timelock set correctly"); + assertEq(address(wrapper.beamState()), address(beamState), "BeamState set correctly"); + assertEq(wrapper.wards(address(this)), 1, "Deployer is ward"); + } + + function testAuth() public { + checkAuth(address(wrapper), "TimelockWrapper"); + } + + function testKissDiss() public { + address usr = makeAddr("usr"); + + assertEq(wrapper.buds(usr), 0); + vm.expectEmit(true, false, false, true); + emit Kiss(usr); + wrapper.kiss(usr); + assertEq(wrapper.buds(usr), 1); + vm.expectEmit(true, false, false, true); + emit Diss(usr); + wrapper.diss(usr); + assertEq(wrapper.buds(usr), 0); + } + + function testAuthModifiers() public { + bytes4[] memory authedMethods = new bytes4[](4); + authedMethods[0] = wrapper.rely.selector; + authedMethods[1] = wrapper.deny.selector; + authedMethods[2] = wrapper.kiss.selector; + authedMethods[3] = wrapper.diss.selector; + + vm.startPrank(address(0xBEEF)); + checkModifier(address(wrapper), "TimelockWrapper/not-authorized", authedMethods); + vm.stopPrank(); + } + + function testTollModifiers() public { + bytes4[] memory tolledMethods = new bytes4[](23); + tolledMethods[0] = wrapper.start.selector; + tolledMethods[1] = wrapper.setHop.selector; + tolledMethods[2] = wrapper.setMaxChange.selector; + tolledMethods[3] = wrapper.addRateLimits.selector; + tolledMethods[4] = wrapper.addController.selector; + tolledMethods[5] = wrapper.addCBeam.selector; + tolledMethods[6] = wrapper.addInitRateLimits.selector; + tolledMethods[7] = wrapper.batchAddInitRateLimits.selector; + tolledMethods[8] = wrapper.grantRole.selector; + tolledMethods[9] = wrapper.revokeRole.selector; + tolledMethods[10] = wrapper.setMintRecipient.selector; + tolledMethods[11] = wrapper.setLayerZeroRecipient.selector; + tolledMethods[12] = wrapper.setMaxSlippage.selector; + tolledMethods[13] = wrapper.setOTCBuffer.selector; + tolledMethods[14] = wrapper.setOTCRechargeRate.selector; + tolledMethods[15] = wrapper.setOTCWhitelistedAsset.selector; + tolledMethods[16] = wrapper.setMaxExchangeRate.selector; + tolledMethods[17] = wrapper.setUniswapV4TickLimits.selector; + tolledMethods[18] = wrapper.setUniswapV3PoolMaxTickDelta.selector; + tolledMethods[19] = wrapper.setUniswapV3AddLiquidityLowerTickBound.selector; + tolledMethods[20] = wrapper.setUniswapV3AddLiquidityUpperTickBound.selector; + tolledMethods[21] = wrapper.setUniswapV3TwapSecondsAgo.selector; + tolledMethods[22] = wrapper.setCentrifugeRecipient.selector; + + vm.startPrank(address(0xBEEF)); + checkModifier(address(wrapper), "TimelockWrapper/not-whitelisted", tolledMethods); + vm.stopPrank(); + } + + // ============================================================================ + // BeamState Configuration Tests + // ============================================================================ + + function testStart() public { + vm.prank(coreCouncil); + beamState.stop(); + assertTrue(beamState.stopped()); + + vm.prank(coreCouncil); + bytes32 opId = wrapper.start(bytes32(0), keccak256("start"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.start.selector), bytes32(0), keccak256("start")); + + assertFalse(beamState.stopped()); + } + + function testSetHop() public { + vm.prank(coreCouncil); + bytes32 opId = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), keccak256("hop"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 3600), bytes32(0), keccak256("hop")); + + assertEq(beamState.getHop(SPARK_RATE_LIMITS), 3600); + } + + function testSetMaxChange() public { + vm.prank(coreCouncil); + bytes32 opId = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), keccak256("mc"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.setMaxChange.selector, GROVE_RATE_LIMITS, 2e18), bytes32(0), keccak256("mc")); + + assertEq(beamState.maxChange(GROVE_RATE_LIMITS), 2e18); + } + + function testAddRateLimits() public { + vm.prank(coreCouncil); + bytes32 opId = wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("rl"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addRateLimits.selector, SPARK_RATE_LIMITS), bytes32(0), keccak256("rl")); + + assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1); + } + + function testAddController() public { + vm.prank(coreCouncil); + bytes32 opId = wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("ctrl"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addController.selector, SPARK_CONTROLLER), bytes32(0), keccak256("ctrl")); + + assertEq(beamState.controllers(SPARK_CONTROLLER), 1); + } + + function testAddCBeam() public { + vm.prank(coreCouncil); + bytes32 opId = wrapper.addCBeam(SPARK_ALM_PROXY, bytes32(0), keccak256("cbeam"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addCBeam.selector, SPARK_ALM_PROXY), bytes32(0), keccak256("cbeam")); + + assertEq(beamState.cBeams(SPARK_ALM_PROXY), 1); + } + + function _checkAddInitRateLimits(address rateLimits, bytes32 key, bytes32 salt) internal { + RateLimitConfig memory config = RateLimitConfig({ + key: key, + rateLimits_: rateLimits, + maxAmount: 10_000_000e18, + slope: 1_000_000e18 + }); + + vm.prank(coreCouncil); + bytes32 opId = wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits_, config.maxAmount, config.slope), bytes32(0), salt); + + // Verify stored in BeamState + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(config.key, rateLimits); + assertEq(limits.maxAmount, config.maxAmount); + assertEq(limits.slope, config.slope); + + // Execute on real rate limiter via Configurator + vm.prank(cBeam); + configurator.setRateLimit(rateLimits, config.key, config.maxAmount, config.slope); + + // Verify set on real rate limiter + (uint256 setMax, uint256 setSlope,,) = RateLimitsLike(rateLimits).getRateLimitData(config.key); + assertEq(setMax, config.maxAmount); + assertEq(setSlope, config.slope); + } + + function testAddInitRateLimitsOnSpark() public { + _checkAddInitRateLimits(SPARK_RATE_LIMITS, keccak256("spark-deposit"), keccak256("initS")); + } + + function testAddInitRateLimitsOnGrove() public { + _checkAddInitRateLimits(GROVE_RATE_LIMITS, keccak256("grove-deposit"), keccak256("initG")); + } + + function _checkBatchAddInitRateLimits(address rateLimits, bytes32 key1, bytes32 key2, bytes32 salt) internal { + RateLimitConfig[] memory configs = new RateLimitConfig[](2); + configs[0] = RateLimitConfig(key1, rateLimits, 5_000_000e18, 500_000e18); + configs[1] = RateLimitConfig(key2, rateLimits, 3_000_000e18, 300_000e18); + + vm.prank(coreCouncil); + wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); + assertEq(timelock.getOperationCount(), 1); + + vm.warp(block.timestamp + MIN_DELAY); + address[] memory targets = new address[](2); + uint256[] memory values = new uint256[](2); + bytes[] memory payloads = new bytes[](2); + for (uint256 i = 0; i < 2; i++) { + targets[i] = address(beamState); + payloads[i] = abi.encodeWithSelector(BeamState.addInitRateLimits.selector, configs[i].key, configs[i].rateLimits_, configs[i].maxAmount, configs[i].slope); + } + timelock.executeBatch(targets, values, payloads, bytes32(0), salt); + + // Verify stored in BeamState + assertEq(beamState.getInitRateLimits(configs[0].key, rateLimits).maxAmount, configs[0].maxAmount); + assertEq(beamState.getInitRateLimits(configs[1].key, rateLimits).maxAmount, configs[1].maxAmount); + + // Execute on real rate limiter via Configurator + vm.startPrank(cBeam); + configurator.setRateLimit(rateLimits, configs[0].key, configs[0].maxAmount, configs[0].slope); + configurator.setRateLimit(rateLimits, configs[1].key, configs[1].maxAmount, configs[1].slope); + vm.stopPrank(); + + // Verify set on real rate limiter + (uint256 max0, uint256 slope0,,) = RateLimitsLike(rateLimits).getRateLimitData(configs[0].key); + assertEq(max0, configs[0].maxAmount); + assertEq(slope0, configs[0].slope); + + (uint256 max1, uint256 slope1,,) = RateLimitsLike(rateLimits).getRateLimitData(configs[1].key); + assertEq(max1, configs[1].maxAmount); + assertEq(slope1, configs[1].slope); + } + + function testBatchAddInitRateLimitsOnSpark() public { + _checkBatchAddInitRateLimits(SPARK_RATE_LIMITS, keccak256("spark-batch-1"), keccak256("spark-batch-2"), keccak256("batchS")); + } + + function testBatchAddInitRateLimitsOnGrove() public { + _checkBatchAddInitRateLimits(GROVE_RATE_LIMITS, keccak256("grove-batch-1"), keccak256("grove-batch-2"), keccak256("batchG")); + } + + // ============================================================================ + // Controller Action Tests + // ============================================================================ + + // --- grantRole (Spark & Grove) --- + + function _checkGrantRole(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + bytes32 role = ctrl.RELAYER(); + address account = makeAddr("relayer"); + + assertFalse(ctrl.hasRole(role, account)); + + bytes memory data = abi.encodeWithSelector(ControllerLike.grantRole.selector, role, account); + vm.prank(coreCouncil); + bytes32 opId = wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertTrue(ctrl.hasRole(role, account)); + } + + function testGrantRoleOnSparkController() public { + _checkGrantRole(SPARK_CONTROLLER, keccak256("grantS")); + } + + function testGrantRoleOnGroveController() public { + _checkGrantRole(GROVE_CONTROLLER, keccak256("grantG")); + } + + // --- revokeRole (Spark & Grove) --- + + function _checkRevokeRole(address controller, address admin, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + bytes32 role = ctrl.RELAYER(); + address account = makeAddr("toRevoke"); + + vm.prank(admin); + ctrl.grantRole(role, account); + + bytes memory data = abi.encodeWithSelector(ControllerLike.revokeRole.selector, role, account); + vm.prank(coreCouncil); + bytes32 opId = wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertFalse(ctrl.hasRole(role, account)); + } + + function testRevokeRoleOnSparkController() public { + _checkRevokeRole(SPARK_CONTROLLER, SPARK_ADMIN, keccak256("revokeS")); + } + + function testRevokeRoleOnGroveController() public { + _checkRevokeRole(GROVE_CONTROLLER, GROVE_ADMIN, keccak256("revokeG")); + } + + // --- setMintRecipient (Spark & Grove) --- + + function _checkSetMintRecipient(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + uint32 domain = 6; + bytes32 recipient = bytes32(uint256(uint160(makeAddr("recipient")))); + + bytes memory data = abi.encodeWithSelector(ControllerLike.setMintRecipient.selector, domain, recipient); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertEq(ctrl.mintRecipients(domain), recipient); + } + + function testSetMintRecipientOnSparkController() public { + _checkSetMintRecipient(SPARK_CONTROLLER, keccak256("mintS")); + } + + function testSetMintRecipientOnGroveController() public { + _checkSetMintRecipient(GROVE_CONTROLLER, keccak256("mintG")); + } + + // --- setLayerZeroRecipient (Spark & Grove) --- + + function _checkSetLayerZeroRecipient(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + uint32 endpointId = 111; + bytes32 recipient = bytes32(uint256(uint160(makeAddr("lzRecipient")))); + + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setLayerZeroRecipient(uint32,bytes32)")), endpointId, recipient); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertEq(ctrl.layerZeroRecipients(endpointId), recipient); + } + + function testSetLayerZeroRecipientOnSparkController() public { + _checkSetLayerZeroRecipient(SPARK_CONTROLLER, keccak256("lzS")); + } + + function testSetLayerZeroRecipientOnGroveController() public { + _checkSetLayerZeroRecipient(GROVE_CONTROLLER, keccak256("lzG")); + } + + // --- setMaxSlippage (Spark & Grove) --- + + function _checkSetMaxSlippage(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + uint256 slippage = 100; + + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setMaxSlippage(address,uint256)")), pool, slippage); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertEq(ctrl.maxSlippages(pool), slippage); + } + + function testSetMaxSlippageOnSparkController() public { + _checkSetMaxSlippage(SPARK_CONTROLLER, keccak256("slipS")); + } + + function testSetMaxSlippageOnGroveController() public { + _checkSetMaxSlippage(GROVE_CONTROLLER, keccak256("slipG")); + } + + // --- setOTCBuffer (Spark Only) --- + + function testSetOTCBufferOnSparkController() public { + ControllerLike spark = ControllerLike(SPARK_CONTROLLER); + address exchange = makeAddr("exchange"); + address buffer = makeAddr("buffer"); + + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setOTCBuffer(address,address)")), exchange, buffer); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("buf"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("buf")); + + vm.prank(cBeam); + configurator.callControllerAction(SPARK_CONTROLLER, data); + (address setBuffer,,,,) = spark.otcs(exchange); + assertEq(setBuffer, buffer); + } + + // --- setOTCRechargeRate (Spark Only) --- + + function testSetOTCRechargeRateOnSparkController() public { + ControllerLike spark = ControllerLike(SPARK_CONTROLLER); + address exchange = makeAddr("exchange"); + + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setOTCRechargeRate(address,uint256)")), exchange, 1e18); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setOTCRechargeRate(exchange, 1e18, SPARK_CONTROLLER, bytes32(0), keccak256("rate"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("rate")); + + vm.prank(cBeam); + configurator.callControllerAction(SPARK_CONTROLLER, data); + (, uint256 rechargeRate,,,) = spark.otcs(exchange); + assertEq(rechargeRate, 1e18); + } + + // --- setOTCWhitelistedAsset (Spark Only) --- + + function testSetOTCWhitelistedAssetOnSparkController() public { + ControllerLike spark = ControllerLike(SPARK_CONTROLLER); + address exchange = makeAddr("exchange"); + address buffer = makeAddr("buffer"); + address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + + // First set the buffer (required before whitelisting assets) + bytes memory bufferData = abi.encodeWithSelector(bytes4(keccak256("setOTCBuffer(address,address)")), exchange, buffer); + vm.prank(coreCouncil); + bytes32 bufOpId = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("wlbuf"), MIN_DELAY); + _execute(bufOpId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, bufferData, SPARK_CONTROLLER), bytes32(0), keccak256("wlbuf")); + vm.prank(cBeam); + configurator.callControllerAction(SPARK_CONTROLLER, bufferData); + + // Now whitelist the asset + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setOTCWhitelistedAsset(address,address,bool)")), exchange, usdc, true); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setOTCWhitelistedAsset(exchange, usdc, true, SPARK_CONTROLLER, bytes32(0), keccak256("wl"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("wl")); + + vm.prank(cBeam); + configurator.callControllerAction(SPARK_CONTROLLER, data); + assertTrue(spark.otcWhitelistedAssets(exchange, usdc)); + } + + // --- setMaxExchangeRate (Spark & Grove) --- + + function _checkSetMaxExchangeRate(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + address sDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA; + uint256 shares = 1e18; + uint256 maxExpectedAssets = 1.1e18; + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setMaxExchangeRate(address,uint256,uint256)")), sDAI, shares, maxExpectedAssets); + + vm.prank(coreCouncil); + bytes32 opId = wrapper.setMaxExchangeRate(sDAI, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + // maxExchangeRates stores value in 1e36 precision: maxExpectedAssets * 1e18 / shares * 1e18 = 1.1e36 + assertEq(ctrl.maxExchangeRates(sDAI), 1.1e36); + } + + function testSetMaxExchangeRateOnSparkController() public { + _checkSetMaxExchangeRate(SPARK_CONTROLLER, keccak256("exrS")); + } + + function testSetMaxExchangeRateOnGroveController() public { + _checkSetMaxExchangeRate(GROVE_CONTROLLER, keccak256("exrG")); + } + + // --- setUniswapV3PoolMaxTickDelta (Grove Only) --- + + function testSetUniswapV3PoolMaxTickDeltaOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3PoolMaxTickDelta(address,uint24)")), pool, uint24(1000)); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), keccak256("tick"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("tick")); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + (uint24 swapMaxTickDelta,,,) = grove.uniswapV3PoolParams(pool); + assertEq(swapMaxTickDelta, 1000); + } + + // --- setUniswapV3AddLiquidityLowerTickBound (Grove Only) --- + + function testSetUniswapV3AddLiquidityLowerTickBoundOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3AddLiquidityLowerTickBound(address,int24)")), pool, int24(-887220)); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), keccak256("lower"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("lower")); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + (, int24 lowerTick,,) = grove.uniswapV3PoolParams(pool); + assertEq(lowerTick, -887220); + } + + // --- setUniswapV3AddLiquidityUpperTickBound (Grove Only) --- + + function testSetUniswapV3AddLiquidityUpperTickBoundOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3AddLiquidityUpperTickBound(address,int24)")), pool, int24(887220)); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), keccak256("upper"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("upper")); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + (,, int24 upperTick,) = grove.uniswapV3PoolParams(pool); + assertEq(upperTick, 887220); + } + + // --- setUniswapV3TwapSecondsAgo (Grove Only) --- + + function testSetUniswapV3TwapSecondsAgoOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3TwapSecondsAgo(address,uint32)")), pool, uint32(1800)); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), keccak256("twap"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("twap")); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + (,,, uint32 twapSecondsAgo) = grove.uniswapV3PoolParams(pool); + assertEq(twapSecondsAgo, 1800); + } + + // --- setCentrifugeRecipient (Grove Only) --- + + function testSetCentrifugeRecipientOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + uint16 centrifugeId = 1; + bytes32 recipient = bytes32(uint256(uint160(makeAddr("centrifuge")))); + + bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setCentrifugeRecipient(uint16,bytes32)")), centrifugeId, recipient); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), keccak256("cent"), MIN_DELAY); + _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("cent")); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + assertEq(grove.centrifugeRecipients(centrifugeId), recipient); + } + + // ============================================================================ + // Schedule-Only Test (UniswapV4 - not on current mainnet controllers) + // ============================================================================ + + function testSetUniswapV4TickLimitsSchedulesCorrectly() public { + // UniswapV4 functions added in v1.9.0, but mainnet controller is v1.8.0 + // See: https://github.com/sparkdotfi/spark-alm-controller/releases/tag/v1.9.0 + bytes32 poolId = keccak256("pool"); + vm.prank(coreCouncil); + bytes32 opId = wrapper.setUniswapV4TickLimits(poolId, -887220, 887220, 200, SPARK_CONTROLLER, bytes32(0), keccak256("v4"), MIN_DELAY); + + assertTrue(opId != bytes32(0)); + assertTrue(timelock.isOperationPending(opId)); + } +} From 27217b65baadef6180eb884c1b995794a6a3e27a Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:53:14 +0700 Subject: [PATCH 055/150] Align wrapper and tests to actual releases currently use (v1.8.0) --- src/timelock/TimelockWrapper.sol | 35 +++++------------------------ test/timelock/TimelockWrapper.t.sol | 28 +++++------------------ 2 files changed, 12 insertions(+), 51 deletions(-) diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index 5f9fef3..d9cad39 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -47,18 +47,18 @@ interface BeamStateLike { } interface ControllerLike { - // Spark functions + // Shared functions (Spark v1.8.0 & Grove v1.8.0) function grantRole(bytes32 role, address account) external; function revokeRole(bytes32 role, address account) external; function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; function setLayerZeroRecipient(uint32 destinationEndpointId, bytes32 layerZeroRecipient) external; function setMaxSlippage(address pool, uint256 maxSlippage) external; + function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; + // Spark-only functions (v1.8.0) function setOTCBuffer(address exchange, address otcBuffer) external; function setOTCRechargeRate(address exchange, uint256 rechargeRate18) external; function setOTCWhitelistedAsset(address exchange, address asset, bool isWhitelisted) external; - function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; - function setUniswapV4TickLimits(bytes32 poolId, int24 tickLowerMin, int24 tickUpperMax, uint24 maxTickSpacing) external; - // Grove-only functions + // Grove-only functions (v1.8.0) function setUniswapV3PoolMaxTickDelta(address pool, uint24 maxTickDelta) external; function setUniswapV3AddLiquidityLowerTickBound(address pool, int24 lowerTickBound) external; function setUniswapV3AddLiquidityUpperTickBound(address pool, int24 upperTickBound) external; @@ -73,8 +73,8 @@ struct RateLimitConfig { uint256 slope; } -// Spark Mainnet controller aligned to: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol -// Grove Mainnet controller aligned to: https://github.com/grove-labs/grove-alm-controller/blob/548c96fa22bcb13afd25cb592ec8cb4bb98c2d86/src/MainnetController.sol +// Spark Mainnet controller aligned to v1.8.0: https://github.com/sparkdotfi/spark-alm-controller/blob/7be959378fe48117f7a06796f94e240345428982/src/MainnetController.sol +// Grove Mainnet controller aligned to v1.8.0: https://github.com/grove-labs/grove-alm-controller/blob/2c6e3d4297d5f244894d05f3dbbe47bcada34712/src/MainnetController.sol // Notes: // - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). @@ -411,29 +411,6 @@ contract TimelockWrapper { emit ProposalSubmitted(operationId, "setMaxExchangeRate"); } - function setUniswapV4TickLimits( - bytes32 poolId, - int24 tickLowerMin, - int24 tickUpperMax, - uint24 maxTickSpacing, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV4TickLimits.selector, - poolId, - tickLowerMin, - tickUpperMax, - maxTickSpacing - ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV4TickLimits"); - } - // Grove-only functions function setUniswapV3PoolMaxTickDelta( diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol index 20336b0..d2e06c5 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapper.t.sol @@ -194,7 +194,7 @@ contract TimelockWrapperTest is DssTest { } function testTollModifiers() public { - bytes4[] memory tolledMethods = new bytes4[](23); + bytes4[] memory tolledMethods = new bytes4[](22); tolledMethods[0] = wrapper.start.selector; tolledMethods[1] = wrapper.setHop.selector; tolledMethods[2] = wrapper.setMaxChange.selector; @@ -212,12 +212,11 @@ contract TimelockWrapperTest is DssTest { tolledMethods[14] = wrapper.setOTCRechargeRate.selector; tolledMethods[15] = wrapper.setOTCWhitelistedAsset.selector; tolledMethods[16] = wrapper.setMaxExchangeRate.selector; - tolledMethods[17] = wrapper.setUniswapV4TickLimits.selector; - tolledMethods[18] = wrapper.setUniswapV3PoolMaxTickDelta.selector; - tolledMethods[19] = wrapper.setUniswapV3AddLiquidityLowerTickBound.selector; - tolledMethods[20] = wrapper.setUniswapV3AddLiquidityUpperTickBound.selector; - tolledMethods[21] = wrapper.setUniswapV3TwapSecondsAgo.selector; - tolledMethods[22] = wrapper.setCentrifugeRecipient.selector; + tolledMethods[17] = wrapper.setUniswapV3PoolMaxTickDelta.selector; + tolledMethods[18] = wrapper.setUniswapV3AddLiquidityLowerTickBound.selector; + tolledMethods[19] = wrapper.setUniswapV3AddLiquidityUpperTickBound.selector; + tolledMethods[20] = wrapper.setUniswapV3TwapSecondsAgo.selector; + tolledMethods[21] = wrapper.setCentrifugeRecipient.selector; vm.startPrank(address(0xBEEF)); checkModifier(address(wrapper), "TimelockWrapper/not-whitelisted", tolledMethods); @@ -669,19 +668,4 @@ contract TimelockWrapperTest is DssTest { configurator.callControllerAction(GROVE_CONTROLLER, data); assertEq(grove.centrifugeRecipients(centrifugeId), recipient); } - - // ============================================================================ - // Schedule-Only Test (UniswapV4 - not on current mainnet controllers) - // ============================================================================ - - function testSetUniswapV4TickLimitsSchedulesCorrectly() public { - // UniswapV4 functions added in v1.9.0, but mainnet controller is v1.8.0 - // See: https://github.com/sparkdotfi/spark-alm-controller/releases/tag/v1.9.0 - bytes32 poolId = keccak256("pool"); - vm.prank(coreCouncil); - bytes32 opId = wrapper.setUniswapV4TickLimits(poolId, -887220, 887220, 200, SPARK_CONTROLLER, bytes32(0), keccak256("v4"), MIN_DELAY); - - assertTrue(opId != bytes32(0)); - assertTrue(timelock.isOperationPending(opId)); - } } From 76618f858ff4d154bcf60e6147bd5343e2b3bb4a Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:55:56 +0700 Subject: [PATCH 056/150] Minor changes --- src/timelock/TimelockWrapper.sol | 8 ++++---- test/Integration.t.sol | 2 +- test/timelock/TimelockWrapper.t.sol | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index d9cad39..41e5b42 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -68,7 +68,7 @@ interface ControllerLike { struct RateLimitConfig { bytes32 key; - address rateLimits_; + address rateLimits; uint256 maxAmount; uint256 slope; } @@ -79,7 +79,7 @@ struct RateLimitConfig { // Notes: // - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). // - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. -// - Only the addInitRateLimits calls can be batched in this wrapper, other calls can use the predecessor mechanism to enforce ordering. +// - The actual downstream changes only take effect when govops use the BEAMState configurations, so atomicity in configurations can not be assumed. // - As part of a controller onboarding it should be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. contract TimelockWrapper { // --- Auth --- @@ -193,7 +193,7 @@ contract TimelockWrapper { bytes memory payload = abi.encodeWithSelector( BeamStateLike.addInitRateLimits.selector, config.key, - config.rateLimits_, + config.rateLimits, config.maxAmount, config.slope ); @@ -219,7 +219,7 @@ contract TimelockWrapper { payloads[i] = abi.encodeWithSelector( BeamStateLike.addInitRateLimits.selector, configs[i].key, - configs[i].rateLimits_, + configs[i].rateLimits, configs[i].maxAmount, configs[i].slope ); diff --git a/test/Integration.t.sol b/test/Integration.t.sol index a76e772..91994e5 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -717,7 +717,7 @@ contract IntegrationTest is Test { // 1f. Schedule addInitRateLimits (via wrapper with RateLimitConfig) vm.prank(coreCouncil); opIds[5] = timelockWrapper.addInitRateLimits( - RateLimitConfig({key: rateLimitKey, rateLimits_: address(mockRateLimits), maxAmount: 1_000 ether, slope: 100 ether}), + RateLimitConfig({key: rateLimitKey, rateLimits: address(mockRateLimits), maxAmount: 1_000 ether, slope: 100 ether}), opIds[4], keccak256("addInitRateLimits"), MIN_DELAY ); diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol index d2e06c5..a9fe6ad 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapper.t.sol @@ -282,14 +282,14 @@ contract TimelockWrapperTest is DssTest { function _checkAddInitRateLimits(address rateLimits, bytes32 key, bytes32 salt) internal { RateLimitConfig memory config = RateLimitConfig({ key: key, - rateLimits_: rateLimits, + rateLimits: rateLimits, maxAmount: 10_000_000e18, slope: 1_000_000e18 }); vm.prank(coreCouncil); bytes32 opId = wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits_, config.maxAmount, config.slope), bytes32(0), salt); + _execute(opId, abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits, config.maxAmount, config.slope), bytes32(0), salt); // Verify stored in BeamState BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(config.key, rateLimits); @@ -329,7 +329,7 @@ contract TimelockWrapperTest is DssTest { bytes[] memory payloads = new bytes[](2); for (uint256 i = 0; i < 2; i++) { targets[i] = address(beamState); - payloads[i] = abi.encodeWithSelector(BeamState.addInitRateLimits.selector, configs[i].key, configs[i].rateLimits_, configs[i].maxAmount, configs[i].slope); + payloads[i] = abi.encodeWithSelector(BeamState.addInitRateLimits.selector, configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope); } timelock.executeBatch(targets, values, payloads, bytes32(0), salt); From 753f8d607115eac6440e7296682370e0753b78dd Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 19 Jan 2026 14:51:54 +0700 Subject: [PATCH 057/150] Minor changes to wrapper test --- test/timelock/TimelockWrapper.t.sol | 152 +++++++++++++++------------- 1 file changed, 80 insertions(+), 72 deletions(-) diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol index a9fe6ad..fecdd52 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapper.t.sol @@ -33,7 +33,6 @@ interface ControllerLike { function mintRecipients(uint32) external view returns (bytes32); function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; function rateLimits() external view returns (address); - function proxy() external view returns (address); // Shared getters function layerZeroRecipients(uint32) external view returns (bytes32); function maxSlippages(address) external view returns (uint256); @@ -59,16 +58,20 @@ contract TimelockWrapperTest is DssTest { event Diss(address indexed usr); // --- Mainnet Addresses --- + // https://github.com/sparkdotfi/spark-address-registry/blob/0a06d13c3e9d36428a6f916ac25528d82154517b/src/Ethereum.sol address constant SPARK_CONTROLLER = 0xE52d643B27601D4d2BAB2052f30cf936ed413cec; - address constant SPARK_ADMIN = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; + address constant SPARK_PROXY = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; + + // https://github.com/grove-labs/grove-address-registry/blob/dd625925ab44e89eadce0cb4258e32aae2dfa73e/src/Ethereum.sol address constant GROVE_CONTROLLER = 0xfd9dEA9a8D5B955649579Af482DB7198A392A9F5; - address constant GROVE_ADMIN = 0x1369f7b2b38c76B6478c0f0E66D94923421891Ba; + address constant GROVE_PROXY = 0x1369f7b2b38c76B6478c0f0E66D94923421891Ba; uint256 constant FORK_BLOCK = 24250000; uint256 constant MIN_DELAY = 1 days; + bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); + // --- Fetched from controllers --- - address public SPARK_ALM_PROXY; address public SPARK_RATE_LIMITS; address public GROVE_RATE_LIMITS; @@ -84,8 +87,7 @@ contract TimelockWrapperTest is DssTest { function setUp() public { vm.createSelectFork(vm.envString("ETH_RPC_URL"), FORK_BLOCK); - // Fetch proxy and rate limits from controllers - SPARK_ALM_PROXY = ControllerLike(SPARK_CONTROLLER).proxy(); + // Fetch rate limits from controllers SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); GROVE_RATE_LIMITS = ControllerLike(GROVE_CONTROLLER).rateLimits(); @@ -101,28 +103,28 @@ contract TimelockWrapperTest is DssTest { wrapper = TimelockWrapper(pas.timelockWrapper); // Grant configurator admin role on mainnet controllers and rate limiters - vm.startPrank(SPARK_ADMIN); - ControllerLike(SPARK_CONTROLLER).grantRole(bytes32(0), address(configurator)); - RateLimitsLike(SPARK_RATE_LIMITS).grantRole(bytes32(0), address(configurator)); + vm.startPrank(SPARK_PROXY); + ControllerLike(SPARK_CONTROLLER).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); + RateLimitsLike(SPARK_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); vm.stopPrank(); - vm.startPrank(GROVE_ADMIN); - ControllerLike(GROVE_CONTROLLER).grantRole(bytes32(0), address(configurator)); - RateLimitsLike(GROVE_RATE_LIMITS).grantRole(bytes32(0), address(configurator)); + vm.startPrank(GROVE_PROXY); + ControllerLike(GROVE_CONTROLLER).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); + RateLimitsLike(GROVE_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); vm.stopPrank(); // Setup controllers, rate limiters, and cBeam in BeamState vm.startPrank(coreCouncil); - bytes32 op1 = wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("spark-ctrl"), MIN_DELAY); - _execute(op1, abi.encodeWithSelector(BeamState.addController.selector, SPARK_CONTROLLER), bytes32(0), keccak256("spark-ctrl")); - bytes32 op2 = wrapper.addController(GROVE_CONTROLLER, bytes32(0), keccak256("grove-ctrl"), MIN_DELAY); - _execute(op2, abi.encodeWithSelector(BeamState.addController.selector, GROVE_CONTROLLER), bytes32(0), keccak256("grove-ctrl")); - bytes32 op3 = wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("spark-rl"), MIN_DELAY); - _execute(op3, abi.encodeWithSelector(BeamState.addRateLimits.selector, SPARK_RATE_LIMITS), bytes32(0), keccak256("spark-rl")); - bytes32 op4 = wrapper.addRateLimits(GROVE_RATE_LIMITS, bytes32(0), keccak256("grove-rl"), MIN_DELAY); - _execute(op4, abi.encodeWithSelector(BeamState.addRateLimits.selector, GROVE_RATE_LIMITS), bytes32(0), keccak256("grove-rl")); - bytes32 op5 = wrapper.addCBeam(cBeam, bytes32(0), keccak256("cbeam"), MIN_DELAY); - _execute(op5, abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), bytes32(0), keccak256("cbeam")); + wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("spark-ctrl"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addController.selector, SPARK_CONTROLLER), bytes32(0), keccak256("spark-ctrl")); + wrapper.addController(GROVE_CONTROLLER, bytes32(0), keccak256("grove-ctrl"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addController.selector, GROVE_CONTROLLER), bytes32(0), keccak256("grove-ctrl")); + wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("spark-rl"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addRateLimits.selector, SPARK_RATE_LIMITS), bytes32(0), keccak256("spark-rl")); + wrapper.addRateLimits(GROVE_RATE_LIMITS, bytes32(0), keccak256("grove-rl"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addRateLimits.selector, GROVE_RATE_LIMITS), bytes32(0), keccak256("grove-rl")); + wrapper.addCBeam(cBeam, bytes32(0), keccak256("cbeam"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), bytes32(0), keccak256("cbeam")); // Verify wrapper correctly configured BeamState assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "Spark controller not added"); @@ -143,7 +145,7 @@ contract TimelockWrapperTest is DssTest { // Helpers // ============================================================================ - function _execute(bytes32 opId, bytes memory payload, bytes32 predecessor, bytes32 salt) internal { + function _execute(bytes memory payload, bytes32 predecessor, bytes32 salt) internal { vm.warp(block.timestamp + MIN_DELAY); address[] memory targets = new address[](1); targets[0] = address(beamState); @@ -233,50 +235,56 @@ contract TimelockWrapperTest is DssTest { assertTrue(beamState.stopped()); vm.prank(coreCouncil); - bytes32 opId = wrapper.start(bytes32(0), keccak256("start"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.start.selector), bytes32(0), keccak256("start")); + wrapper.start(bytes32(0), keccak256("start"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.start.selector), bytes32(0), keccak256("start")); assertFalse(beamState.stopped()); } function testSetHop() public { vm.prank(coreCouncil); - bytes32 opId = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), keccak256("hop"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 3600), bytes32(0), keccak256("hop")); + wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), keccak256("hop"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 3600), bytes32(0), keccak256("hop")); assertEq(beamState.getHop(SPARK_RATE_LIMITS), 3600); } function testSetMaxChange() public { vm.prank(coreCouncil); - bytes32 opId = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), keccak256("mc"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.setMaxChange.selector, GROVE_RATE_LIMITS, 2e18), bytes32(0), keccak256("mc")); + wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), keccak256("mc"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.setMaxChange.selector, GROVE_RATE_LIMITS, 2e18), bytes32(0), keccak256("mc")); assertEq(beamState.maxChange(GROVE_RATE_LIMITS), 2e18); } function testAddRateLimits() public { + address rateLimits = makeAddr("rateLimits"); + vm.prank(coreCouncil); - bytes32 opId = wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("rl"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addRateLimits.selector, SPARK_RATE_LIMITS), bytes32(0), keccak256("rl")); + wrapper.addRateLimits(rateLimits, bytes32(0), keccak256("rl"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addRateLimits.selector, rateLimits), bytes32(0), keccak256("rl")); - assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1); + assertEq(beamState.rateLimits(rateLimits), 1); } function testAddController() public { + address controller = makeAddr("controller"); + vm.prank(coreCouncil); - bytes32 opId = wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("ctrl"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addController.selector, SPARK_CONTROLLER), bytes32(0), keccak256("ctrl")); + wrapper.addController(controller, bytes32(0), keccak256("ctrl"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addController.selector, controller), bytes32(0), keccak256("ctrl")); - assertEq(beamState.controllers(SPARK_CONTROLLER), 1); + assertEq(beamState.controllers(controller), 1); } function testAddCBeam() public { + address beam = makeAddr("beam"); + vm.prank(coreCouncil); - bytes32 opId = wrapper.addCBeam(SPARK_ALM_PROXY, bytes32(0), keccak256("cbeam"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addCBeam.selector, SPARK_ALM_PROXY), bytes32(0), keccak256("cbeam")); + wrapper.addCBeam(beam, bytes32(0), keccak256("cbeam"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addCBeam.selector, beam), bytes32(0), keccak256("cbeam")); - assertEq(beamState.cBeams(SPARK_ALM_PROXY), 1); + assertEq(beamState.cBeams(beam), 1); } function _checkAddInitRateLimits(address rateLimits, bytes32 key, bytes32 salt) internal { @@ -288,8 +296,8 @@ contract TimelockWrapperTest is DssTest { }); vm.prank(coreCouncil); - bytes32 opId = wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits, config.maxAmount, config.slope), bytes32(0), salt); + wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits, config.maxAmount, config.slope), bytes32(0), salt); // Verify stored in BeamState BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(config.key, rateLimits); @@ -376,8 +384,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(ControllerLike.grantRole.selector, role, account); vm.prank(coreCouncil); - bytes32 opId = wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -404,8 +412,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(ControllerLike.revokeRole.selector, role, account); vm.prank(coreCouncil); - bytes32 opId = wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -413,11 +421,11 @@ contract TimelockWrapperTest is DssTest { } function testRevokeRoleOnSparkController() public { - _checkRevokeRole(SPARK_CONTROLLER, SPARK_ADMIN, keccak256("revokeS")); + _checkRevokeRole(SPARK_CONTROLLER, SPARK_PROXY, keccak256("revokeS")); } function testRevokeRoleOnGroveController() public { - _checkRevokeRole(GROVE_CONTROLLER, GROVE_ADMIN, keccak256("revokeG")); + _checkRevokeRole(GROVE_CONTROLLER, GROVE_PROXY, keccak256("revokeG")); } // --- setMintRecipient (Spark & Grove) --- @@ -429,8 +437,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(ControllerLike.setMintRecipient.selector, domain, recipient); vm.prank(coreCouncil); - bytes32 opId = wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -454,8 +462,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setLayerZeroRecipient(uint32,bytes32)")), endpointId, recipient); vm.prank(coreCouncil); - bytes32 opId = wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -479,8 +487,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setMaxSlippage(address,uint256)")), pool, slippage); vm.prank(coreCouncil); - bytes32 opId = wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -504,8 +512,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setOTCBuffer(address,address)")), exchange, buffer); vm.prank(coreCouncil); - bytes32 opId = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("buf"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("buf")); + wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("buf"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("buf")); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, data); @@ -521,8 +529,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setOTCRechargeRate(address,uint256)")), exchange, 1e18); vm.prank(coreCouncil); - bytes32 opId = wrapper.setOTCRechargeRate(exchange, 1e18, SPARK_CONTROLLER, bytes32(0), keccak256("rate"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("rate")); + wrapper.setOTCRechargeRate(exchange, 1e18, SPARK_CONTROLLER, bytes32(0), keccak256("rate"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("rate")); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, data); @@ -541,16 +549,16 @@ contract TimelockWrapperTest is DssTest { // First set the buffer (required before whitelisting assets) bytes memory bufferData = abi.encodeWithSelector(bytes4(keccak256("setOTCBuffer(address,address)")), exchange, buffer); vm.prank(coreCouncil); - bytes32 bufOpId = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("wlbuf"), MIN_DELAY); - _execute(bufOpId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, bufferData, SPARK_CONTROLLER), bytes32(0), keccak256("wlbuf")); + wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("wlbuf"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, bufferData, SPARK_CONTROLLER), bytes32(0), keccak256("wlbuf")); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, bufferData); // Now whitelist the asset bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setOTCWhitelistedAsset(address,address,bool)")), exchange, usdc, true); vm.prank(coreCouncil); - bytes32 opId = wrapper.setOTCWhitelistedAsset(exchange, usdc, true, SPARK_CONTROLLER, bytes32(0), keccak256("wl"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("wl")); + wrapper.setOTCWhitelistedAsset(exchange, usdc, true, SPARK_CONTROLLER, bytes32(0), keccak256("wl"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("wl")); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, data); @@ -567,8 +575,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setMaxExchangeRate(address,uint256,uint256)")), sDAI, shares, maxExpectedAssets); vm.prank(coreCouncil); - bytes32 opId = wrapper.setMaxExchangeRate(sDAI, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + wrapper.setMaxExchangeRate(sDAI, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -592,8 +600,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3PoolMaxTickDelta(address,uint24)")), pool, uint24(1000)); vm.prank(coreCouncil); - bytes32 opId = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), keccak256("tick"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("tick")); + wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), keccak256("tick"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("tick")); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); @@ -609,8 +617,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3AddLiquidityLowerTickBound(address,int24)")), pool, int24(-887220)); vm.prank(coreCouncil); - bytes32 opId = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), keccak256("lower"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("lower")); + wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), keccak256("lower"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("lower")); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); @@ -626,8 +634,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3AddLiquidityUpperTickBound(address,int24)")), pool, int24(887220)); vm.prank(coreCouncil); - bytes32 opId = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), keccak256("upper"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("upper")); + wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), keccak256("upper"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("upper")); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); @@ -643,8 +651,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3TwapSecondsAgo(address,uint32)")), pool, uint32(1800)); vm.prank(coreCouncil); - bytes32 opId = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), keccak256("twap"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("twap")); + wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), keccak256("twap"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("twap")); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); @@ -661,8 +669,8 @@ contract TimelockWrapperTest is DssTest { bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setCentrifugeRecipient(uint16,bytes32)")), centrifugeId, recipient); vm.prank(coreCouncil); - bytes32 opId = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), keccak256("cent"), MIN_DELAY); - _execute(opId, abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("cent")); + wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), keccak256("cent"), MIN_DELAY); + _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("cent")); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); From 2c424355976f4e77befd462cd6d1cdee85ef12b0 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 19 Jan 2026 17:14:30 +0700 Subject: [PATCH 058/150] Fetch proposals data in wrapper test --- src/timelock/Timelock.sol | 1 - test/timelock/TimelockWrapper.t.sol | 159 +++++++++++++++------------- 2 files changed, 83 insertions(+), 77 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 79b72cb..2619e15 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -168,7 +168,6 @@ contract Timelock is TimelockController, Pausable { return bytes32(0); } - function getOperationCount() external view returns (uint256) { return _operationIds.length(); } diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol index fecdd52..2a5be54 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapper.t.sol @@ -115,16 +115,17 @@ contract TimelockWrapperTest is DssTest { // Setup controllers, rate limiters, and cBeam in BeamState vm.startPrank(coreCouncil); - wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("spark-ctrl"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addController.selector, SPARK_CONTROLLER), bytes32(0), keccak256("spark-ctrl")); - wrapper.addController(GROVE_CONTROLLER, bytes32(0), keccak256("grove-ctrl"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addController.selector, GROVE_CONTROLLER), bytes32(0), keccak256("grove-ctrl")); - wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("spark-rl"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addRateLimits.selector, SPARK_RATE_LIMITS), bytes32(0), keccak256("spark-rl")); - wrapper.addRateLimits(GROVE_RATE_LIMITS, bytes32(0), keccak256("grove-rl"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addRateLimits.selector, GROVE_RATE_LIMITS), bytes32(0), keccak256("grove-rl")); - wrapper.addCBeam(cBeam, bytes32(0), keccak256("cbeam"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), bytes32(0), keccak256("cbeam")); + bytes32 id; + id = wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("spark-ctrl"), MIN_DELAY); + _execute(id); + id = wrapper.addController(GROVE_CONTROLLER, bytes32(0), keccak256("grove-ctrl"), MIN_DELAY); + _execute(id); + id = wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("spark-rl"), MIN_DELAY); + _execute(id); + id = wrapper.addRateLimits(GROVE_RATE_LIMITS, bytes32(0), keccak256("grove-rl"), MIN_DELAY); + _execute(id); + id = wrapper.addCBeam(cBeam, bytes32(0), keccak256("cbeam"), MIN_DELAY); + _execute(id); // Verify wrapper correctly configured BeamState assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "Spark controller not added"); @@ -145,14 +146,20 @@ contract TimelockWrapperTest is DssTest { // Helpers // ============================================================================ - function _execute(bytes memory payload, bytes32 predecessor, bytes32 salt) internal { + function _execute(bytes32 id) internal { vm.warp(block.timestamp + MIN_DELAY); - address[] memory targets = new address[](1); - targets[0] = address(beamState); - uint256[] memory values = new uint256[](1); - bytes[] memory payloads = new bytes[](1); - payloads[0] = payload; - timelock.executeBatch(targets, values, payloads, predecessor, salt); + Timelock.Operation memory op = timelock.getOperation(id); + timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); + } + + function _getControllerAction(bytes32 id) internal view returns (bytes memory data, address controller) { + Timelock.Operation memory op = timelock.getOperation(id); + // Decode payload: selector (4 bytes) || abi.encode(data, controller) + bytes memory payload = op.payloads[0]; + assembly { + payload := add(payload, 4) + } + (data, controller) = abi.decode(payload, (bytes, address)); } // ============================================================================ @@ -235,24 +242,24 @@ contract TimelockWrapperTest is DssTest { assertTrue(beamState.stopped()); vm.prank(coreCouncil); - wrapper.start(bytes32(0), keccak256("start"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.start.selector), bytes32(0), keccak256("start")); + bytes32 id = wrapper.start(bytes32(0), keccak256("start"), MIN_DELAY); + _execute(id); assertFalse(beamState.stopped()); } function testSetHop() public { vm.prank(coreCouncil); - wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), keccak256("hop"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 3600), bytes32(0), keccak256("hop")); + bytes32 id = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), keccak256("hop"), MIN_DELAY); + _execute(id); assertEq(beamState.getHop(SPARK_RATE_LIMITS), 3600); } function testSetMaxChange() public { vm.prank(coreCouncil); - wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), keccak256("mc"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.setMaxChange.selector, GROVE_RATE_LIMITS, 2e18), bytes32(0), keccak256("mc")); + bytes32 id = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), keccak256("mc"), MIN_DELAY); + _execute(id); assertEq(beamState.maxChange(GROVE_RATE_LIMITS), 2e18); } @@ -261,8 +268,8 @@ contract TimelockWrapperTest is DssTest { address rateLimits = makeAddr("rateLimits"); vm.prank(coreCouncil); - wrapper.addRateLimits(rateLimits, bytes32(0), keccak256("rl"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addRateLimits.selector, rateLimits), bytes32(0), keccak256("rl")); + bytes32 id = wrapper.addRateLimits(rateLimits, bytes32(0), keccak256("rl"), MIN_DELAY); + _execute(id); assertEq(beamState.rateLimits(rateLimits), 1); } @@ -271,8 +278,8 @@ contract TimelockWrapperTest is DssTest { address controller = makeAddr("controller"); vm.prank(coreCouncil); - wrapper.addController(controller, bytes32(0), keccak256("ctrl"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addController.selector, controller), bytes32(0), keccak256("ctrl")); + bytes32 id = wrapper.addController(controller, bytes32(0), keccak256("ctrl"), MIN_DELAY); + _execute(id); assertEq(beamState.controllers(controller), 1); } @@ -281,8 +288,8 @@ contract TimelockWrapperTest is DssTest { address beam = makeAddr("beam"); vm.prank(coreCouncil); - wrapper.addCBeam(beam, bytes32(0), keccak256("cbeam"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addCBeam.selector, beam), bytes32(0), keccak256("cbeam")); + bytes32 id = wrapper.addCBeam(beam, bytes32(0), keccak256("cbeam"), MIN_DELAY); + _execute(id); assertEq(beamState.cBeams(beam), 1); } @@ -296,8 +303,8 @@ contract TimelockWrapperTest is DssTest { }); vm.prank(coreCouncil); - wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits, config.maxAmount, config.slope), bytes32(0), salt); + bytes32 id = wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); + _execute(id); // Verify stored in BeamState BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(config.key, rateLimits); @@ -382,10 +389,10 @@ contract TimelockWrapperTest is DssTest { assertFalse(ctrl.hasRole(role, account)); - bytes memory data = abi.encodeWithSelector(ControllerLike.grantRole.selector, role, account); vm.prank(coreCouncil); - wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + bytes32 id = wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -410,10 +417,10 @@ contract TimelockWrapperTest is DssTest { vm.prank(admin); ctrl.grantRole(role, account); - bytes memory data = abi.encodeWithSelector(ControllerLike.revokeRole.selector, role, account); vm.prank(coreCouncil); - wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + bytes32 id = wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -435,10 +442,10 @@ contract TimelockWrapperTest is DssTest { uint32 domain = 6; bytes32 recipient = bytes32(uint256(uint160(makeAddr("recipient")))); - bytes memory data = abi.encodeWithSelector(ControllerLike.setMintRecipient.selector, domain, recipient); vm.prank(coreCouncil); - wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + bytes32 id = wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -460,10 +467,10 @@ contract TimelockWrapperTest is DssTest { uint32 endpointId = 111; bytes32 recipient = bytes32(uint256(uint160(makeAddr("lzRecipient")))); - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setLayerZeroRecipient(uint32,bytes32)")), endpointId, recipient); vm.prank(coreCouncil); - wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + bytes32 id = wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -485,10 +492,10 @@ contract TimelockWrapperTest is DssTest { address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; uint256 slippage = 100; - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setMaxSlippage(address,uint256)")), pool, slippage); vm.prank(coreCouncil); - wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + bytes32 id = wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -510,10 +517,10 @@ contract TimelockWrapperTest is DssTest { address exchange = makeAddr("exchange"); address buffer = makeAddr("buffer"); - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setOTCBuffer(address,address)")), exchange, buffer); vm.prank(coreCouncil); - wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("buf"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("buf")); + bytes32 id = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("buf"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, data); @@ -527,10 +534,10 @@ contract TimelockWrapperTest is DssTest { ControllerLike spark = ControllerLike(SPARK_CONTROLLER); address exchange = makeAddr("exchange"); - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setOTCRechargeRate(address,uint256)")), exchange, 1e18); vm.prank(coreCouncil); - wrapper.setOTCRechargeRate(exchange, 1e18, SPARK_CONTROLLER, bytes32(0), keccak256("rate"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("rate")); + bytes32 id = wrapper.setOTCRechargeRate(exchange, 1e18, SPARK_CONTROLLER, bytes32(0), keccak256("rate"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, data); @@ -547,18 +554,18 @@ contract TimelockWrapperTest is DssTest { address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // First set the buffer (required before whitelisting assets) - bytes memory bufferData = abi.encodeWithSelector(bytes4(keccak256("setOTCBuffer(address,address)")), exchange, buffer); vm.prank(coreCouncil); - wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("wlbuf"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, bufferData, SPARK_CONTROLLER), bytes32(0), keccak256("wlbuf")); + bytes32 bufferId = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("wlbuf"), MIN_DELAY); + (bytes memory bufferData,) = _getControllerAction(bufferId); + _execute(bufferId); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, bufferData); // Now whitelist the asset - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setOTCWhitelistedAsset(address,address,bool)")), exchange, usdc, true); vm.prank(coreCouncil); - wrapper.setOTCWhitelistedAsset(exchange, usdc, true, SPARK_CONTROLLER, bytes32(0), keccak256("wl"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, SPARK_CONTROLLER), bytes32(0), keccak256("wl")); + bytes32 id = wrapper.setOTCWhitelistedAsset(exchange, usdc, true, SPARK_CONTROLLER, bytes32(0), keccak256("wl"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, data); @@ -572,11 +579,11 @@ contract TimelockWrapperTest is DssTest { address sDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA; uint256 shares = 1e18; uint256 maxExpectedAssets = 1.1e18; - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setMaxExchangeRate(address,uint256,uint256)")), sDAI, shares, maxExpectedAssets); vm.prank(coreCouncil); - wrapper.setMaxExchangeRate(sDAI, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, controller), bytes32(0), salt); + bytes32 id = wrapper.setMaxExchangeRate(sDAI, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); @@ -598,10 +605,10 @@ contract TimelockWrapperTest is DssTest { ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3PoolMaxTickDelta(address,uint24)")), pool, uint24(1000)); vm.prank(coreCouncil); - wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), keccak256("tick"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("tick")); + bytes32 id = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), keccak256("tick"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); @@ -615,10 +622,10 @@ contract TimelockWrapperTest is DssTest { ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3AddLiquidityLowerTickBound(address,int24)")), pool, int24(-887220)); vm.prank(coreCouncil); - wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), keccak256("lower"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("lower")); + bytes32 id = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), keccak256("lower"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); @@ -632,10 +639,10 @@ contract TimelockWrapperTest is DssTest { ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3AddLiquidityUpperTickBound(address,int24)")), pool, int24(887220)); vm.prank(coreCouncil); - wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), keccak256("upper"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("upper")); + bytes32 id = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), keccak256("upper"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); @@ -649,10 +656,10 @@ contract TimelockWrapperTest is DssTest { ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setUniswapV3TwapSecondsAgo(address,uint32)")), pool, uint32(1800)); vm.prank(coreCouncil); - wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), keccak256("twap"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("twap")); + bytes32 id = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), keccak256("twap"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); @@ -667,10 +674,10 @@ contract TimelockWrapperTest is DssTest { uint16 centrifugeId = 1; bytes32 recipient = bytes32(uint256(uint160(makeAddr("centrifuge")))); - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("setCentrifugeRecipient(uint16,bytes32)")), centrifugeId, recipient); vm.prank(coreCouncil); - wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), keccak256("cent"), MIN_DELAY); - _execute(abi.encodeWithSelector(BeamState.addInitControllerActions.selector, data, GROVE_CONTROLLER), bytes32(0), keccak256("cent")); + bytes32 id = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), keccak256("cent"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); From 33c603ca6309e5651f8284babc5616a53a4df69e Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:38:15 +0700 Subject: [PATCH 059/150] getNextExecutableOperation pagination --- src/timelock/Timelock.sol | 19 ++++---- src/timelock/TimelockKeeperJob.sol | 4 +- test/timelock/Timelock.t.sol | 75 ++++++++++++++++++++++++------ 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 2619e15..9615c16 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -143,16 +143,15 @@ contract Timelock is TimelockController, Pausable { // Operations may still not be executable due to various downstream conditions. // It is assumed that this is not a perfect fetching mechanism and that if needed proposals // can be executed without cron keepers, or canceled in case they are jamming this mechanism. - function getNextExecutableOperation() public view returns (bytes32 id) { - return getNextExecutableOperation(0); - } - - /// Returns the next executable operation, limited by maxIterations (0 = no limit) - function getNextExecutableOperation(uint256 maxIterations) public view returns (bytes32 id) { + function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) public view returns (bytes32 id, uint256 nextIndex) { uint256 length = _operationIds.length(); - uint256 limit = maxIterations == 0 ? length : (maxIterations < length ? maxIterations : length); + if (startIndex >= length) return (bytes32(0), length); + + // Safe: if maxIterations is 0 (no limit) or exceeds remaining operations, scan to end + uint256 remaining = length - startIndex; + uint256 endIndex = (maxIterations == 0 || maxIterations > remaining) ? length : startIndex + maxIterations; - for (uint256 i = 0; i < limit; ++i) { + for (uint256 i = startIndex; i < endIndex; ++i) { bytes32 operationId = _operationIds.at(i); // Check if operation is ready @@ -162,10 +161,10 @@ contract Timelock is TimelockController, Pausable { Operation memory op = operations[operationId]; if (op.predecessor != bytes32(0) && !isOperationDone(op.predecessor)) continue; - return operationId; + return (operationId, i + 1); } - return bytes32(0); + return (bytes32(0), endIndex); } function getOperationCount() external view returns (uint256) { diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol index 54adb79..e54699e 100644 --- a/src/timelock/TimelockKeeperJob.sol +++ b/src/timelock/TimelockKeeperJob.sol @@ -33,7 +33,7 @@ interface TimelockLike { bytes32 salt; } - function getNextExecutableOperation(uint256 maxIterations) external view returns (bytes32 id); + function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) external view returns (bytes32 id, uint256 nextIndex); function getOperation(bytes32 id) external view returns (Operation memory op); function executeBatch( address[] calldata targets, @@ -66,7 +66,7 @@ contract TimelockKeeperJob is IJob { function work(bytes32 network, bytes calldata) external { if (!sequencer.isMaster(network)) revert NotMaster(network); - bytes32 id = timelock.getNextExecutableOperation(maxIterations); + (bytes32 id,) = timelock.getNextExecutableOperation(0, maxIterations); if (id == bytes32(0)) revert NoExecutableOperation(); TimelockLike.Operation memory op = timelock.getOperation(id); diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol index 564f03b..a376c0c 100644 --- a/test/timelock/Timelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -904,7 +904,7 @@ contract TimelockTest is Test { // ============================================================================ function testGetNextExecutableOperationEmpty() public view { - bytes32 id = timelock.getNextExecutableOperation(); + (bytes32 id,) = timelock.getNextExecutableOperation(0, 0); assertEq(id, bytes32(0)); } @@ -914,7 +914,7 @@ contract TimelockTest is Test { vm.prank(proposer); _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - bytes32 id = timelock.getNextExecutableOperation(); + (bytes32 id,) = timelock.getNextExecutableOperation(0, 0); assertEq(id, bytes32(0)); } @@ -926,7 +926,7 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - bytes32 id = timelock.getNextExecutableOperation(); + (bytes32 id,) = timelock.getNextExecutableOperation(0, 0); assertEq(id, expectedId); } @@ -947,7 +947,7 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - bytes32 nextId = timelock.getNextExecutableOperation(); + (bytes32 nextId,) = timelock.getNextExecutableOperation(0, 0); assertEq(nextId, id1); } @@ -962,12 +962,12 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - bytes32 nextId = timelock.getNextExecutableOperation(); + (bytes32 nextId,) = timelock.getNextExecutableOperation(0, 0); assertEq(nextId, id1); _execute(address(mockTarget), 0, data1, bytes32(0), keccak256("s1")); - nextId = timelock.getNextExecutableOperation(); + (nextId,) = timelock.getNextExecutableOperation(0, 0); bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("s2")); assertEq(nextId, id2); } @@ -990,15 +990,15 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); // With maxIterations=3, should not find the ready operation (it's at index 3) - bytes32 id = timelock.getNextExecutableOperation(3); + (bytes32 id,) = timelock.getNextExecutableOperation(0, 3); assertEq(id, bytes32(0), "Should not find with limit 3"); // With maxIterations=4, should find it - id = timelock.getNextExecutableOperation(4); + (id,) = timelock.getNextExecutableOperation(0, 4); assertEq(id, readyId, "Should find with limit 4"); // With maxIterations=100, should also find it - id = timelock.getNextExecutableOperation(100); + (id,) = timelock.getNextExecutableOperation(0, 100); assertEq(id, readyId, "Should find with limit 100"); } @@ -1018,12 +1018,60 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); // With maxIterations=0 (no limit), should find it even though it's at index 10 - bytes32 id = timelock.getNextExecutableOperation(0); + (bytes32 id,) = timelock.getNextExecutableOperation(0, 0); assertEq(id, readyId, "Zero should mean no limit"); + } + + function testGetNextExecutableOperationStartIndexPastEnd() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + vm.prank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + // length=1, startIndex=5 -> returns (0, length) + (bytes32 id, uint256 nextIndex) = timelock.getNextExecutableOperation(5, 0); + assertEq(id, bytes32(0)); + assertEq(nextIndex, 1); + } + + function testGetNextExecutableOperationLargeMaxIterations() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + vm.prank(proposer); + bytes32 expectedId = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); - // The no-param version should also work (it calls with 0) - id = timelock.getNextExecutableOperation(); - assertEq(id, readyId, "No-param version should also find it"); + // Very large maxIterations should not overflow + (bytes32 id, uint256 nextIndex) = timelock.getNextExecutableOperation(0, type(uint256).max); + assertEq(id, expectedId); + assertEq(nextIndex, 1); + } + + function testGetNextExecutableOperationPagination() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + vm.startPrank(proposer); + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s0"), 10 days); // not ready + bytes32 id1 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s1"), MIN_DELAY); // ready + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), 10 days); // not ready + bytes32 id3 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s3"), MIN_DELAY); // ready + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + // First call finds id1 at index 1, returns nextIndex=2 + (bytes32 id, uint256 nextIndex) = timelock.getNextExecutableOperation(0, 0); + assertEq(id, id1); + assertEq(nextIndex, 2); + + // Continue from nextIndex=2, finds id3 at index 3 + (id, nextIndex) = timelock.getNextExecutableOperation(nextIndex, 0); + assertEq(id, id3); + assertEq(nextIndex, 4); + + // Continue from nextIndex=4, nothing left + (id, nextIndex) = timelock.getNextExecutableOperation(nextIndex, 0); + assertEq(id, bytes32(0)); + assertEq(nextIndex, 4); } function testGetOperationNonExistent() public view { @@ -1119,4 +1167,5 @@ contract TimelockTest is Test { assertEq(op.predecessor, predecessor); assertEq(op.salt, salt); } + } From 24ba943f87c1ab7883bac7bc7908bb1a37ec05a2 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 20 Jan 2026 12:01:38 +0700 Subject: [PATCH 060/150] Add getOperationIndex, remove nextIndex return param --- src/timelock/Timelock.sol | 16 +++-- src/timelock/TimelockKeeperJob.sol | 4 +- test/timelock/Timelock.t.sol | 110 ++++++++++++++++++++++------- 3 files changed, 100 insertions(+), 30 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 9615c16..7899c92 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -143,9 +143,9 @@ contract Timelock is TimelockController, Pausable { // Operations may still not be executable due to various downstream conditions. // It is assumed that this is not a perfect fetching mechanism and that if needed proposals // can be executed without cron keepers, or canceled in case they are jamming this mechanism. - function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) public view returns (bytes32 id, uint256 nextIndex) { + function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) public view returns (bytes32 id) { uint256 length = _operationIds.length(); - if (startIndex >= length) return (bytes32(0), length); + if (startIndex >= length) return bytes32(0); // Safe: if maxIterations is 0 (no limit) or exceeds remaining operations, scan to end uint256 remaining = length - startIndex; @@ -161,10 +161,18 @@ contract Timelock is TimelockController, Pausable { Operation memory op = operations[operationId]; if (op.predecessor != bytes32(0) && !isOperationDone(op.predecessor)) continue; - return (operationId, i + 1); + return operationId; } - return (bytes32(0), endIndex); + return bytes32(0); + } + + function getOperationIndex(bytes32 id) public view returns (bool exists, uint256 index) { + uint256 position = _operationIds._inner._positions[id]; + if (position == 0) { + return (false, 0); + } + return (true, position - 1); } function getOperationCount() external view returns (uint256) { diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol index e54699e..e13b2f7 100644 --- a/src/timelock/TimelockKeeperJob.sol +++ b/src/timelock/TimelockKeeperJob.sol @@ -33,7 +33,7 @@ interface TimelockLike { bytes32 salt; } - function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) external view returns (bytes32 id, uint256 nextIndex); + function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) external view returns (bytes32 id); function getOperation(bytes32 id) external view returns (Operation memory op); function executeBatch( address[] calldata targets, @@ -66,7 +66,7 @@ contract TimelockKeeperJob is IJob { function work(bytes32 network, bytes calldata) external { if (!sequencer.isMaster(network)) revert NotMaster(network); - (bytes32 id,) = timelock.getNextExecutableOperation(0, maxIterations); + bytes32 id = timelock.getNextExecutableOperation(0, maxIterations); if (id == bytes32(0)) revert NoExecutableOperation(); TimelockLike.Operation memory op = timelock.getOperation(id); diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol index a376c0c..2a87eb2 100644 --- a/test/timelock/Timelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -904,7 +904,7 @@ contract TimelockTest is Test { // ============================================================================ function testGetNextExecutableOperationEmpty() public view { - (bytes32 id,) = timelock.getNextExecutableOperation(0, 0); + bytes32 id = timelock.getNextExecutableOperation(0, 0); assertEq(id, bytes32(0)); } @@ -914,7 +914,7 @@ contract TimelockTest is Test { vm.prank(proposer); _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - (bytes32 id,) = timelock.getNextExecutableOperation(0, 0); + bytes32 id = timelock.getNextExecutableOperation(0, 0); assertEq(id, bytes32(0)); } @@ -926,7 +926,7 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - (bytes32 id,) = timelock.getNextExecutableOperation(0, 0); + bytes32 id = timelock.getNextExecutableOperation(0, 0); assertEq(id, expectedId); } @@ -947,7 +947,7 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - (bytes32 nextId,) = timelock.getNextExecutableOperation(0, 0); + bytes32 nextId = timelock.getNextExecutableOperation(0, 0); assertEq(nextId, id1); } @@ -962,12 +962,12 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - (bytes32 nextId,) = timelock.getNextExecutableOperation(0, 0); + bytes32 nextId = timelock.getNextExecutableOperation(0, 0); assertEq(nextId, id1); _execute(address(mockTarget), 0, data1, bytes32(0), keccak256("s1")); - (nextId,) = timelock.getNextExecutableOperation(0, 0); + nextId = timelock.getNextExecutableOperation(0, 0); bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("s2")); assertEq(nextId, id2); } @@ -990,15 +990,15 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); // With maxIterations=3, should not find the ready operation (it's at index 3) - (bytes32 id,) = timelock.getNextExecutableOperation(0, 3); + bytes32 id = timelock.getNextExecutableOperation(0, 3); assertEq(id, bytes32(0), "Should not find with limit 3"); // With maxIterations=4, should find it - (id,) = timelock.getNextExecutableOperation(0, 4); + id = timelock.getNextExecutableOperation(0, 4); assertEq(id, readyId, "Should find with limit 4"); // With maxIterations=100, should also find it - (id,) = timelock.getNextExecutableOperation(0, 100); + id = timelock.getNextExecutableOperation(0, 100); assertEq(id, readyId, "Should find with limit 100"); } @@ -1018,7 +1018,7 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); // With maxIterations=0 (no limit), should find it even though it's at index 10 - (bytes32 id,) = timelock.getNextExecutableOperation(0, 0); + bytes32 id = timelock.getNextExecutableOperation(0, 0); assertEq(id, readyId, "Zero should mean no limit"); } @@ -1027,10 +1027,9 @@ contract TimelockTest is Test { vm.prank(proposer); _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - // length=1, startIndex=5 -> returns (0, length) - (bytes32 id, uint256 nextIndex) = timelock.getNextExecutableOperation(5, 0); + // length=1, startIndex=5 -> returns bytes32(0) + bytes32 id = timelock.getNextExecutableOperation(5, 0); assertEq(id, bytes32(0)); - assertEq(nextIndex, 1); } function testGetNextExecutableOperationLargeMaxIterations() public { @@ -1041,9 +1040,8 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); // Very large maxIterations should not overflow - (bytes32 id, uint256 nextIndex) = timelock.getNextExecutableOperation(0, type(uint256).max); + bytes32 id = timelock.getNextExecutableOperation(0, type(uint256).max); assertEq(id, expectedId); - assertEq(nextIndex, 1); } function testGetNextExecutableOperationPagination() public { @@ -1058,20 +1056,84 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - // First call finds id1 at index 1, returns nextIndex=2 - (bytes32 id, uint256 nextIndex) = timelock.getNextExecutableOperation(0, 0); + // First call finds id1 + bytes32 id = timelock.getNextExecutableOperation(0, 0); assertEq(id, id1); - assertEq(nextIndex, 2); - // Continue from nextIndex=2, finds id3 at index 3 - (id, nextIndex) = timelock.getNextExecutableOperation(nextIndex, 0); + // Get the index of id1 + (bool exists, uint256 index) = timelock.getOperationIndex(id1); + assertTrue(exists); + assertEq(index, 1); + + // Execute id1, then continue from same index (swap-and-pop puts last element there) + _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s1")); + + // Continue from same index, finds id3 (which was swapped from the end) + id = timelock.getNextExecutableOperation(index, 0); assertEq(id, id3); - assertEq(nextIndex, 4); - // Continue from nextIndex=4, nothing left - (id, nextIndex) = timelock.getNextExecutableOperation(nextIndex, 0); + // Get index of id3 + (exists, index) = timelock.getOperationIndex(id3); + assertTrue(exists); + + // Execute id3 + _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s3")); + + // Continue from same index, nothing ready left + id = timelock.getNextExecutableOperation(index, 0); assertEq(id, bytes32(0)); - assertEq(nextIndex, 4); + } + + function testGetOperationIndex() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + // Schedule 3 operations + vm.startPrank(proposer); + bytes32 id0 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s0"), MIN_DELAY); + bytes32 id1 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s1"), MIN_DELAY); + bytes32 id2 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), MIN_DELAY); + vm.stopPrank(); + + // Verify initial indices + (bool exists0, uint256 index0) = timelock.getOperationIndex(id0); + (bool exists1, uint256 index1) = timelock.getOperationIndex(id1); + (bool exists2, uint256 index2) = timelock.getOperationIndex(id2); + + assertTrue(exists0); + assertTrue(exists1); + assertTrue(exists2); + assertEq(index0, 0); + assertEq(index1, 1); + assertEq(index2, 2); + + // Execute id0 (index 0) - id2 should swap into index 0 + vm.warp(block.timestamp + MIN_DELAY); + _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s0")); + + // id0 no longer exists + (exists0,) = timelock.getOperationIndex(id0); + assertFalse(exists0); + + // id1 unchanged at index 1 + (exists1, index1) = timelock.getOperationIndex(id1); + assertTrue(exists1); + assertEq(index1, 1); + + // id2 swapped to index 0 + (exists2, index2) = timelock.getOperationIndex(id2); + assertTrue(exists2); + assertEq(index2, 0); + + // Execute id1 (index 1) - it's now last, so just removed + _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s1")); + + (exists1,) = timelock.getOperationIndex(id1); + assertFalse(exists1); + + // id2 still at index 0 + (exists2, index2) = timelock.getOperationIndex(id2); + assertTrue(exists2); + assertEq(index2, 0); } function testGetOperationNonExistent() public view { From cefd7ab0ebb43459cde008735d3bb5049ce25de2 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 20 Jan 2026 12:12:39 +0700 Subject: [PATCH 061/150] public => external --- src/timelock/Timelock.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 7899c92..2ec14c7 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -143,7 +143,7 @@ contract Timelock is TimelockController, Pausable { // Operations may still not be executable due to various downstream conditions. // It is assumed that this is not a perfect fetching mechanism and that if needed proposals // can be executed without cron keepers, or canceled in case they are jamming this mechanism. - function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) public view returns (bytes32 id) { + function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) external view returns (bytes32 id) { uint256 length = _operationIds.length(); if (startIndex >= length) return bytes32(0); @@ -167,7 +167,7 @@ contract Timelock is TimelockController, Pausable { return bytes32(0); } - function getOperationIndex(bytes32 id) public view returns (bool exists, uint256 index) { + function getOperationIndex(bytes32 id) external view returns (bool exists, uint256 index) { uint256 position = _operationIds._inner._positions[id]; if (position == 0) { return (false, 0); From cbf09070bc786300d3c03d9c8e45606f0911462a Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 20 Jan 2026 12:19:49 +0700 Subject: [PATCH 062/150] Redeploy on constructor test, standarize modifiers test --- test/timelock/TimelockWrapper.t.sol | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol index 2a5be54..24ea358 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapper.t.sol @@ -166,10 +166,14 @@ contract TimelockWrapperTest is DssTest { // Authorization Tests // ============================================================================ - function testConstructor() public view { - assertEq(address(wrapper.timelock()), address(timelock), "Timelock set correctly"); - assertEq(address(wrapper.beamState()), address(beamState), "BeamState set correctly"); - assertEq(wrapper.wards(address(this)), 1, "Deployer is ward"); + function testConstructor() public { + vm.expectEmit(true, false, false, true); + emit Rely(address(this)); + TimelockWrapper newWrapper = new TimelockWrapper(address(timelock), address(beamState)); + + assertEq(address(newWrapper.timelock()), address(timelock), "Timelock set correctly"); + assertEq(address(newWrapper.beamState()), address(beamState), "BeamState set correctly"); + assertEq(newWrapper.wards(address(this)), 1, "Deployer is ward"); } function testAuth() public { @@ -191,11 +195,9 @@ contract TimelockWrapperTest is DssTest { } function testAuthModifiers() public { - bytes4[] memory authedMethods = new bytes4[](4); - authedMethods[0] = wrapper.rely.selector; - authedMethods[1] = wrapper.deny.selector; - authedMethods[2] = wrapper.kiss.selector; - authedMethods[3] = wrapper.diss.selector; + bytes4[] memory authedMethods = new bytes4[](2); + authedMethods[0] = wrapper.kiss.selector; + authedMethods[1] = wrapper.diss.selector; vm.startPrank(address(0xBEEF)); checkModifier(address(wrapper), "TimelockWrapper/not-authorized", authedMethods); From cedd4c002e4e8b491fa7c12bbcd86f741a6cf1d9 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:20:30 +0700 Subject: [PATCH 063/150] Minor changes --- src/timelock/Timelock.sol | 4 +--- src/timelock/TimelockKeeperJob.sol | 6 ++++-- src/timelock/TimelockWrapper.sol | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 2ec14c7..7bc438d 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -39,7 +39,6 @@ contract Timelock is TimelockController, Pausable { // Changes from original timelock: // - Do not allow proposers to change admin-only configurations - // - Support adding cancellers which are not proposers // - Make execution permissionless // - Add pausing logic // - Allow admin to change the delay immediately @@ -96,8 +95,7 @@ contract Timelock is TimelockController, Pausable { bytes32 salt, uint256 delay ) public virtual override whenNotPaused { - uint256 len = targets.length; - for (uint256 i = 0; i < len; ++i) { + for (uint256 i = 0; i < targets.length; ++i) { require(targets[i] != address(this), "Timelock/self-calls-disabled"); } diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol index e13b2f7..52b3ed8 100644 --- a/src/timelock/TimelockKeeperJob.sol +++ b/src/timelock/TimelockKeeperJob.sol @@ -16,8 +16,10 @@ pragma solidity ^0.8.21; +// TODO: import this from dss-cron/src/interfaces once porting this contract interface IJob { - function workable(bytes32 network) external returns (bool, bytes memory); + function work(bytes32 network, bytes calldata args) external; + function workable(bytes32 network) external returns (bool canWork, bytes memory args); } interface SequencerLike { @@ -63,7 +65,7 @@ contract TimelockKeeperJob is IJob { maxIterations = _maxIterations; } - function work(bytes32 network, bytes calldata) external { + function work(bytes32 network, bytes calldata) external override { if (!sequencer.isMaster(network)) revert NotMaster(network); bytes32 id = timelock.getNextExecutableOperation(0, maxIterations); diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index 41e5b42..fa94c40 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -79,8 +79,8 @@ struct RateLimitConfig { // Notes: // - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). // - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. -// - The actual downstream changes only take effect when govops use the BEAMState configurations, so atomicity in configurations can not be assumed. -// - As part of a controller onboarding it should be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. +// - The actual downstream changes only take effect when cBEAMs use the BEAMState configurations, so atomicity in configurations can not be assumed. +// - As part of a controller onboarding it might need to be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. contract TimelockWrapper { // --- Auth --- mapping(address => uint256) public wards; From af35491ff5067caf8803303dd4a28af70cf0f7f1 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 20 Jan 2026 20:31:53 +0700 Subject: [PATCH 064/150] Avoid loading full struct in getNextExecutableOperation --- src/timelock/Timelock.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 7bc438d..297f326 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -156,8 +156,8 @@ contract Timelock is TimelockController, Pausable { if (!isOperationReady(operationId)) continue; // Check if predecessor is done, if any - Operation memory op = operations[operationId]; - if (op.predecessor != bytes32(0) && !isOperationDone(op.predecessor)) continue; + bytes32 predecessor = operations[operationId].predecessor; + if (predecessor != bytes32(0) && !isOperationDone(predecessor)) continue; return operationId; } From 5c590478953f6606ec0ac8d1b389f482182f5782 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 20 Jan 2026 20:35:07 +0700 Subject: [PATCH 065/150] memory => calldata --- src/timelock/TimelockWrapper.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index fa94c40..5e001bd 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -18,18 +18,18 @@ pragma solidity ^0.8.21; interface TimelockLike { function scheduleBatch( - address[] memory targets, - uint256[] memory values, - bytes[] memory payloads, + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, bytes32 predecessor, bytes32 salt, uint256 delay ) external; function hashOperationBatch( - address[] memory targets, - uint256[] memory values, - bytes[] memory payloads, + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, bytes32 predecessor, bytes32 salt ) external view returns (bytes32); From 06e2e54956ef5d44cda82f4036e5fdd9dfc1ffa9 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Wed, 21 Jan 2026 11:17:52 +0700 Subject: [PATCH 066/150] Use maxIterations >= remaining --- src/timelock/Timelock.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 297f326..e17d78c 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -147,7 +147,7 @@ contract Timelock is TimelockController, Pausable { // Safe: if maxIterations is 0 (no limit) or exceeds remaining operations, scan to end uint256 remaining = length - startIndex; - uint256 endIndex = (maxIterations == 0 || maxIterations > remaining) ? length : startIndex + maxIterations; + uint256 endIndex = (maxIterations == 0 || maxIterations >= remaining) ? length : startIndex + maxIterations; for (uint256 i = startIndex; i < endIndex; ++i) { bytes32 operationId = _operationIds.at(i); From 717bef953bf071175e8bf9e030e5931910c8ce96 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Wed, 21 Jan 2026 11:44:46 +0700 Subject: [PATCH 067/150] Remove unneeded id storing, rename test --- test/timelock/Timelock.t.sol | 76 ++++++++++++------------------------ 1 file changed, 26 insertions(+), 50 deletions(-) diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol index 2a87eb2..786f7fc 100644 --- a/test/timelock/Timelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -371,10 +371,9 @@ contract TimelockTest is Test { function testCannotScheduleDuplicate() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); vm.startPrank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockUnexpectedOperationState.selector, id, bytes32(1 << uint8(TimelockController.OperationState.Unset)))); _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); @@ -455,10 +454,9 @@ contract TimelockTest is Test { function testExecuteRevertsIfNotReady() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); vm.expectRevert(abi.encodeWithSelector(TimelockController.TimelockUnexpectedOperationState.selector, id, bytes32(1 << uint8(TimelockController.OperationState.Ready)))); _execute(address(mockTarget), 0, data, bytes32(0), SALT); @@ -540,10 +538,9 @@ contract TimelockTest is Test { function testExecuteWithPredecessor() public { bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); vm.prank(proposer); - _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); + bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); @@ -561,10 +558,9 @@ contract TimelockTest is Test { function testExecuteRevertsIfPredecessorNotDone() public { bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes32 id1 = _hashOperation(address(mockTarget), 0, data1, bytes32(0), SALT); vm.prank(proposer); - _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); + bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), SALT, MIN_DELAY); bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); @@ -601,10 +597,9 @@ contract TimelockTest is Test { function testCancelProposerCanCancel() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); vm.prank(proposer); vm.expectEmit(true, false, false, false); @@ -616,10 +611,9 @@ contract TimelockTest is Test { function testCancelCancellerCanCancel() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); vm.prank(canceller); timelock.cancel(id); @@ -629,10 +623,9 @@ contract TimelockTest is Test { function testCancelRevertsIfNotAuthorized() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); bytes32 cancellerRole = timelock.CANCELLER_ROLE(); vm.prank(other); @@ -642,10 +635,9 @@ contract TimelockTest is Test { function testCancelCannotCancelAfterExecution() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 id = _hashOperation(address(mockTarget), 0, data, bytes32(0), SALT); vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); + bytes32 id = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); _execute(address(mockTarget), 0, data, bytes32(0), SALT); @@ -904,8 +896,7 @@ contract TimelockTest is Test { // ============================================================================ function testGetNextExecutableOperationEmpty() public view { - bytes32 id = timelock.getNextExecutableOperation(0, 0); - assertEq(id, bytes32(0)); + assertEq(timelock.getNextExecutableOperation(0, 0), bytes32(0)); } function testGetNextExecutableOperationNotReady() public { @@ -914,8 +905,7 @@ contract TimelockTest is Test { vm.prank(proposer); _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - bytes32 id = timelock.getNextExecutableOperation(0, 0); - assertEq(id, bytes32(0)); + assertEq(timelock.getNextExecutableOperation(0, 0), bytes32(0)); } function testGetNextExecutableOperationReady() public { @@ -926,8 +916,7 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - bytes32 id = timelock.getNextExecutableOperation(0, 0); - assertEq(id, expectedId); + assertEq(timelock.getNextExecutableOperation(0, 0), expectedId); } function testGetNextExecutableOperationMultipleReturnsFirst() public { @@ -947,29 +936,25 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - bytes32 nextId = timelock.getNextExecutableOperation(0, 0); - assertEq(nextId, id1); + assertEq(timelock.getNextExecutableOperation(0, 0), id1); } - function testGetNextExecutableOperationSkipsPendingPredecessor() public { + function testGetNextExecutableOperationChangesAfterExecution() public { bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); vm.startPrank(proposer); bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); - _schedule(address(mockTarget), 0, data2, id1, keccak256("s2"), MIN_DELAY); + bytes32 id2 = _schedule(address(mockTarget), 0, data2, id1, keccak256("s2"), MIN_DELAY); vm.stopPrank(); vm.warp(block.timestamp + MIN_DELAY); - bytes32 nextId = timelock.getNextExecutableOperation(0, 0); - assertEq(nextId, id1); + assertEq(timelock.getNextExecutableOperation(0, 0), id1); _execute(address(mockTarget), 0, data1, bytes32(0), keccak256("s1")); - nextId = timelock.getNextExecutableOperation(0, 0); - bytes32 id2 = _hashOperation(address(mockTarget), 0, data2, id1, keccak256("s2")); - assertEq(nextId, id2); + assertEq(timelock.getNextExecutableOperation(0, 0), id2); } function testGetNextExecutableOperationWithMaxIterations() public { @@ -989,17 +974,14 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); - // With maxIterations=3, should not find the ready operation (it's at index 3) - bytes32 id = timelock.getNextExecutableOperation(0, 3); - assertEq(id, bytes32(0), "Should not find with limit 3"); + // With maxIterations=3, should not find the ready operation (it's at index 3); + assertEq(timelock.getNextExecutableOperation(0, 3), bytes32(0), "Should not find with limit 3"); // With maxIterations=4, should find it - id = timelock.getNextExecutableOperation(0, 4); - assertEq(id, readyId, "Should find with limit 4"); + assertEq(timelock.getNextExecutableOperation(0, 4), readyId, "Should find with limit 4"); // With maxIterations=100, should also find it - id = timelock.getNextExecutableOperation(0, 100); - assertEq(id, readyId, "Should find with limit 100"); + assertEq(timelock.getNextExecutableOperation(0, 100), readyId, "Should find with limit 100"); } function testGetNextExecutableOperationZeroMeansNoLimit() public { @@ -1018,8 +1000,7 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); // With maxIterations=0 (no limit), should find it even though it's at index 10 - bytes32 id = timelock.getNextExecutableOperation(0, 0); - assertEq(id, readyId, "Zero should mean no limit"); + assertEq(timelock.getNextExecutableOperation(0, 0), readyId, "Zero should mean no limit"); } function testGetNextExecutableOperationStartIndexPastEnd() public { @@ -1028,8 +1009,7 @@ contract TimelockTest is Test { _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); // length=1, startIndex=5 -> returns bytes32(0) - bytes32 id = timelock.getNextExecutableOperation(5, 0); - assertEq(id, bytes32(0)); + assertEq(timelock.getNextExecutableOperation(5, 0), bytes32(0)); } function testGetNextExecutableOperationLargeMaxIterations() public { @@ -1040,8 +1020,7 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); // Very large maxIterations should not overflow - bytes32 id = timelock.getNextExecutableOperation(0, type(uint256).max); - assertEq(id, expectedId); + assertEq(timelock.getNextExecutableOperation(0, type(uint256).max), expectedId); } function testGetNextExecutableOperationPagination() public { @@ -1057,8 +1036,7 @@ contract TimelockTest is Test { vm.warp(block.timestamp + MIN_DELAY); // First call finds id1 - bytes32 id = timelock.getNextExecutableOperation(0, 0); - assertEq(id, id1); + assertEq(timelock.getNextExecutableOperation(0, 0), id1); // Get the index of id1 (bool exists, uint256 index) = timelock.getOperationIndex(id1); @@ -1069,8 +1047,7 @@ contract TimelockTest is Test { _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s1")); // Continue from same index, finds id3 (which was swapped from the end) - id = timelock.getNextExecutableOperation(index, 0); - assertEq(id, id3); + assertEq(timelock.getNextExecutableOperation(index, 0), id3); // Get index of id3 (exists, index) = timelock.getOperationIndex(id3); @@ -1080,8 +1057,7 @@ contract TimelockTest is Test { _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s3")); // Continue from same index, nothing ready left - id = timelock.getNextExecutableOperation(index, 0); - assertEq(id, bytes32(0)); + assertEq(timelock.getNextExecutableOperation(index, 0), bytes32(0)); } function testGetOperationIndex() public { From ea3f4bad5a6a68e00a5594de3ca27cc44e18b7b4 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 21 Jan 2026 09:25:36 -0300 Subject: [PATCH 068/150] Fix CI + Use some real contracts in the Integration test --- .github/workflows/test.yml | 3 +- test/Integration.t.sol | 272 +++++++++++++++++++------------------ 2 files changed, 139 insertions(+), 136 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 90f1e7d..5515439 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,4 +32,5 @@ jobs: run: | forge test -vvv id: test - + env: + ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 91994e5..57399e1 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -16,7 +16,7 @@ pragma solidity ^0.8.21; -import { Test } from "forge-std/Test.sol"; +import "dss-test/DssTest.sol"; import { PASInstance } from "deploy/PASInstance.sol"; import { PASDeploy } from "deploy/PASDeploy.sol"; import { PASInit } from "deploy/PASInit.sol"; @@ -25,57 +25,40 @@ import { Configurator, RateLimitsLike } from "src/Configurator.sol"; import { Timelock } from "src/timelock/Timelock.sol"; import { TimelockWrapper, RateLimitConfig } from "src/timelock/TimelockWrapper.sol"; -// Mock contract implementing RateLimits interface and controller functionality -contract MockTarget { - RateLimitsLike.RateLimitData public rateLimitData; - uint256 public currentRateLimit; - bool public shouldFail; - bytes public lastCallData; - - function setRateLimitData( - bytes32, - uint256 maxAmount, - uint256 slope, - uint256 lastAmount, - uint256 lastUpdated - ) external { - rateLimitData = RateLimitsLike.RateLimitData(maxAmount, slope, lastAmount, lastUpdated); - } - - function setUnlimitedRateLimitData(bytes32) external { - rateLimitData = RateLimitsLike.RateLimitData(type(uint256).max, 0, type(uint256).max, block.timestamp); - } - - function getRateLimitData(bytes32) external view returns (RateLimitsLike.RateLimitData memory) { - return rateLimitData; - } - - function getCurrentRateLimit(bytes32) external view returns (uint256) { - return currentRateLimit; - } +// Interface for SparkController +interface ControllerLike { + function rateLimits() external view returns (address); + function setMintRecipient(uint32 domain, bytes32 mintRecipient) external; + function mintRecipients(uint32 domain) external view returns (bytes32); + function grantRole(bytes32 role, address account) external; +} - function setCurrentRateLimit(uint256 _current) external { - currentRateLimit = _current; +// Extended RateLimitsLike with role management +interface RateLimitsWithRolesLike { + struct RateLimitData { + uint256 maxAmount; + uint256 slope; + uint256 lastAmount; + uint256 lastUpdated; } - function setShouldFail(bool _fail) external { - shouldFail = _fail; - } + function getRateLimitData(bytes32) external view returns (RateLimitData memory); + function getCurrentRateLimit(bytes32) external view returns (uint256); + function setRateLimitData(bytes32, uint256, uint256, uint256, uint256) external; + function setUnlimitedRateLimitData(bytes32) external; + function grantRole(bytes32 role, address account) external; +} - // Mock controller function for testing callControllerAction - function controllerFunction(uint256 value) external returns (uint256) { - lastCallData = msg.data; - require(!shouldFail, "MockTarget/controller-function-failed"); - return value * 2; - } +contract IntegrationTest is DssTest { - fallback() external { - lastCallData = msg.data; - require(!shouldFail, "MockTarget/fallback-failed"); - } -} + // Mainnet addresses + address constant SPARK_CONTROLLER = 0xE52d643B27601D4d2BAB2052f30cf936ed413cec; + address constant SPARK_PROXY = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; + uint256 constant FORK_BLOCK = 24250000; + bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); -contract IntegrationTest is Test { + // Fetched from controller + address public SPARK_RATE_LIMITS; PASInstance public pas; BeamState public beamState; @@ -89,8 +72,6 @@ contract IntegrationTest is Test { address public canceller; address public pauser; address public cBeam; // Mock cBeam address - address public rateLimits; // Mock rate limits address - address public controller; // Mock controller address uint256 public constant MIN_DELAY = 1 days; bytes32 public constant SALT = keccak256("integration-test"); @@ -112,14 +93,18 @@ contract IntegrationTest is Test { event ProposalSubmitted(bytes32 indexed operationId, string functionName); function setUp() public { + // Fork mainnet at specified block + vm.createSelectFork(vm.envString("ETH_RPC_URL"), FORK_BLOCK); + + // Fetch SPARK_RATE_LIMITS from controller + SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); + deployer = address(this); owner = address(0x1); coreCouncil = address(0x2); canceller = address(0x3); pauser = address(0x4); cBeam = address(0x5); - rateLimits = address(0x6); - controller = address(0x7); // Deploy using PASDeploy pas = PASDeploy.deploy(deployer, owner, MIN_DELAY); @@ -222,27 +207,27 @@ contract IntegrationTest is Test { function testCoreCouncilCanDelRateLimits() public { // Setup: owner adds rateLimits vm.prank(owner); - beamState.addRateLimits(rateLimits); - assertEq(beamState.rateLimits(rateLimits), 1, "rateLimits should be added"); + beamState.addRateLimits(SPARK_RATE_LIMITS); + assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1, "rateLimits should be added"); // CoreCouncil can delete (role 2) vm.prank(coreCouncil); - beamState.delRateLimits(rateLimits); + beamState.delRateLimits(SPARK_RATE_LIMITS); - assertEq(beamState.rateLimits(rateLimits), 0, "rateLimits should be deleted"); + assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 0, "rateLimits should be deleted"); } function testCoreCouncilCanDelController() public { // Setup: owner adds controller vm.prank(owner); - beamState.addController(controller); - assertEq(beamState.controllers(controller), 1, "controller should be added"); + beamState.addController(SPARK_CONTROLLER); + assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "controller should be added"); // CoreCouncil can delete (role 2) vm.prank(coreCouncil); - beamState.delController(controller); + beamState.delController(SPARK_CONTROLLER); - assertEq(beamState.controllers(controller), 0, "controller should be deleted"); + assertEq(beamState.controllers(SPARK_CONTROLLER), 0, "controller should be deleted"); } function testCoreCouncilCanDelCBeam() public { @@ -260,36 +245,36 @@ contract IntegrationTest is Test { function testCoreCouncilCanSetAndUnsetCBeamForRateLimits() public { // Setup: owner adds rateLimits and cBeam, then sets association vm.startPrank(owner); - beamState.addRateLimits(rateLimits); + beamState.addRateLimits(SPARK_RATE_LIMITS); beamState.addCBeam(cBeam); vm.stopPrank(); vm.prank(coreCouncil); - beamState.setCBeamForRateLimits(rateLimits, cBeam); - assertEq(beamState.rateLimitsCBeams(rateLimits, cBeam), 1, "cBeam should be set"); + beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); + assertEq(beamState.rateLimitsCBeams(SPARK_RATE_LIMITS, cBeam), 1, "cBeam should be set"); // CoreCouncil can unset association (role 2) vm.prank(coreCouncil); - beamState.unsetCBeamForRateLimits(rateLimits, cBeam); - assertEq(beamState.rateLimitsCBeams(rateLimits, cBeam), 0, "cBeam should be unset for rateLimits"); + beamState.unsetCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); + assertEq(beamState.rateLimitsCBeams(SPARK_RATE_LIMITS, cBeam), 0, "cBeam should be unset for rateLimits"); } function testCoreCouncilCanSetAndUnsetCBeamForController() public { // Setup: owner adds controller and cBeam, then sets association vm.startPrank(owner); - beamState.addController(controller); + beamState.addController(SPARK_CONTROLLER); beamState.addCBeam(cBeam); vm.stopPrank(); vm.prank(coreCouncil); - beamState.setCBeamForController(controller, cBeam); - assertEq(beamState.controllersCBeams(controller, cBeam), 1, "cBeam should be set"); + beamState.setCBeamForController(SPARK_CONTROLLER, cBeam); + assertEq(beamState.controllersCBeams(SPARK_CONTROLLER, cBeam), 1, "cBeam should be set"); // CoreCouncil can unset association (role 2) vm.prank(coreCouncil); - beamState.unsetCBeamForController(controller, cBeam); + beamState.unsetCBeamForController(SPARK_CONTROLLER, cBeam); - assertEq(beamState.controllersCBeams(controller, cBeam), 0, "cBeam should be unset for controller"); + assertEq(beamState.controllersCBeams(SPARK_CONTROLLER, cBeam), 0, "cBeam should be unset for controller"); } function testCoreCouncilCanDelInitRateLimits() public { @@ -297,16 +282,16 @@ contract IntegrationTest is Test { // Setup: owner adds init rate limits vm.prank(owner); - beamState.addInitRateLimits(key, rateLimits, 1_000 ether, 100 ether); + beamState.addInitRateLimits(key, SPARK_RATE_LIMITS, 1_000 ether, 100 ether); - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, rateLimits); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, SPARK_RATE_LIMITS); assertEq(limits.maxAmount, 1_000 ether, "maxAmount should be set"); assertEq(limits.slope, 100 ether, "slope should be set"); // CoreCouncil can delete (role 2) vm.prank(coreCouncil); - beamState.delInitRateLimits(key, rateLimits); - limits = beamState.getInitRateLimits(key, rateLimits); + beamState.delInitRateLimits(key, SPARK_RATE_LIMITS); + limits = beamState.getInitRateLimits(key, SPARK_RATE_LIMITS); assertEq(limits.maxAmount, 0, "maxAmount should be deleted"); assertEq(limits.slope, 0, "slope should be deleted"); } @@ -316,14 +301,14 @@ contract IntegrationTest is Test { // Setup: owner adds init controller action vm.prank(owner); - bytes32 key = beamState.addInitControllerActions(actionData, controller); + bytes32 key = beamState.addInitControllerActions(actionData, SPARK_CONTROLLER); - assertTrue(beamState.isControllerActionEnabled(key, controller), "action should be enabled"); + assertTrue(beamState.isControllerActionEnabled(key, SPARK_CONTROLLER), "action should be enabled"); // CoreCouncil can delete (role 2) vm.prank(coreCouncil); - beamState.delInitControllerActions(key, controller); - assertFalse(beamState.isControllerActionEnabled(key, controller), "action should be disabled"); + beamState.delInitControllerActions(key, SPARK_CONTROLLER); + assertFalse(beamState.isControllerActionEnabled(key, SPARK_CONTROLLER), "action should be disabled"); } function testNonAuthorizedCannotDoRole2Actions() public { @@ -423,11 +408,11 @@ contract IntegrationTest is Test { uint256 slope = 100 ether; _scheduleAndExecute( - abi.encodeWithSelector(BeamState.addInitRateLimits.selector, key, rateLimits, maxAmount, slope), + abi.encodeWithSelector(BeamState.addInitRateLimits.selector, key, SPARK_RATE_LIMITS, maxAmount, slope), keccak256("addInitRateLimits") ); - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, rateLimits); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, SPARK_RATE_LIMITS); assertEq(limits.maxAmount, maxAmount, "maxAmount should be set via timelock"); assertEq(limits.slope, slope, "slope should be set via timelock"); } @@ -436,12 +421,12 @@ contract IntegrationTest is Test { bytes memory actionData = abi.encodeWithSignature("someAction(uint256)", 42); _scheduleAndExecute( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, actionData, controller), + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, actionData, SPARK_CONTROLLER), keccak256("addInitControllerActions") ); bytes32 key = keccak256(actionData); - assertTrue(beamState.isControllerActionEnabled(key, controller), "action should be enabled via timelock"); + assertTrue(beamState.isControllerActionEnabled(key, SPARK_CONTROLLER), "action should be enabled via timelock"); } // ============================================================================ @@ -557,7 +542,7 @@ contract IntegrationTest is Test { function testOperationsWithPredecessor() public { // Schedule first operation: addRateLimits - bytes memory payload1 = abi.encodeWithSelector(BeamState.addRateLimits.selector, rateLimits); + bytes memory payload1 = abi.encodeWithSelector(BeamState.addRateLimits.selector, SPARK_RATE_LIMITS); address[] memory targets = new address[](1); targets[0] = address(beamState); uint256[] memory values = new uint256[](1); @@ -569,7 +554,7 @@ contract IntegrationTest is Test { bytes32 id1 = timelock.hashOperationBatch(targets, values, payloads1, bytes32(0), keccak256("op1")); // Schedule second operation: addController with predecessor - bytes memory payload2 = abi.encodeWithSelector(BeamState.addController.selector, controller); + bytes memory payload2 = abi.encodeWithSelector(BeamState.addController.selector, SPARK_CONTROLLER); bytes[] memory payloads2 = new bytes[](1); payloads2[0] = payload2; @@ -584,11 +569,11 @@ contract IntegrationTest is Test { // Execute first timelock.executeBatch(targets, values, payloads1, bytes32(0), keccak256("op1")); - assertEq(beamState.rateLimits(rateLimits), 1, "rateLimits should be added"); + assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1, "rateLimits should be added"); // Now execute second timelock.executeBatch(targets, values, payloads2, id1, keccak256("op2")); - assertEq(beamState.controllers(controller), 1, "controller should be added"); + assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "controller should be added"); } // ============================================================================ @@ -602,11 +587,11 @@ contract IntegrationTest is Test { // 2. Schedule addRateLimits with predecessor vm.prank(coreCouncil); - bytes32 addRateLimitsOp = timelockWrapper.addRateLimits(rateLimits, addCBeamOp, keccak256("step2"), MIN_DELAY); + bytes32 addRateLimitsOp = timelockWrapper.addRateLimits(SPARK_RATE_LIMITS, addCBeamOp, keccak256("step2"), MIN_DELAY); // 3. Schedule setHop with predecessor vm.prank(coreCouncil); - bytes32 setHopOp = timelockWrapper.setHop(rateLimits, 4 hours, addRateLimitsOp, keccak256("step3"), MIN_DELAY); + bytes32 setHopOp = timelockWrapper.setHop(SPARK_RATE_LIMITS, 4 hours, addRateLimitsOp, keccak256("step3"), MIN_DELAY); // Wait for delay vm.warp(block.timestamp + MIN_DELAY); @@ -618,16 +603,16 @@ contract IntegrationTest is Test { Timelock.Operation memory op2 = timelock.getOperation(addRateLimitsOp); timelock.executeBatch(op2.targets, op2.values, op2.payloads, op2.predecessor, op2.salt); - assertEq(beamState.rateLimits(rateLimits), 1, "rateLimits added"); + assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1, "rateLimits added"); Timelock.Operation memory op3 = timelock.getOperation(setHopOp); timelock.executeBatch(op3.targets, op3.values, op3.payloads, op3.predecessor, op3.salt); - assertEq(beamState.getHop(rateLimits), 4 hours, "hop set"); + assertEq(beamState.getHop(SPARK_RATE_LIMITS), 4 hours, "hop set"); // 5. CoreCouncil can now directly set cBeam for rateLimits (role 2) vm.prank(coreCouncil); - beamState.setCBeamForRateLimits(rateLimits, cBeam); - assertEq(beamState.rateLimitsCBeams(rateLimits, cBeam), 1, "cBeam associated with rateLimits"); + beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); + assertEq(beamState.rateLimitsCBeams(SPARK_RATE_LIMITS, cBeam), 1, "cBeam associated with rateLimits"); // 6. Emergency stop by coreCouncil (direct, no timelock) vm.prank(coreCouncil); @@ -665,7 +650,7 @@ contract IntegrationTest is Test { } // ============================================================================ - // Post-Onboarding cBeam Operations Test + // Post-Onboarding cBeam Operations Test with Real Contracts // ============================================================================ function _scheduleDirect(bytes memory payload, bytes32 predecessor, bytes32 salt) internal returns (bytes32) { @@ -682,12 +667,15 @@ contract IntegrationTest is Test { } function testCBeamCanOperateAfterOnboarding() public { - // Deploy mock targets for rateLimits and controller - MockTarget mockRateLimits = new MockTarget(); - MockTarget mockController = new MockTarget(); - - bytes32 rateLimitKey = keccak256("test-rate-limit"); - bytes memory controllerAction = abi.encodeWithSelector(MockTarget.controllerFunction.selector, 42); + bytes32 rateLimitKey = keccak256("integration-test-key"); + + // Controller action: setMintRecipient for domain 6 + address testRecipient = address(0xDEADBEEF); + bytes memory setMintRecipientAction = abi.encodeWithSelector( + ControllerLike.setMintRecipient.selector, + uint32(6), // domain + bytes32(uint256(uint160(testRecipient))) + ); // ======================================== // Phase 1: Onboard via Timelock (Role 1) @@ -698,32 +686,32 @@ contract IntegrationTest is Test { vm.prank(coreCouncil); opIds[0] = timelockWrapper.addCBeam(cBeam, bytes32(0), keccak256("addCBeam"), MIN_DELAY); - // 1b. Schedule addRateLimits for mock target + // 1b. Schedule addRateLimits for SPARK_RATE_LIMITS vm.prank(coreCouncil); - opIds[1] = timelockWrapper.addRateLimits(address(mockRateLimits), opIds[0], keccak256("addRateLimits"), MIN_DELAY); + opIds[1] = timelockWrapper.addRateLimits(SPARK_RATE_LIMITS, opIds[0], keccak256("addRateLimits"), MIN_DELAY); - // 1c. Schedule addController for mock target + // 1c. Schedule addController for SPARK_CONTROLLER vm.prank(coreCouncil); - opIds[2] = timelockWrapper.addController(address(mockController), opIds[1], keccak256("addController"), MIN_DELAY); + opIds[2] = timelockWrapper.addController(SPARK_CONTROLLER, opIds[1], keccak256("addController"), MIN_DELAY); // 1d. Schedule setHop for rateLimits vm.prank(coreCouncil); - opIds[3] = timelockWrapper.setHop(address(mockRateLimits), 1 hours, opIds[2], keccak256("setHop"), MIN_DELAY); + opIds[3] = timelockWrapper.setHop(SPARK_RATE_LIMITS, 1 hours, opIds[2], keccak256("setHop"), MIN_DELAY); // 1e. Schedule setMaxChange for rateLimits vm.prank(coreCouncil); - opIds[4] = timelockWrapper.setMaxChange(address(mockRateLimits), 2 ether, opIds[3], keccak256("setMaxChange"), MIN_DELAY); + opIds[4] = timelockWrapper.setMaxChange(SPARK_RATE_LIMITS, 2 ether, opIds[3], keccak256("setMaxChange"), MIN_DELAY); // 1f. Schedule addInitRateLimits (via wrapper with RateLimitConfig) vm.prank(coreCouncil); opIds[5] = timelockWrapper.addInitRateLimits( - RateLimitConfig({key: rateLimitKey, rateLimits: address(mockRateLimits), maxAmount: 1_000 ether, slope: 100 ether}), + RateLimitConfig({key: rateLimitKey, rateLimits: SPARK_RATE_LIMITS, maxAmount: 1_000_000e18, slope: 100_000e18}), opIds[4], keccak256("addInitRateLimits"), MIN_DELAY ); // 1g. Schedule addInitControllerActions (directly via timelock - no wrapper for generic actions) opIds[6] = _scheduleDirect( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, controllerAction, address(mockController)), + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, setMintRecipientAction, SPARK_CONTROLLER), opIds[5], keccak256("addInitControllerActions") ); } @@ -739,46 +727,56 @@ contract IntegrationTest is Test { // Verify onboarding results assertEq(beamState.cBeams(cBeam), 1, "cBeam should be added"); - assertEq(beamState.rateLimits(address(mockRateLimits)), 1, "rateLimits should be added"); - assertEq(beamState.controllers(address(mockController)), 1, "controller should be added"); - assertEq(beamState.getHop(address(mockRateLimits)), 1 hours, "hop should be set"); - assertEq(beamState.getMaxChange(address(mockRateLimits)), 2 ether, "maxChange should be set"); + assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1, "rateLimits should be added"); + assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "controller should be added"); + assertEq(beamState.getHop(SPARK_RATE_LIMITS), 1 hours, "hop should be set"); + assertEq(beamState.getMaxChange(SPARK_RATE_LIMITS), 2 ether, "maxChange should be set"); { - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(rateLimitKey, address(mockRateLimits)); - assertEq(limits.maxAmount, 1_000 ether, "init rate limit maxAmount should be set"); - assertEq(limits.slope, 100 ether, "init rate limit slope should be set"); + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(rateLimitKey, SPARK_RATE_LIMITS); + assertEq(limits.maxAmount, 1_000_000e18, "init rate limit maxAmount should be set"); + assertEq(limits.slope, 100_000e18, "init rate limit slope should be set"); } - assertTrue(beamState.isControllerActionEnabled(keccak256(controllerAction), address(mockController)), "controller action should be enabled"); + assertTrue(beamState.isControllerActionEnabled(keccak256(setMintRecipientAction), SPARK_CONTROLLER), "controller action should be enabled"); // ======================================== - // Phase 2: Associate cBeam (Role 2 - direct) + // Phase 2: Grant admin role to configurator and associate cBeam (Role 2 - direct) // ======================================== + // Grant OZ_DEFAULT_ADMIN_ROLE to configurator on both controller and rate limits + // This allows configurator to call functions on these contracts + vm.startPrank(SPARK_PROXY); + ControllerLike(SPARK_CONTROLLER).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); + RateLimitsWithRolesLike(SPARK_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); + vm.stopPrank(); + vm.startPrank(coreCouncil); - beamState.setCBeamForRateLimits(address(mockRateLimits), cBeam); - beamState.setCBeamForController(address(mockController), cBeam); + beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); + beamState.setCBeamForController(SPARK_CONTROLLER, cBeam); vm.stopPrank(); - assertEq(beamState.rateLimitsCBeams(address(mockRateLimits), cBeam), 1, "cBeam should be associated with rateLimits"); - assertEq(beamState.controllersCBeams(address(mockController), cBeam), 1, "cBeam should be associated with controller"); + assertEq(beamState.rateLimitsCBeams(SPARK_RATE_LIMITS, cBeam), 1, "cBeam should be associated with rateLimits"); + assertEq(beamState.controllersCBeams(SPARK_CONTROLLER, cBeam), 1, "cBeam should be associated with controller"); // ======================================== // Phase 3: cBeam operates via Configurator // ======================================== - // 3a. cBeam sets rate limit (within default limits - no hop needed) + // 3a. cBeam sets rate limit vm.prank(cBeam); - configurator.setRateLimit(address(mockRateLimits), rateLimitKey, 500 ether, 50 ether); + configurator.setRateLimit(SPARK_RATE_LIMITS, rateLimitKey, 500_000e18, 50_000e18); { - RateLimitsLike.RateLimitData memory data = mockRateLimits.getRateLimitData(rateLimitKey); - assertEq(data.maxAmount, 500 ether, "rate limit maxAmount should be set by cBeam"); - assertEq(data.slope, 50 ether, "rate limit slope should be set by cBeam"); + RateLimitsLike.RateLimitData memory data = RateLimitsLike(SPARK_RATE_LIMITS).getRateLimitData(rateLimitKey); + assertEq(data.maxAmount, 500_000e18, "rate limit maxAmount should be set by cBeam on real contract"); + assertEq(data.slope, 50_000e18, "rate limit slope should be set by cBeam on real contract"); } // 3b. cBeam calls controller action vm.prank(cBeam); - configurator.callControllerAction(address(mockController), controllerAction); - assertEq(mockController.lastCallData(), controllerAction, "controller should have received the action"); + configurator.callControllerAction(SPARK_CONTROLLER, setMintRecipientAction); + + // Verify + bytes32 expectedRecipient = bytes32(uint256(uint160(testRecipient))); + assertEq(ControllerLike(SPARK_CONTROLLER).mintRecipients(6), expectedRecipient, "mintRecipient should be set on real controller"); // ======================================== // Phase 4: Verify restrictions @@ -788,18 +786,22 @@ contract IntegrationTest is Test { address unauthorizedCBeam = address(0x999); vm.prank(unauthorizedCBeam); vm.expectRevert("Configurator/not-authorized-ratelimits-cBeam"); - configurator.setRateLimit(address(mockRateLimits), rateLimitKey, 100 ether, 10 ether); + configurator.setRateLimit(SPARK_RATE_LIMITS, rateLimitKey, 100_000e18, 10_000e18); // 4b. Unauthorized cBeam cannot call controller action vm.prank(unauthorizedCBeam); vm.expectRevert("Configurator/not-authorized-controller-cBeam"); - configurator.callControllerAction(address(mockController), controllerAction); + configurator.callControllerAction(SPARK_CONTROLLER, setMintRecipientAction); // 4c. cBeam cannot call non-whitelisted action - bytes memory nonWhitelistedAction = abi.encodeWithSelector(MockTarget.controllerFunction.selector, 999); + bytes memory nonWhitelistedAction = abi.encodeWithSelector( + ControllerLike.setMintRecipient.selector, + uint32(7), // different domain + bytes32(uint256(uint160(testRecipient))) + ); vm.prank(cBeam); vm.expectRevert("Configurator/not-valid-data"); - configurator.callControllerAction(address(mockController), nonWhitelistedAction); + configurator.callControllerAction(SPARK_CONTROLLER, nonWhitelistedAction); // 4d. Operations blocked when stopped vm.prank(coreCouncil); @@ -807,11 +809,11 @@ contract IntegrationTest is Test { vm.prank(cBeam); vm.expectRevert("Configurator/stopped"); - configurator.setRateLimit(address(mockRateLimits), rateLimitKey, 400 ether, 40 ether); + configurator.setRateLimit(SPARK_RATE_LIMITS, rateLimitKey, 400_000e18, 40_000e18); vm.prank(cBeam); vm.expectRevert("Configurator/stopped"); - configurator.callControllerAction(address(mockController), controllerAction); + configurator.callControllerAction(SPARK_CONTROLLER, setMintRecipientAction); // 4e. Operations resume after restart (via timelock) vm.prank(coreCouncil); @@ -825,12 +827,12 @@ contract IntegrationTest is Test { assertFalse(beamState.stopped(), "system should be running"); - // cBeam can operate again + // cBeam can operate again on real contracts vm.prank(cBeam); - configurator.setRateLimit(address(mockRateLimits), rateLimitKey, 400 ether, 40 ether); + configurator.setRateLimit(SPARK_RATE_LIMITS, rateLimitKey, 400_000e18, 40_000e18); { - RateLimitsLike.RateLimitData memory data = mockRateLimits.getRateLimitData(rateLimitKey); - assertEq(data.maxAmount, 400 ether, "rate limit should be updated after restart"); + RateLimitsLike.RateLimitData memory data = RateLimitsLike(SPARK_RATE_LIMITS).getRateLimitData(rateLimitKey); + assertEq(data.maxAmount, 400_000e18, "rate limit should be updated after restart on real contract"); } } } From 01aa916bd208088ae2c74d0ece64d9192562ff78 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:33:45 -0300 Subject: [PATCH 069/150] Minor fixes Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- src/BeamState.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index b830c13..f772523 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -66,8 +66,8 @@ contract BeamState { event UnsetCBeamForRateLimits(address indexed rateLimits_, address indexed cBeam); event AddInitRateLimits(bytes32 indexed key, address indexed rateLimits_, uint256 maxAmount, uint256 slope); event DelInitRateLimits(bytes32 indexed key, address indexed rateLimits_); - event AddInitControllerActions(bytes32 indexed key, address indexed rateLimits_); - event DelInitControllerActions(bytes32 indexed key, address indexed rateLimits_); + event AddInitControllerActions(bytes32 indexed key, address indexed controller); + event DelInitControllerActions(bytes32 indexed key, address indexed controller); // --- Modifiers --- @@ -161,7 +161,7 @@ contract BeamState { emit Stop(); } - function start()external roleAuth { + function start() external roleAuth { stopped = false; emit Start(); } From 395bbc745234a7c6b793eb6974c350d813a1714e Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 21 Jan 2026 10:36:20 -0300 Subject: [PATCH 070/150] Add comments --- src/BeamState.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/BeamState.sol b/src/BeamState.sol index f772523..973c839 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -20,6 +20,9 @@ contract BeamState { // --- Storage variables --- + // Note: Some of the variables defined here are for controlling the actions of the Configurator. + // That's why they might not have a direct apparent reason within this contract itself. + mapping(address usr => uint256 allowed) public wards; mapping(address usr => bytes32 rolesData) public userRoles; mapping(bytes4 sig => bytes32 rolesData) public actionsRoles; @@ -183,6 +186,8 @@ contract BeamState { } function delRateLimits(address rateLimits_) external roleAuth { + // Note: it's a soft deprecation avoiding pairing it with new cBeams. + // Existing relationships need to be individually unset for full removal. rateLimits[rateLimits_] = 0; emit DelRateLimits(rateLimits_); } @@ -193,6 +198,8 @@ contract BeamState { } function delController(address controller) external roleAuth { + // Note: it's a soft deprecation avoiding pairing it with new cBeams. + // Existing relationships need to be individually unset for full removal. controllers[controller] = 0; emit DelController(controller); } @@ -203,6 +210,8 @@ contract BeamState { } function delCBeam(address cBeam) external roleAuth { + // Note: it's a soft deprecation avoiding pairing it with new rateLimits and controllers. + // Existing relationships need to be individually unset for full removal. cBeams[cBeam] = 0; emit DelCBeam(cBeam); } From f13d85dfed16d5d91b94907f8fb6ae18c066fa93 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 21 Jan 2026 13:16:03 -0300 Subject: [PATCH 071/150] Add keys to chainlog --- deploy/PASInit.sol | 8 +++ test/Integration.t.sol | 93 ++++++++++++++++------------- test/timelock/TimelockWrapper.t.sol | 36 +++++++---- 3 files changed, 86 insertions(+), 51 deletions(-) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index f165d1f..9655a74 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -16,6 +16,7 @@ pragma solidity >=0.8.0; +import { DssInstance } from "dss-test/MCD.sol"; import { PASInstance } from "./PASInstance.sol"; interface BeamStateLike { @@ -63,6 +64,7 @@ library PASInit { uint256 constant internal WAD = 10**18; function init( + DssInstance memory dss, PASInstance memory pasInstance, uint256 minDelay, address coreCouncil, @@ -121,5 +123,11 @@ library PASInit { timelock.grantRole(timelock.PAUSER_ROLE(), pausers[i]); } timelockWrapper.kiss(coreCouncil); + + // --- Chainlog --- + + dss.chainlog.setAddress("PAS_STATE", address(beamState)); + dss.chainlog.setAddress("PAS_CONFIGURATOR", address(configurator)); + dss.chainlog.setAddress("PAS_TIMELOCK", address(timelock)); } } diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 57399e1..64c5888 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -17,6 +17,7 @@ pragma solidity ^0.8.21; 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"; @@ -54,27 +55,28 @@ contract IntegrationTest is DssTest { // Mainnet addresses address constant SPARK_CONTROLLER = 0xE52d643B27601D4d2BAB2052f30cf936ed413cec; address constant SPARK_PROXY = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; + address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; uint256 constant FORK_BLOCK = 24250000; bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); // Fetched from controller - address public SPARK_RATE_LIMITS; + address SPARK_RATE_LIMITS; - PASInstance public pas; - BeamState public beamState; - Configurator public configurator; - Timelock public timelock; - TimelockWrapper public timelockWrapper; + DssInstance dss; + PASInstance pas; + BeamState beamState; + Configurator configurator; + Timelock timelock; + TimelockWrapper timelockWrapper; - address public deployer; - address public owner; // Timelock admin - address public coreCouncil; // Has role 2 on BeamState, proposer/canceller on Timelock - address public canceller; - address public pauser; - address public cBeam; // Mock cBeam address + address pauseProxy; // Timelock admin + address coreCouncil; // Has role 2 on BeamState, proposer/canceller on Timelock + address canceller; + address pauser; + address cBeam; // Mock cBeam address - uint256 public constant MIN_DELAY = 1 days; - bytes32 public constant SALT = keccak256("integration-test"); + uint256 constant MIN_DELAY = 1 days; + bytes32 constant SALT = keccak256("integration-test"); // Events event CallScheduled( @@ -96,18 +98,21 @@ contract IntegrationTest is DssTest { // Fork mainnet at specified block vm.createSelectFork(vm.envString("ETH_RPC_URL"), FORK_BLOCK); + // Load DssInstance from chainlog + dss = MCD.loadFromChainlog(CHAINLOG); + // Fetch SPARK_RATE_LIMITS from controller SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); - deployer = address(this); - owner = address(0x1); - coreCouncil = address(0x2); - canceller = address(0x3); - pauser = address(0x4); - cBeam = address(0x5); + pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); + + coreCouncil = address(0x1); + canceller = address(0x2); + pauser = address(0x3); + cBeam = address(0x4); // Deploy using PASDeploy - pas = PASDeploy.deploy(deployer, owner, MIN_DELAY); + pas = PASDeploy.deploy(address(this), pauseProxy, MIN_DELAY); // Cast to typed contracts beamState = BeamState(pas.beamState); @@ -115,15 +120,15 @@ contract IntegrationTest is DssTest { timelock = Timelock(payable(pas.timelock)); timelockWrapper = TimelockWrapper(pas.timelockWrapper); - // Initialize using PASInit (must be called by owner who has auth on BeamState and Timelock) + // Initialize using PASInit (must be called by pauseProxy who has auth on BeamState and Timelock) address[] memory cancellers = new address[](1); cancellers[0] = canceller; address[] memory pausers = new address[](1); pausers[0] = pauser; - vm.startPrank(owner); - PASInit.init(pas, MIN_DELAY, coreCouncil, cancellers, pausers); + vm.startPrank(pauseProxy); + PASInit.init(dss, pas, MIN_DELAY, coreCouncil, cancellers, pausers); vm.stopPrank(); } @@ -147,8 +152,14 @@ contract IntegrationTest is DssTest { assertEq(address(timelockWrapper.beamState()), address(beamState), "wrapper should reference beamState"); } + function testChainlogEntriesAfterInit() public view { + assertEq(dss.chainlog.getAddress("PAS_STATE"), address(beamState), "PAS_STATE should be set in chainlog"); + assertEq(dss.chainlog.getAddress("PAS_CONFIGURATOR"), address(configurator), "PAS_CONFIGURATOR should be set in chainlog"); + assertEq(dss.chainlog.getAddress("PAS_TIMELOCK"), address(timelock), "PAS_TIMELOCK should be set in chainlog"); + } + function testTimelockRolesAfterInit() public view { - assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), owner), "owner should be admin"); + assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), pauseProxy), "pauseProxy should be admin"); assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should be proposer"); assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)), "wrapper should be proposer"); assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), coreCouncil), "coreCouncil should be canceller"); @@ -205,8 +216,8 @@ contract IntegrationTest is DssTest { } function testCoreCouncilCanDelRateLimits() public { - // Setup: owner adds rateLimits - vm.prank(owner); + // Setup: pauseProxy adds rateLimits + vm.prank(pauseProxy); beamState.addRateLimits(SPARK_RATE_LIMITS); assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1, "rateLimits should be added"); @@ -218,8 +229,8 @@ contract IntegrationTest is DssTest { } function testCoreCouncilCanDelController() public { - // Setup: owner adds controller - vm.prank(owner); + // Setup: pauseProxy adds controller + vm.prank(pauseProxy); beamState.addController(SPARK_CONTROLLER); assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "controller should be added"); @@ -231,8 +242,8 @@ contract IntegrationTest is DssTest { } function testCoreCouncilCanDelCBeam() public { - // Owner adds cBeam directly (has auth/wards) - vm.prank(owner); + // pauseProxy adds cBeam directly (has auth/wards) + vm.prank(pauseProxy); beamState.addCBeam(cBeam); assertEq(beamState.cBeams(cBeam), 1, "cBeam should be added"); @@ -243,8 +254,8 @@ contract IntegrationTest is DssTest { } function testCoreCouncilCanSetAndUnsetCBeamForRateLimits() public { - // Setup: owner adds rateLimits and cBeam, then sets association - vm.startPrank(owner); + // Setup: pauseProxy adds rateLimits and cBeam, then sets association + vm.startPrank(pauseProxy); beamState.addRateLimits(SPARK_RATE_LIMITS); beamState.addCBeam(cBeam); vm.stopPrank(); @@ -260,8 +271,8 @@ contract IntegrationTest is DssTest { } function testCoreCouncilCanSetAndUnsetCBeamForController() public { - // Setup: owner adds controller and cBeam, then sets association - vm.startPrank(owner); + // Setup: pauseProxy adds controller and cBeam, then sets association + vm.startPrank(pauseProxy); beamState.addController(SPARK_CONTROLLER); beamState.addCBeam(cBeam); vm.stopPrank(); @@ -280,8 +291,8 @@ contract IntegrationTest is DssTest { function testCoreCouncilCanDelInitRateLimits() public { bytes32 key = keccak256("test-rate-limit-key"); - // Setup: owner adds init rate limits - vm.prank(owner); + // Setup: pauseProxy adds init rate limits + vm.prank(pauseProxy); beamState.addInitRateLimits(key, SPARK_RATE_LIMITS, 1_000 ether, 100 ether); BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, SPARK_RATE_LIMITS); @@ -299,8 +310,8 @@ contract IntegrationTest is DssTest { function testCoreCouncilCanDelInitControllerActions() public { bytes memory actionData = abi.encodeWithSignature("someAction(uint256)", 42); - // Setup: owner adds init controller action - vm.prank(owner); + // Setup: pauseProxy adds init controller action + vm.prank(pauseProxy); bytes32 key = beamState.addInitControllerActions(actionData, SPARK_CONTROLLER); assertTrue(beamState.isControllerActionEnabled(key, SPARK_CONTROLLER), "action should be enabled"); @@ -521,7 +532,7 @@ contract IntegrationTest is DssTest { vm.prank(pauser); timelock.pause(); - vm.prank(owner); + vm.prank(pauseProxy); timelock.unpause(); assertFalse(timelock.paused(), "timelock should be unpaused"); @@ -637,7 +648,7 @@ contract IntegrationTest is DssTest { function testAdminCanChangeDelayImmediately() public { uint256 newDelay = 2 days; - vm.prank(owner); + vm.prank(pauseProxy); timelock.updateDelayImmediately(newDelay); assertEq(timelock.getMinDelay(), newDelay, "delay should be updated immediately"); diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol index 24ea358..4748b8c 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapper.t.sol @@ -17,6 +17,7 @@ pragma solidity ^0.8.21; import "dss-test/DssTest.sol"; +import { MCD, DssInstance } from "dss-test/MCD.sol"; import { TimelockWrapper, RateLimitConfig } from "src/timelock/TimelockWrapper.sol"; import { Timelock } from "src/timelock/Timelock.sol"; import { BeamState } from "src/BeamState.sol"; @@ -66,27 +67,35 @@ contract TimelockWrapperTest is DssTest { address constant GROVE_CONTROLLER = 0xfd9dEA9a8D5B955649579Af482DB7198A392A9F5; address constant GROVE_PROXY = 0x1369f7b2b38c76B6478c0f0E66D94923421891Ba; + address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; + uint256 constant FORK_BLOCK = 24250000; uint256 constant MIN_DELAY = 1 days; bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); + DssInstance dss; + // --- Fetched from controllers --- - address public SPARK_RATE_LIMITS; - address public GROVE_RATE_LIMITS; + address SPARK_RATE_LIMITS; + address GROVE_RATE_LIMITS; // --- PAS Instance --- - BeamState public beamState; - Configurator public configurator; - Timelock public timelock; - TimelockWrapper public wrapper; + BeamState beamState; + Configurator configurator; + Timelock timelock; + TimelockWrapper wrapper; - address public coreCouncil; - address public cBeam; + address pauseProxy; + address coreCouncil; + address cBeam; function setUp() public { vm.createSelectFork(vm.envString("ETH_RPC_URL"), FORK_BLOCK); + // Load DssInstance from chainlog + dss = MCD.loadFromChainlog(CHAINLOG); + // Fetch rate limits from controllers SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); GROVE_RATE_LIMITS = ControllerLike(GROVE_CONTROLLER).rateLimits(); @@ -94,8 +103,13 @@ contract TimelockWrapperTest is DssTest { coreCouncil = makeAddr("coreCouncil"); cBeam = makeAddr("cBeam"); - PASInstance memory pas = PASDeploy.deploy(address(this), address(this), MIN_DELAY); - PASInit.init(pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); + pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); + + PASInstance memory pas = PASDeploy.deploy(address(this), pauseProxy, MIN_DELAY); + + vm.startPrank(pauseProxy); + PASInit.init(dss, pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); + vm.stopPrank(); beamState = BeamState(pas.beamState); configurator = Configurator(pas.configurator); @@ -183,6 +197,7 @@ contract TimelockWrapperTest is DssTest { function testKissDiss() public { address usr = makeAddr("usr"); + vm.startPrank(pauseProxy); assertEq(wrapper.buds(usr), 0); vm.expectEmit(true, false, false, true); emit Kiss(usr); @@ -192,6 +207,7 @@ contract TimelockWrapperTest is DssTest { emit Diss(usr); wrapper.diss(usr); assertEq(wrapper.buds(usr), 0); + vm.stopPrank(); } function testAuthModifiers() public { From 895bafd4d4b60d658f1c86d8fa6bdccd093e3a46 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 21 Jan 2026 13:18:20 -0300 Subject: [PATCH 072/150] Fix solidity version to 0.8.24 --- deploy/PASDeploy.sol | 2 +- foundry.toml | 5 +++++ src/BeamState.sol | 2 +- src/Configurator.sol | 2 +- src/timelock/Timelock.sol | 2 +- src/timelock/TimelockKeeperJob.sol | 2 +- src/timelock/TimelockWrapper.sol | 2 +- test/BeamState.t.sol | 2 +- test/Configurator.t.sol | 2 +- test/Integration.t.sol | 2 +- test/timelock/Timelock.t.sol | 2 +- test/timelock/TimelockKeeperJob.t.sol | 2 +- test/timelock/TimelockWrapper.t.sol | 2 +- 13 files changed, 17 insertions(+), 12 deletions(-) diff --git a/deploy/PASDeploy.sol b/deploy/PASDeploy.sol index 26f459b..a6dc163 100644 --- a/deploy/PASDeploy.sol +++ b/deploy/PASDeploy.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import { ScriptTools } from "dss-test/ScriptTools.sol"; import { PASInstance } from "./PASInstance.sol"; diff --git a/foundry.toml b/foundry.toml index 25b918f..bb34125 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,5 +2,10 @@ src = "src" out = "out" libs = ["lib"] +solc = "0.8.24" +optimizer = true +optimizer_runs = 200 +evmVersion = "cancun" +verbosity = 1 # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/src/BeamState.sol b/src/BeamState.sol index 973c839..7e79daf 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; contract BeamState { diff --git a/src/Configurator.sol b/src/Configurator.sol index 4c68ff4..143bab5 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; interface BeamStateLike { function controllersCBeams(address, address) external view returns (uint256); diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index e17d78c..69a9cf1 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol"; import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol index 52b3ed8..df23f21 100644 --- a/src/timelock/TimelockKeeperJob.sol +++ b/src/timelock/TimelockKeeperJob.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; // TODO: import this from dss-cron/src/interfaces once porting this contract interface IJob { diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index 5e001bd..7d92f03 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; interface TimelockLike { function scheduleBatch( diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index ff2025e..130df91 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import "dss-test/DssTest.sol"; import { BeamState } from "../src/BeamState.sol"; diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 418f3f0..5478210 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import "dss-test/DssTest.sol"; import { Configurator, BeamStateLike, RateLimitsLike } from "../src/Configurator.sol"; diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 64c5888..4791eef 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import "dss-test/DssTest.sol"; import { MCD, DssInstance } from "dss-test/MCD.sol"; diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol index 786f7fc..412a365 100644 --- a/test/timelock/Timelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import { Test } from "forge-std/Test.sol"; import { Timelock } from "src/timelock/Timelock.sol"; diff --git a/test/timelock/TimelockKeeperJob.t.sol b/test/timelock/TimelockKeeperJob.t.sol index 3055397..6a24666 100644 --- a/test/timelock/TimelockKeeperJob.t.sol +++ b/test/timelock/TimelockKeeperJob.t.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import { Test } from "forge-std/Test.sol"; import { TimelockKeeperJob } from "src/timelock/TimelockKeeperJob.sol"; diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol index 4748b8c..2977ca9 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapper.t.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import "dss-test/DssTest.sol"; import { MCD, DssInstance } from "dss-test/MCD.sol"; From b49cead74db584db45657e2b23800507da8b5c9f Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:09:18 +0700 Subject: [PATCH 073/150] Remove TimelockKeeperJob as will be in dss-cron --- src/timelock/TimelockKeeperJob.sol | 96 ------ test/timelock/TimelockKeeperJob.t.sol | 431 -------------------------- 2 files changed, 527 deletions(-) delete mode 100644 src/timelock/TimelockKeeperJob.sol delete mode 100644 test/timelock/TimelockKeeperJob.t.sol diff --git a/src/timelock/TimelockKeeperJob.sol b/src/timelock/TimelockKeeperJob.sol deleted file mode 100644 index df23f21..0000000 --- a/src/timelock/TimelockKeeperJob.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-FileCopyrightText: © 2026 Dai Foundation -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.24; - -// TODO: import this from dss-cron/src/interfaces once porting this contract -interface IJob { - function work(bytes32 network, bytes calldata args) external; - function workable(bytes32 network) external returns (bool canWork, bytes memory args); -} - -interface SequencerLike { - function isMaster(bytes32 network) external view returns (bool); -} - -interface TimelockLike { - struct Operation { - address[] targets; - uint256[] values; - bytes[] payloads; - bytes32 predecessor; - bytes32 salt; - } - - function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) external view returns (bytes32 id); - function getOperation(bytes32 id) external view returns (Operation memory op); - function executeBatch( - address[] calldata targets, - uint256[] calldata values, - bytes[] calldata payloads, - bytes32 predecessor, - bytes32 salt - ) external payable; -} - -contract TimelockKeeperJob is IJob { - - SequencerLike public immutable sequencer; - TimelockLike public immutable timelock; - uint256 public immutable maxIterations; - - // --- Errors --- - error NotMaster(bytes32 network); - error NoExecutableOperation(); - - // --- Events --- - event Work(bytes32 indexed network, bytes32 indexed operationId); - - constructor(address _sequencer, address _timelock, uint256 _maxIterations) { - sequencer = SequencerLike(_sequencer); - timelock = TimelockLike(_timelock); - maxIterations = _maxIterations; - } - - function work(bytes32 network, bytes calldata) external override { - if (!sequencer.isMaster(network)) revert NotMaster(network); - - bytes32 id = timelock.getNextExecutableOperation(0, maxIterations); - if (id == bytes32(0)) revert NoExecutableOperation(); - - TimelockLike.Operation memory op = timelock.getOperation(id); - - // Assume that in case a proposal needs eth the timelock is pre-funded, or alternatively it is executed manually - timelock.executeBatch{value: 0}(op.targets, op.values, op.payloads, op.predecessor, op.salt); - - emit Work(network, id); - } - - function workable(bytes32 network) external override returns (bool, bytes memory) { - if (!sequencer.isMaster(network)) return (false, bytes("Network is not master")); - - bytes memory args = ""; - try this.work(network, args) { - // Work succeeds - return (true, args); - } catch { - // Can not work -- carry on - } - - return (false, bytes("No executable operation")); - } -} - diff --git a/test/timelock/TimelockKeeperJob.t.sol b/test/timelock/TimelockKeeperJob.t.sol deleted file mode 100644 index 6a24666..0000000 --- a/test/timelock/TimelockKeeperJob.t.sol +++ /dev/null @@ -1,431 +0,0 @@ -// SPDX-FileCopyrightText: © 2026 Dai Foundation -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.24; - -import { Test } from "forge-std/Test.sol"; -import { TimelockKeeperJob } from "src/timelock/TimelockKeeperJob.sol"; -import { Timelock } from "src/timelock/Timelock.sol"; - -contract MockSequencer { - mapping(bytes32 => bool) public masterNetworks; - - function setMaster(bytes32 network, bool isMaster_) external { - masterNetworks[network] = isMaster_; - } - - function isMaster(bytes32 network) external view returns (bool) { - return masterNetworks[network]; - } -} - -contract MockTarget { - uint256 public value; - bool public called; - - function setValue(uint256 _value) external { - value = _value; - called = true; - } - - function revertCall() external pure { - revert("MockTarget: revert"); - } - - function receiveEther() external payable { - called = true; - } - - // Fallback to accept empty payload calls - fallback() external payable { - called = true; - } - - receive() external payable { - called = true; - } -} - - -contract TimelockKeeperJobTest is Test { - TimelockKeeperJob public keeper; - Timelock public timelock; - MockSequencer public sequencer; - MockTarget public mockTarget; - - address public admin; - address public proposer; - - uint256 public constant MIN_DELAY = 1 days; - bytes32 public constant NETWORK = keccak256("mainnet"); - bytes32 public constant SALT = keccak256("test-salt"); - - event Work(bytes32 indexed network, bytes32 indexed operationId); - - function setUp() public { - admin = address(this); - proposer = address(0x2); - - // Deploy sequencer mock - sequencer = new MockSequencer(); - sequencer.setMaster(NETWORK, true); - - // Deploy timelock - timelock = new Timelock(MIN_DELAY, admin); - - // Grant proposer role after deployment - timelock.grantRole(timelock.PROPOSER_ROLE(), proposer); - - // Deploy keeper - keeper = new TimelockKeeperJob(address(sequencer), address(timelock), 100); - - // Deploy mock target - mockTarget = new MockTarget(); - - // Fund timelock for ETH operations - vm.deal(address(timelock), 100 ether); - } - - // ============================================================================ - // Helper Functions - // ============================================================================ - - function _schedule( - address target, - uint256 value, - bytes memory data, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) internal returns (bytes32) { - address[] memory targets = new address[](1); - targets[0] = target; - uint256[] memory values = new uint256[](1); - values[0] = value; - bytes[] memory payloads = new bytes[](1); - payloads[0] = data; - - vm.prank(proposer); - timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); - - return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); - } - - function _toArray(address addr) internal pure returns (address[] memory) { - address[] memory arr = new address[](1); - arr[0] = addr; - return arr; - } - - function _toArray(uint256 val) internal pure returns (uint256[] memory) { - uint256[] memory arr = new uint256[](1); - arr[0] = val; - return arr; - } - - function _toArrayBytes(bytes memory data) internal pure returns (bytes[] memory) { - bytes[] memory arr = new bytes[](1); - arr[0] = data; - return arr; - } - - function testConstructor() public view { - assertEq(address(keeper.sequencer()), address(sequencer), "Sequencer set"); - assertEq(address(keeper.timelock()), address(timelock), "Timelock set"); - assertEq(keeper.maxIterations(), 100, "maxIterations set"); - } - - // ============================================================================ - // Work Function Tests - // ============================================================================ - - function testWorkExecutesOperation() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - bytes32 opId = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - vm.expectEmit(true, true, false, false); - emit Work(NETWORK, opId); - keeper.work(NETWORK, ""); - - assertTrue(mockTarget.called(), "Target was called"); - assertEq(mockTarget.value(), 42, "Value was set"); - assertEq(timelock.getOperationCount(), 0, "Operation removed from queue"); - } - - function testWorkRevertsIfNotMaster() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - bytes32 wrongNetwork = keccak256("wrong-network"); - - vm.expectRevert(abi.encodeWithSelector(TimelockKeeperJob.NotMaster.selector, wrongNetwork)); - keeper.work(wrongNetwork, ""); - } - - function testWorkRevertsIfNoExecutableOperation() public { - // No operations scheduled - vm.expectRevert(TimelockKeeperJob.NoExecutableOperation.selector); - keeper.work(NETWORK, ""); - } - - function testWorkRevertsIfOperationNotReady() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - // Don't warp time - operation not ready - vm.expectRevert(TimelockKeeperJob.NoExecutableOperation.selector); - keeper.work(NETWORK, ""); - } - - function testWorkRevertsIfTargetReverts() public { - bytes memory data = abi.encodeWithSelector(MockTarget.revertCall.selector); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - vm.expectRevert("MockTarget: revert"); - keeper.work(NETWORK, ""); - } - - function testWorkMultipleOperationsExecutesFirst() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - - bytes32 opId1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); - _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("s2"), MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - vm.expectEmit(true, true, false, false); - emit Work(NETWORK, opId1); - keeper.work(NETWORK, ""); - - assertEq(mockTarget.value(), 1, "First operation executed"); - assertEq(timelock.getOperationCount(), 1, "One operation remaining"); - - // Execute second - keeper.work(NETWORK, ""); - assertEq(mockTarget.value(), 2, "Second operation executed"); - assertEq(timelock.getOperationCount(), 0, "No operations remaining"); - } - - function testWorkSkipsNotReadyOperations() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - - // First operation with longer delay (not ready yet) - _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), 10 days); - // Second operation with shorter delay (will be ready) - bytes32 opId2 = _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("s2"), MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // Keeper should skip first (not ready) and execute second - vm.expectEmit(true, true, false, false); - emit Work(NETWORK, opId2); - keeper.work(NETWORK, ""); - - assertEq(mockTarget.value(), 2, "Second operation executed"); - assertEq(timelock.getOperationCount(), 1, "First operation still pending"); - } - - function testWorkWithPredecessor() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - - bytes32 opId1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); - _schedule(address(mockTarget), 0, data2, opId1, keccak256("s2"), MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // First work executes first operation - keeper.work(NETWORK, ""); - assertEq(mockTarget.value(), 1, "First operation executed"); - - // Second work executes second operation (predecessor now done) - keeper.work(NETWORK, ""); - assertEq(mockTarget.value(), 2, "Second operation executed"); - } - - function testWorkRespectsMaxIterations() public { - // Schedule more than maxIterations operations, with the ready one beyond the limit - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - // Schedule 101 operations with long delay (not ready) - for (uint256 i = 0; i < 101; i++) { - vm.prank(proposer); - timelock.scheduleBatch( - _toArray(address(mockTarget)), - _toArray(uint256(0)), - _toArrayBytes(data), - bytes32(0), - keccak256(abi.encodePacked("notready", i)), - 10 days - ); - } - - // Schedule 1 ready operation (will be at index 101, beyond maxIterations=100) - vm.prank(proposer); - timelock.scheduleBatch( - _toArray(address(mockTarget)), - _toArray(uint256(0)), - _toArrayBytes(data), - bytes32(0), - keccak256("ready"), - MIN_DELAY - ); - - vm.warp(block.timestamp + MIN_DELAY); - - // Keeper should not find the operation because it's beyond maxIterations - vm.expectRevert(TimelockKeeperJob.NoExecutableOperation.selector); - keeper.work(NETWORK, ""); - } - - // ============================================================================ - // Workable Function Tests - // ============================================================================ - - function testWorkableReturnsFalseIfNotMaster() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - bytes32 wrongNetwork = keccak256("wrong-network"); - (bool canWork, bytes memory reason) = keeper.workable(wrongNetwork); - - assertFalse(canWork, "Should not be workable on wrong network"); - assertEq(string(reason), "Network is not master", "Correct reason"); - } - - function testWorkableReturnsFalseIfNoOperation() public { - (bool canWork, bytes memory reason) = keeper.workable(NETWORK); - - assertFalse(canWork, "Should not be workable with no operations"); - assertEq(string(reason), "No executable operation", "Correct reason"); - } - - function testWorkableReturnsFalseIfOperationNotReady() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - // Don't warp - not ready - (bool canWork, bytes memory reason) = keeper.workable(NETWORK); - - assertFalse(canWork, "Should not be workable when not ready"); - assertEq(string(reason), "No executable operation", "Correct reason"); - } - - function testWorkableReturnsTrueIfOperationReady() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // Use snapshot to prevent workable() from persisting state changes - uint256 snapshot = vm.snapshot(); - (bool canWork, bytes memory args) = keeper.workable(NETWORK); - vm.revertTo(snapshot); - - assertTrue(canWork, "Should be workable when ready"); - assertEq(args, "", "Args is empty"); - - // Verify state was not changed by workable check - assertFalse(mockTarget.called(), "Target should not be called by workable check"); - assertEq(timelock.getOperationCount(), 1, "Operation should still exist"); - } - - function testWorkableReturnsFalseIfTargetReverts() public { - bytes memory data = abi.encodeWithSelector(MockTarget.revertCall.selector); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - (bool canWork, bytes memory reason) = keeper.workable(NETWORK); - - assertFalse(canWork, "Should not be workable when target reverts"); - assertEq(string(reason), "No executable operation", "Correct reason"); - } - - // ============================================================================ - // ETH Handling Tests - // ============================================================================ - - /// Operations requiring ETH will fail via keeper since value is always 0 - function testWorkFailsIfOperationNeedsETHAndTimelockNotFunded() public { - // Clear timelock balance - vm.prank(address(timelock)); - (bool success,) = address(0xdead).call{value: address(timelock).balance}(""); - require(success); - - bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); - _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // Will fail because timelock has no ETH and keeper sends value: 0 - vm.expectRevert(); - keeper.work(NETWORK, ""); - } - - function testWorkSucceedsIfTimelockPreFunded() public { - // Timelock is already funded in setUp - bytes memory data = abi.encodeWithSelector(MockTarget.receiveEther.selector); - _schedule(address(mockTarget), 1 ether, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - keeper.work(NETWORK, ""); - - assertTrue(mockTarget.called(), "Target received ETH from pre-funded timelock"); - } - - // ============================================================================ - // Access Control Tests - // ============================================================================ - - function testAnyoneCanCallWork() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // Random address can call work - address randomCaller = address(0x12345); - vm.prank(randomCaller); - keeper.work(NETWORK, ""); - - assertTrue(mockTarget.called(), "Anyone can execute via keeper"); - } - - function testAnyoneCanCallWorkable() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - address randomCaller = address(0x12345); - vm.prank(randomCaller); - (bool canWork,) = keeper.workable(NETWORK); - - assertTrue(canWork, "Anyone can call workable"); - } -} From dfb6e4bb329c54d257d5b9297ce338d8f5e72db7 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:25:46 +0700 Subject: [PATCH 074/150] Add missing getNext* tests for 100% coverage --- test/timelock/Timelock.t.sol | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol index 412a365..a4592a2 100644 --- a/test/timelock/Timelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -1060,6 +1060,50 @@ contract TimelockTest is Test { assertEq(timelock.getNextExecutableOperation(index, 0), bytes32(0)); } + function testGetNextExecutableOperationSkipsPendingPredecessor() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + bytes memory data3 = abi.encodeWithSelector(MockTarget.setValue.selector, 3); + + vm.startPrank(proposer); + // Schedule id1 with long delay (won't be ready) + bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), 10 days); + // Schedule id2 with id1 as predecessor (ready but predecessor not done) + _schedule(address(mockTarget), 0, data2, id1, keccak256("s2"), MIN_DELAY); + // Schedule id3 with no predecessor (ready) + bytes32 id3 = _schedule(address(mockTarget), 0, data3, bytes32(0), keccak256("s3"), MIN_DELAY); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + // Should skip id1 (not ready), skip id2 (predecessor not done), return id3 + assertEq(timelock.getNextExecutableOperation(0, 0), id3); + } + + function testGetNextExecutableOperationWithDonePredecessor() public { + bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); + bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); + + // Schedule operation A (no predecessor) + vm.prank(proposer); + bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + + // Schedule operation B with A as predecessor + vm.prank(proposer); + bytes32 id2 = _schedule(address(mockTarget), 0, data2, id1, keccak256("s2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Before executing predecessor: should return id1 (id2's predecessor not done) + assertEq(timelock.getNextExecutableOperation(0, 0), id1); + + // Execute id1 (predecessor is now done) + _execute(address(mockTarget), 0, data1, bytes32(0), keccak256("s1")); + + // Now id2's predecessor is done, should return id2 + assertEq(timelock.getNextExecutableOperation(0, 0), id2); + } + function testGetOperationIndex() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); From 7fbf258ad4105069d7835ec26190a130f7ebe1d3 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 22 Jan 2026 10:06:00 -0300 Subject: [PATCH 075/150] Fix conditional --- src/Configurator.sol | 5 +++-- test/Configurator.t.sol | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Configurator.sol b/src/Configurator.sol index 143bab5..6a282f8 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -97,12 +97,13 @@ contract Configurator { emit SetRateLimit(rateLimits, key, type(uint256).max, 0); } else { RateLimitsLike.RateLimitData memory current = RateLimitsLike(rateLimits).getRateLimitData(key); - bool safe = maxAmount <= defMaxAmount && slope <= defSlope || maxAmount <= current.maxAmount && slope <= current.slope; + bool withinCurrent = maxAmount <= current.maxAmount && slope <= current.slope; + bool safe = maxAmount <= defMaxAmount && slope <= defSlope || withinCurrent; uint256 maxChange = beamState.getMaxChange(rateLimits); require(safe || block.timestamp >= zzz[rateLimits][key] + beamState.getHop(rateLimits), "Configurator/increment-too-soon"); require(safe || maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD require(safe || slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); - if (maxAmount >= current.maxAmount || slope >= current.slope) { + if (!withinCurrent) { zzz[rateLimits][key] = block.timestamp; } uint256 lastAmount = RateLimitsLike(rateLimits).getCurrentRateLimit(key); diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 5478210..12c9397 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -22,35 +22,35 @@ import { BeamState } from "../src/BeamState.sol"; // Mock TARGET contract implementing RateLimits interface contract MockTarget { - RateLimitsLike.RateLimitData public rateLimitData; - uint256 public currentRateLimit; + mapping(bytes32 key => RateLimitsLike.RateLimitData) public rateLimitData; + mapping(bytes32 key => uint256) public currentRateLimit; bool public shouldFail; bytes public lastCallData; function setRateLimitData( - bytes32, + bytes32 key, uint256 maxAmount, uint256 slope, uint256 lastAmount, uint256 lastUpdated ) external { - rateLimitData = RateLimitsLike.RateLimitData(maxAmount, slope, lastAmount, lastUpdated); + rateLimitData[key] = RateLimitsLike.RateLimitData(maxAmount, slope, lastAmount, lastUpdated); } - function setUnlimitedRateLimitData(bytes32) external { - rateLimitData = RateLimitsLike.RateLimitData(type(uint256).max, 0, type(uint256).max, block.timestamp); + function setUnlimitedRateLimitData(bytes32 key) external { + rateLimitData[key] = RateLimitsLike.RateLimitData(type(uint256).max, 0, type(uint256).max, block.timestamp); } - function getRateLimitData(bytes32) external view returns (RateLimitsLike.RateLimitData memory) { - return rateLimitData; + function getRateLimitData(bytes32 key) external view returns (RateLimitsLike.RateLimitData memory) { + return rateLimitData[key]; } - function getCurrentRateLimit(bytes32) external view returns (uint256) { - return currentRateLimit; + function getCurrentRateLimit(bytes32 key) external view returns (uint256) { + return currentRateLimit[key]; } - function setCurrentRateLimit(uint256 _current) external { - currentRateLimit = _current; + function setCurrentRateLimit(bytes32 key, uint256 _current) external { + currentRateLimit[key] = _current; } function setShouldFail(bool _fail) external { @@ -121,7 +121,7 @@ contract ConfiguratorTest is DssTest { uint256 lastUpdated ) internal { target.setRateLimitData(key, maxAmount, slope, lastAmount, lastUpdated); - target.setCurrentRateLimit(lastAmount); + target.setCurrentRateLimit(key, lastAmount); } function _setupDefaultRateLimits(bytes32 key, address target, uint256 maxAmount, uint256 slope) internal { @@ -400,7 +400,7 @@ contract ConfiguratorTest is DssTest { _setupCBeam(address(target1), CBEAM1); _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); _setupRateLimitData(target1, key, 2_000 * WAD, 20 * WAD, 1_500 * WAD, block.timestamp); - target1.setCurrentRateLimit(1_500 * WAD); + target1.setCurrentRateLimit(key, 1_500 * WAD); // Decrease maxAmount below current lastAmount vm.prank(CBEAM1); @@ -415,7 +415,7 @@ contract ConfiguratorTest is DssTest { _setupCBeam(address(target1), CBEAM1); _setupDefaultRateLimits(key, address(target1), 2_000 * WAD, 20 * WAD); _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 500 * WAD, block.timestamp); - target1.setCurrentRateLimit(500 * WAD); + target1.setCurrentRateLimit(key, 500 * WAD); // Increase maxAmount vm.prank(CBEAM1); From 4c18ff786c8145f165e57f88bbfedda2f550bf50 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 22 Jan 2026 10:20:41 -0300 Subject: [PATCH 076/150] Revert when passing different params to setRateLimit and default is unlimited --- src/Configurator.sol | 1 + test/Configurator.t.sol | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Configurator.sol b/src/Configurator.sol index 6a282f8..acbfc08 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -93,6 +93,7 @@ contract Configurator { function setRateLimit(address rateLimits, bytes32 key, uint256 maxAmount, uint256 slope) external notStopped authRateLimits(rateLimits) { (uint256 defMaxAmount, uint256 defSlope) = beamState.getInitRateLimits(key, rateLimits); if (defMaxAmount == type(uint256).max && defSlope == 0) { + require(maxAmount == type(uint256).max && slope == 0, "Configurator/unlimited-incorrect-params"); RateLimitsLike(rateLimits).setUnlimitedRateLimitData(key); emit SetRateLimit(rateLimits, key, type(uint256).max, 0); } else { diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 12c9397..2d68e69 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -382,17 +382,31 @@ contract ConfiguratorTest is DssTest { // Set defaults that allow unlimited for specific TARGET _setupDefaultRateLimits(key, address(target1), type(uint256).max, 0); - // Any value passed should result in unlimited due to special unlimited check + // Must pass unlimited values when defaults are unlimited vm.prank(CBEAM1); vm.expectEmit(); emit SetRateLimit(address(target1), key, type(uint256).max, 0); - configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); + configurator.setRateLimit(address(target1), key, type(uint256).max, 0); RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, type(uint256).max, "should set unlimited when defaults are unlimited"); assertEq(data.slope, 0, "slope should be 0 for unlimited"); } + function testRevertSetLimitedWhenDefaultsUnlimited() public { + bytes32 key = keccak256("unlimited-revert-key"); + _setupCBeam(address(target1), CBEAM1); + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + + // Set defaults to unlimited + _setupDefaultRateLimits(key, address(target1), type(uint256).max, 0); + + // Should revert when passing limited values with unlimited defaults + vm.prank(CBEAM1); + vm.expectRevert("Configurator/unlimited-incorrect-params"); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); + } + // --- LastAmount Capping Tests --- function testLastAmountCappedAtMaxAmount() public { From fd7ed09b302bb36b3564a1c0ae74487d350ea2e1 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 22 Jan 2026 10:57:24 -0300 Subject: [PATCH 077/150] Use enum for roles --- deploy/PASInit.sol | 52 ++++++++++++++++++++++----------------- test/Integration.t.sol | 56 +++++++++++++++++++++--------------------- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 9655a74..482a3da 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -63,6 +63,12 @@ interface TimelockWrapperLike { library PASInit { uint256 constant internal WAD = 10**18; + enum Role { + _UNSET, // 0 (unused) + DELAYED, // 1 + IMMEDIATE // 2 + } + function init( DssInstance memory dss, PASInstance memory pasInstance, @@ -85,29 +91,29 @@ library PASInit { // --- Configure BeamState --- - // Define Beam actions that are accessed through timelock (role 1) and directly (role 2) - beamState.setRoleAction(1, BeamStateLike.start.selector, true); - beamState.setRoleAction(1, BeamStateLike.setHop.selector, true); - beamState.setRoleAction(1, BeamStateLike.setMaxChange.selector, true); - beamState.setRoleAction(1, BeamStateLike.addRateLimits.selector, true); - beamState.setRoleAction(1, BeamStateLike.addController.selector, true); - beamState.setRoleAction(1, BeamStateLike.addCBeam.selector, true); - beamState.setRoleAction(1, BeamStateLike.addInitRateLimits.selector, true); - beamState.setRoleAction(1, BeamStateLike.addInitControllerActions.selector, true); - beamState.setRoleAction(2, BeamStateLike.stop.selector, true); - beamState.setRoleAction(2, BeamStateLike.delRateLimits.selector, true); - beamState.setRoleAction(2, BeamStateLike.delController.selector, true); - beamState.setRoleAction(2, BeamStateLike.delCBeam.selector, true); - beamState.setRoleAction(2, BeamStateLike.setCBeamForRateLimits.selector, true); - beamState.setRoleAction(2, BeamStateLike.unsetCBeamForRateLimits.selector, true); - beamState.setRoleAction(2, BeamStateLike.setCBeamForController.selector, true); - beamState.setRoleAction(2, BeamStateLike.unsetCBeamForController.selector, true); - beamState.setRoleAction(2, BeamStateLike.delInitRateLimits.selector, true); - beamState.setRoleAction(2, BeamStateLike.delInitControllerActions.selector, true); - - // Set timelock as the user with role 1 and coreCouncil as the one with role 2 - beamState.setUserRole(address(timelock), 1, true); - beamState.setUserRole(coreCouncil, 2, true); + // Define Beam actions that are accessed through timelock (DELAYED) and directly (IMMEDIATE) + beamState.setRoleAction(uint8(Role.DELAYED), BeamStateLike.start.selector, true); + beamState.setRoleAction(uint8(Role.DELAYED), BeamStateLike.setHop.selector, true); + beamState.setRoleAction(uint8(Role.DELAYED), BeamStateLike.setMaxChange.selector, true); + beamState.setRoleAction(uint8(Role.DELAYED), BeamStateLike.addRateLimits.selector, true); + beamState.setRoleAction(uint8(Role.DELAYED), BeamStateLike.addController.selector, true); + beamState.setRoleAction(uint8(Role.DELAYED), BeamStateLike.addCBeam.selector, true); + beamState.setRoleAction(uint8(Role.DELAYED), BeamStateLike.addInitRateLimits.selector, true); + beamState.setRoleAction(uint8(Role.DELAYED), BeamStateLike.addInitControllerActions.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.stop.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.delRateLimits.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.delController.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.delCBeam.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.setCBeamForRateLimits.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.unsetCBeamForRateLimits.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.setCBeamForController.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.unsetCBeamForController.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.delInitRateLimits.selector, true); + beamState.setRoleAction(uint8(Role.IMMEDIATE), BeamStateLike.delInitControllerActions.selector, true); + + // Set timelock as the user with DELAYED role and coreCouncil with IMMEDIATE role + beamState.setUserRole(address(timelock), uint8(Role.DELAYED), true); + beamState.setUserRole(coreCouncil, uint8(Role.IMMEDIATE), true); // --- Configure Timelock and Wrapper --- diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 4791eef..9a83402 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -70,7 +70,7 @@ contract IntegrationTest is DssTest { TimelockWrapper timelockWrapper; address pauseProxy; // Timelock admin - address coreCouncil; // Has role 2 on BeamState, proposer/canceller on Timelock + address coreCouncil; // Has IMMEDIATE role on BeamState, proposer/canceller on Timelock address canceller; address pauser; address cBeam; // Mock cBeam address @@ -168,34 +168,34 @@ contract IntegrationTest is DssTest { } function testBeamStateRolesAfterInit() public view { - // Timelock has role 1 - assertTrue(beamState.hasUserRole(address(timelock), 1), "timelock should have role 1"); - // CoreCouncil has role 2 - assertTrue(beamState.hasUserRole(coreCouncil, 2), "coreCouncil should have role 2"); + // Timelock has DELAYED role + assertTrue(beamState.hasUserRole(address(timelock), uint8(PASInit.Role.DELAYED)), "timelock should have DELAYED role"); + // CoreCouncil has IMMEDIATE role + assertTrue(beamState.hasUserRole(coreCouncil, uint8(PASInit.Role.IMMEDIATE)), "coreCouncil should have IMMEDIATE role"); } function testBeamStateActionsConfigured() public view { - // Role 1 actions (timelocked) - assertTrue(beamState.isActionInRole(BeamState.start.selector, 1), "start should be role 1"); - assertTrue(beamState.isActionInRole(BeamState.setHop.selector, 1), "setHop should be role 1"); - assertTrue(beamState.isActionInRole(BeamState.setMaxChange.selector, 1), "setMaxChange should be role 1"); - assertTrue(beamState.isActionInRole(BeamState.addRateLimits.selector, 1), "addRateLimits should be role 1"); - assertTrue(beamState.isActionInRole(BeamState.addController.selector, 1), "addController should be role 1"); - assertTrue(beamState.isActionInRole(BeamState.addCBeam.selector, 1), "addCBeam should be role 1"); - assertTrue(beamState.isActionInRole(BeamState.addInitRateLimits.selector, 1), "addInitRateLimits should be role 1"); - assertTrue(beamState.isActionInRole(BeamState.addInitControllerActions.selector, 1), "addInitControllerActions should be role 1"); - - // Role 2 actions (direct) - assertTrue(beamState.isActionInRole(BeamState.stop.selector, 2), "stop should be role 2"); - assertTrue(beamState.isActionInRole(BeamState.delRateLimits.selector, 2), "delRateLimits should be role 2"); - assertTrue(beamState.isActionInRole(BeamState.delController.selector, 2), "delController should be role 2"); - assertTrue(beamState.isActionInRole(BeamState.delCBeam.selector, 2), "delCBeam should be role 2"); - assertTrue(beamState.isActionInRole(BeamState.setCBeamForRateLimits.selector, 2), "setCBeamForRateLimits should be role 2"); - assertTrue(beamState.isActionInRole(BeamState.unsetCBeamForRateLimits.selector, 2), "unsetCBeamForRateLimits should be role 2"); - assertTrue(beamState.isActionInRole(BeamState.setCBeamForController.selector, 2), "setCBeamForController should be role 2"); - assertTrue(beamState.isActionInRole(BeamState.unsetCBeamForController.selector, 2), "unsetCBeamForController should be role 2"); - assertTrue(beamState.isActionInRole(BeamState.delInitRateLimits.selector, 2), "delInitRateLimits should be role 2"); - assertTrue(beamState.isActionInRole(BeamState.delInitControllerActions.selector, 2), "delInitControllerActions should be role 2"); + // DELAYED role actions (timelocked) + assertTrue(beamState.isActionInRole(BeamState.start.selector, uint8(PASInit.Role.DELAYED)), "start should be DELAYED"); + assertTrue(beamState.isActionInRole(BeamState.setHop.selector, uint8(PASInit.Role.DELAYED)), "setHop should be DELAYED"); + assertTrue(beamState.isActionInRole(BeamState.setMaxChange.selector, uint8(PASInit.Role.DELAYED)), "setMaxChange should be DELAYED"); + assertTrue(beamState.isActionInRole(BeamState.addRateLimits.selector, uint8(PASInit.Role.DELAYED)), "addRateLimits should be DELAYED"); + assertTrue(beamState.isActionInRole(BeamState.addController.selector, uint8(PASInit.Role.DELAYED)), "addController should be DELAYED"); + assertTrue(beamState.isActionInRole(BeamState.addCBeam.selector, uint8(PASInit.Role.DELAYED)), "addCBeam should be DELAYED"); + assertTrue(beamState.isActionInRole(BeamState.addInitRateLimits.selector, uint8(PASInit.Role.DELAYED)), "addInitRateLimits should be DELAYED"); + assertTrue(beamState.isActionInRole(BeamState.addInitControllerActions.selector, uint8(PASInit.Role.DELAYED)), "addInitControllerActions should be DELAYED"); + + // IMMEDIATE role actions (direct) + assertTrue(beamState.isActionInRole(BeamState.stop.selector, uint8(PASInit.Role.IMMEDIATE)), "stop should be IMMEDIATE"); + assertTrue(beamState.isActionInRole(BeamState.delRateLimits.selector, uint8(PASInit.Role.IMMEDIATE)), "delRateLimits should be IMMEDIATE"); + assertTrue(beamState.isActionInRole(BeamState.delController.selector, uint8(PASInit.Role.IMMEDIATE)), "delController should be IMMEDIATE"); + assertTrue(beamState.isActionInRole(BeamState.delCBeam.selector, uint8(PASInit.Role.IMMEDIATE)), "delCBeam should be IMMEDIATE"); + assertTrue(beamState.isActionInRole(BeamState.setCBeamForRateLimits.selector, uint8(PASInit.Role.IMMEDIATE)), "setCBeamForRateLimits should be IMMEDIATE"); + assertTrue(beamState.isActionInRole(BeamState.unsetCBeamForRateLimits.selector, uint8(PASInit.Role.IMMEDIATE)), "unsetCBeamForRateLimits should be IMMEDIATE"); + assertTrue(beamState.isActionInRole(BeamState.setCBeamForController.selector, uint8(PASInit.Role.IMMEDIATE)), "setCBeamForController should be IMMEDIATE"); + assertTrue(beamState.isActionInRole(BeamState.unsetCBeamForController.selector, uint8(PASInit.Role.IMMEDIATE)), "unsetCBeamForController should be IMMEDIATE"); + assertTrue(beamState.isActionInRole(BeamState.delInitRateLimits.selector, uint8(PASInit.Role.IMMEDIATE)), "delInitRateLimits should be IMMEDIATE"); + assertTrue(beamState.isActionInRole(BeamState.delInitControllerActions.selector, uint8(PASInit.Role.IMMEDIATE)), "delInitControllerActions should be IMMEDIATE"); } function testTimelockWrapperBudsAfterInit() public view { @@ -203,7 +203,7 @@ contract IntegrationTest is DssTest { } // ============================================================================ - // CoreCouncil Direct Actions (Role 2) Tests + // CoreCouncil Direct Actions (IMMEDIATE Role) Tests // ============================================================================ function testCoreCouncilCanStop() public { @@ -331,7 +331,7 @@ contract IntegrationTest is DssTest { } // ============================================================================ - // Timelock Flow Tests (Role 1 Actions) + // Timelock Flow Tests (DELAYED Role Actions) // ============================================================================ function _scheduleAndExecute(bytes memory payload, bytes32 salt) internal { From 37f1a7cf1de637639f4de249de9e9147a66cb9c4 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 22 Jan 2026 14:49:50 -0300 Subject: [PATCH 078/150] Reorder some tests --- test/BeamState.t.sol | 242 +++++++++++++++++++++---------------------- 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 130df91..3159a87 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -185,6 +185,82 @@ contract BeamStateTest is DssTest { beamState.setHop(TARGET1, 100); } + // --- Stop/Start Tests --- + + function testStop() public { + assertFalse(beamState.stopped(), "BeamState should not be stopped initially"); + + vm.expectEmit(); + emit Stop(); + beamState.stop(); + + assertTrue(beamState.stopped(), "BeamState should be stopped after calling stop"); + } + + function testStopRoleAuth() public { + beamState.setRoleAction(7, beamState.stop.selector, true); + beamState.setUserRole(USER1, 7, true); + + vm.prank(USER1); + beamState.stop(); + + assertTrue(beamState.stopped(), "BeamState should be stopped by role user"); + } + + function testStopNotAuthorized() public { + vm.prank(USER1); + vm.expectRevert("BeamState/role-not-authorized"); + beamState.stop(); + } + + function testStart() public { + beamState.stop(); + assertTrue(beamState.stopped(), "BeamState should be stopped"); + + vm.expectEmit(); + emit Start(); + beamState.start(); + + assertFalse(beamState.stopped(), "BeamState should not be stopped after calling start"); + } + + function testStartRoleAuth() public { + beamState.stop(); + + beamState.setRoleAction(7, beamState.start.selector, true); + beamState.setUserRole(USER1, 7, true); + + vm.prank(USER1); + beamState.start(); + + assertFalse(beamState.stopped(), "BeamState should be started by role user"); + } + + function testStartNotAuthorized() public { + beamState.stop(); + + vm.prank(USER1); + vm.expectRevert("BeamState/role-not-authorized"); + beamState.start(); + } + + function testMultipleStopCalls() public { + beamState.stop(); + assertTrue(beamState.stopped(), "BeamState should be stopped"); + + // Calling stop again should not revert + beamState.stop(); + assertTrue(beamState.stopped(), "BeamState should still be stopped"); + } + + function testMultipleStartCalls() public { + assertFalse(beamState.stopped(), "BeamState should not be stopped initially"); + + // Calling start when not stopped should not revert + beamState.start(); + assertFalse(beamState.stopped(), "BeamState should still not be stopped"); + } + // --- Hop Configuration Tests --- function testSetHop() public { @@ -273,51 +349,6 @@ contract BeamStateTest is DssTest { assertEq(beamState.maxChange(TARGET1), 2 * WAD, "maxChange should be set by role user"); } - // --- cBeam Management Tests --- - - function testAddCBeam() public { - assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should not be registered initially"); - - vm.expectEmit(); - emit AddCBeam(CBEAM1); - beamState.addCBeam(CBEAM1); - - assertEq(beamState.cBeams(CBEAM1), 1, "CBEAM1 should be registered"); - } - - function testAddCBeamRoleAuth() public { - beamState.setRoleAction(3, beamState.addCBeam.selector, true); - beamState.setUserRole(USER1, 3, true); - - vm.prank(USER1); - beamState.addCBeam(CBEAM1); - - assertEq(beamState.cBeams(CBEAM1), 1, "CBEAM1 should be registered by role user"); - } - - function testDelCBeam() public { - beamState.addCBeam(CBEAM1); - assertEq(beamState.cBeams(CBEAM1), 1, "CBEAM1 should be registered"); - - vm.expectEmit(); - emit DelCBeam(CBEAM1); - beamState.delCBeam(CBEAM1); - - assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should not be registered after delete"); - } - - function testDelCBeamRoleAuth() public { - beamState.addCBeam(CBEAM1); - - beamState.setRoleAction(3, beamState.delCBeam.selector, true); - beamState.setUserRole(USER1, 3, true); - - vm.prank(USER1); - beamState.delCBeam(CBEAM1); - - assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should be deleted by role user"); - } - // --- RateLimits Management Tests --- function testAddRateLimits() public { @@ -424,6 +455,51 @@ contract BeamStateTest is DssTest { assertEq(beamState.controllers(TARGET2), 1, "TARGET2 should be registered"); } + // --- cBeam Management Tests --- + + function testAddCBeam() public { + assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should not be registered initially"); + + vm.expectEmit(); + emit AddCBeam(CBEAM1); + beamState.addCBeam(CBEAM1); + + assertEq(beamState.cBeams(CBEAM1), 1, "CBEAM1 should be registered"); + } + + function testAddCBeamRoleAuth() public { + beamState.setRoleAction(3, beamState.addCBeam.selector, true); + beamState.setUserRole(USER1, 3, true); + + vm.prank(USER1); + beamState.addCBeam(CBEAM1); + + assertEq(beamState.cBeams(CBEAM1), 1, "CBEAM1 should be registered by role user"); + } + + function testDelCBeam() public { + beamState.addCBeam(CBEAM1); + assertEq(beamState.cBeams(CBEAM1), 1, "CBEAM1 should be registered"); + + vm.expectEmit(); + emit DelCBeam(CBEAM1); + beamState.delCBeam(CBEAM1); + + assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should not be registered after delete"); + } + + function testDelCBeamRoleAuth() public { + beamState.addCBeam(CBEAM1); + + beamState.setRoleAction(3, beamState.delCBeam.selector, true); + beamState.setUserRole(USER1, 3, true); + + vm.prank(USER1); + beamState.delCBeam(CBEAM1); + + assertEq(beamState.cBeams(CBEAM1), 0, "CBEAM1 should be deleted by role user"); + } + // --- cBeam-RateLimits Mapping Tests --- function testSetCBeamForRateLimits() public { @@ -842,80 +918,4 @@ contract BeamStateTest is DssTest { assertEq(limits.maxAmount, type(uint256).max, "TARGET2 should fallback to global unlimited maxAmount"); assertEq(limits.slope, 0, "TARGET2 should fallback to global slope of 0 for unlimited"); } - - // --- Stop/Start Tests --- - - function testStop() public { - assertFalse(beamState.stopped(), "BeamState should not be stopped initially"); - - vm.expectEmit(); - emit Stop(); - beamState.stop(); - - assertTrue(beamState.stopped(), "BeamState should be stopped after calling stop"); - } - - function testStopRoleAuth() public { - beamState.setRoleAction(7, beamState.stop.selector, true); - beamState.setUserRole(USER1, 7, true); - - vm.prank(USER1); - beamState.stop(); - - assertTrue(beamState.stopped(), "BeamState should be stopped by role user"); - } - - function testStopNotAuthorized() public { - vm.prank(USER1); - vm.expectRevert("BeamState/role-not-authorized"); - beamState.stop(); - } - - function testStart() public { - beamState.stop(); - assertTrue(beamState.stopped(), "BeamState should be stopped"); - - vm.expectEmit(); - emit Start(); - beamState.start(); - - assertFalse(beamState.stopped(), "BeamState should not be stopped after calling start"); - } - - function testStartRoleAuth() public { - beamState.stop(); - - beamState.setRoleAction(7, beamState.start.selector, true); - beamState.setUserRole(USER1, 7, true); - - vm.prank(USER1); - beamState.start(); - - assertFalse(beamState.stopped(), "BeamState should be started by role user"); - } - - function testStartNotAuthorized() public { - beamState.stop(); - - vm.prank(USER1); - vm.expectRevert("BeamState/role-not-authorized"); - beamState.start(); - } - - function testMultipleStopCalls() public { - beamState.stop(); - assertTrue(beamState.stopped(), "BeamState should be stopped"); - - // Calling stop again should not revert - beamState.stop(); - assertTrue(beamState.stopped(), "BeamState should still be stopped"); - } - - function testMultipleStartCalls() public { - assertFalse(beamState.stopped(), "BeamState should not be stopped initially"); - - // Calling start when not stopped should not revert - beamState.start(); - assertFalse(beamState.stopped(), "BeamState should still not be stopped"); - } } From ae5803cbff2b4e3f5baab67e1400ed1f6e668be9 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 22 Jan 2026 15:40:24 -0300 Subject: [PATCH 079/150] Minor state test changes --- test/BeamState.t.sol | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 3159a87..c6862ca 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -690,6 +690,14 @@ contract BeamStateTest is DssTest { assertEq(limits.maxAmount, 200 * WAD, "should return TARGET1 specific maxAmount"); assertEq(limits.slope, 0, "should return TARGET1 specific slope (unlimited)"); + // Set specific values for TARGET1 (with maxAmount = 0) + beamState.addInitRateLimits(key, TARGET1, 0, 1 * WAD); + + // Should NOT fallback because slope is non-zero (only falls back when BOTH are 0) + limits = beamState.getInitRateLimits(key, TARGET1); + assertEq(limits.maxAmount, 0, "should return TARGET1 specific maxAmount (zero)"); + assertEq(limits.slope, 1 * WAD, "should return TARGET1 specific slope"); + // Test that fallback only happens when BOTH are zero beamState.addInitRateLimits(key, TARGET2, 0, 0); limits = beamState.getInitRateLimits(key, TARGET2); @@ -906,7 +914,7 @@ contract BeamStateTest is DssTest { // Set unlimited as global default beamState.addInitRateLimits(key, address(0), type(uint256).max, 0); - // Now set unlimited for specific TARGET (this now works with the fix!) + // Now set unlimited for specific TARGET beamState.addInitRateLimits(key, TARGET1, type(uint256).max, 0); BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(key, TARGET1); From fb87759bd8377326fc91ad449b03aaedc58216d5 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 22 Jan 2026 17:24:38 -0300 Subject: [PATCH 080/150] Fix some configurator tests --- test/Configurator.t.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 2d68e69..6922c9b 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -149,6 +149,7 @@ contract ConfiguratorTest is DssTest { RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 400 * WAD, "maxAmount should be updated by authorized cBeam"); + assertEq(data.slope, 4 * WAD, "slope should be updated by authorized cBeam"); } function testAuthCBeamCannotConfigureDifferentTarget() public { @@ -233,7 +234,7 @@ contract ConfiguratorTest is DssTest { function testSetRateLimitIncreasingAfterHop() public { bytes32 key = keccak256("after-hop-key"); _setupCBeam(address(target1), CBEAM1); - _setupDefaultRateLimits(key, address(target1), 3_000 * WAD, 30 * WAD); + _setupDefaultRateLimits(key, address(target1), 2_000 * WAD, 20 * WAD); _setupRateLimitData(target1, key, 2_000 * WAD, 20 * WAD, 2_000 * WAD, block.timestamp); beamState.setHop(address(target1), 3_600); // 1 hour hop beamState.setMaxChange(address(target1), 15 * WAD / 10); // 1.5x @@ -291,7 +292,7 @@ contract ConfiguratorTest is DssTest { function testSetRateLimitMaxChangeAtLimit() public { bytes32 key = keccak256("maxchange-exact-key"); _setupCBeam(address(target1), CBEAM1); - _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 100 * WAD); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 100 * WAD); _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); beamState.setHop(address(target1), 3_600); beamState.setMaxChange(address(target1), 15 * WAD / 10); // 1.5x @@ -338,9 +339,10 @@ contract ConfiguratorTest is DssTest { function testZzzTimestampUpdatedOnSecondIncrease() public { bytes32 key = keccak256("zzz-second-key"); _setupCBeam(address(target1), CBEAM1); - _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 100 * WAD); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 100 * WAD); _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); beamState.setHop(address(target1), 3_600); + vm.warp(block.timestamp + 3_600); uint256 firstTime = block.timestamp; vm.prank(CBEAM1); From 24a07f884cb2bca1c25475ab5f4d70400b130346 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 22 Jan 2026 18:08:46 -0300 Subject: [PATCH 081/150] More configurator tests changes --- test/Configurator.t.sol | 107 +++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 6922c9b..086f827 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -229,6 +229,14 @@ contract ConfiguratorTest is DssTest { vm.prank(CBEAM1); vm.expectRevert("Configurator/increment-too-soon"); configurator.setRateLimit(address(target1), key, 1_400 * WAD, 14 * WAD); + + // Fails also if one of the parameters is decreasing + vm.prank(CBEAM1); + vm.expectRevert("Configurator/increment-too-soon"); + configurator.setRateLimit(address(target1), key, 1_400 * WAD, 12 * WAD); + vm.prank(CBEAM1); + vm.expectRevert("Configurator/increment-too-soon"); + configurator.setRateLimit(address(target1), key, 1_200 * WAD, 14 * WAD); } function testSetRateLimitIncreasingAfterHop() public { @@ -276,7 +284,7 @@ contract ConfiguratorTest is DssTest { function testSetRateLimitMaxChangeEnforcedSlope() public { bytes32 key = keccak256("maxchange-slope-key"); _setupCBeam(address(target1), CBEAM1); - _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 10 * WAD); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); _setupRateLimitData(target1, key, 1_000 * WAD, 8 * WAD, 1_000 * WAD, block.timestamp); beamState.setHop(address(target1), 1); // Minimal hop delay beamState.setMaxChange(address(target1), 15 * WAD / 10); // 1.5x @@ -310,24 +318,10 @@ contract ConfiguratorTest is DssTest { // --- Timestamp Tracking Tests --- - function testZzzTimestampSetOnIncrease() public { - bytes32 key = keccak256("zzz-key"); - _setupCBeam(address(target1), CBEAM1); - _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 100 * WAD); - _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); - - uint256 timeBefore = block.timestamp; - - vm.prank(CBEAM1); - configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); - - assertEq(configurator.zzz(address(target1), key), timeBefore, "zzz should be set to current timestamp"); - } - function testZzzTimestampNotSetOnDecrease() public { bytes32 key = keccak256("zzz-decrease-key"); _setupCBeam(address(target1), CBEAM1); - _setupDefaultRateLimits(key, address(target1), 1_0000 * WAD, 100 * WAD); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 100 * WAD); _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); vm.prank(CBEAM1); @@ -514,6 +508,73 @@ contract ConfiguratorTest is DssTest { configurator.setRateLimit(address(target1), key, 1_500 * WAD, 9 * WAD); } + function testSpecificHopUsedOverGlobal() public { + bytes32 key = keccak256("specific-hop-key"); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + + // Set specific hop shorter than global (1 hour vs 1 day) + beamState.setHop(address(target1), 3_600); + + assertGt(beamState.hop(address(0)), 3_600, "global hop should be greater than 3_600"); + assertEq(beamState.hop(address(target1)), 3_600, "TARGET1 specific hop should be 3600"); + assertEq(beamState.getHop(address(target1)), 3_600, "should use specific hop"); + + vm.warp(block.timestamp + 3_600); // Warp to specific hop (shorter than global) + + // First increase beyond defaults (unsafe) + vm.prank(CBEAM1); + configurator.setRateLimit(address(target1), key, 1_100 * WAD, 11 * WAD); + + // Try second increase immediately - should fail due to specific hop + vm.prank(CBEAM1); + vm.expectRevert("Configurator/increment-too-soon"); + configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); + + // Wait for specific hop period (not the global 86_400) + vm.warp(block.timestamp + 3_600); + + // Should succeed after specific hop period + vm.prank(CBEAM1); + configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); + + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); + assertEq(data.maxAmount, 1_200 * WAD, "maxAmount should increase after specific hop"); + assertEq(data.slope, 12 * WAD, "slope should increase after specific hop"); + } + + function testSpecificMaxChangeUsedOverGlobal() public { + bytes32 key = keccak256("specific-maxchange-key"); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 900 * WAD, 9 * WAD, 900 * WAD, block.timestamp); + + // Set specific maxChange higher than global (2x vs 1.5x) + beamState.setMaxChange(address(target1), 2 * WAD); + + assertLt(beamState.maxChange(address(0)), 2 * WAD, "global maxChange should be less than 2x"); + assertEq(beamState.maxChange(address(target1)), 2 * WAD, "TARGET1 specific maxChange should be 2x"); + assertEq(beamState.getMaxChange(address(target1)), 2 * WAD, "should use specific maxChange"); + + vm.warp(block.timestamp + 86_400); // Wait for hop + + // Increase by 1.67x - would fail with global 1.5x, but succeeds with specific 2x + vm.prank(CBEAM1); + configurator.setRateLimit(address(target1), key, 1_500 * WAD, 15 * WAD); + + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); + assertEq(data.maxAmount, 1_500 * WAD, "should allow 1.67x increase with specific 2x maxChange"); + assertEq(data.slope, 15 * WAD, "should allow 1.67x increase with specific 2x slope"); + + vm.warp(block.timestamp + 86_400); // Wait for next hop + + // Try to exceed the specific maxChange (2x) - should fail + vm.prank(CBEAM1); + vm.expectRevert("Configurator/maxChange-maxAmount"); + configurator.setRateLimit(address(target1), key, 3_100 * WAD, 15 * WAD); // >2x from 1500 + } + // --- Controller Action Tests --- function testCallControllerAction() public { @@ -587,20 +648,6 @@ contract ConfiguratorTest is DssTest { configurator.callControllerAction(address(target1), data); } - function testCallControllerActionArbitraryData() public { - bytes memory data = abi.encodeWithSignature("someOtherFunction(address,uint256)", address(this), 999); - - _setupCBeam(address(target1), CBEAM1); - beamState.addInitControllerActions(data, address(target1)); - - vm.prank(CBEAM1); - vm.expectEmit(); - emit CallControllerAction(address(target1), data); - configurator.callControllerAction(address(target1), data); - - assertEq(target1.lastCallData(), data, "arbitrary data should be passed correctly"); - } - // --- Combined functions Tests --- function testFullRateLimitWorkflow() public { From 72ff9f766de9c6ce2a537e448a8c8cb9c6e2d700 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:43:14 +0700 Subject: [PATCH 082/150] Small comment addition --- src/timelock/TimelockWrapper.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index 7d92f03..f761e5e 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -79,7 +79,7 @@ struct RateLimitConfig { // Notes: // - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). // - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. -// - The actual downstream changes only take effect when cBEAMs use the BEAMState configurations, so atomicity in configurations can not be assumed. +// - The actual downstream changes only take effect when cBEAMs use the BEAMState configurations, so atomicity in configurations can not be assumed (which is a known issue). // - As part of a controller onboarding it might need to be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. contract TimelockWrapper { // --- Auth --- From b7514b90840113adfaacde1dbaf067c5e0e4d0af Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:43:34 +0700 Subject: [PATCH 083/150] Addr README --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d894f9 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# PAS - Permissioned Action System + +PAS is a governance framework for managing rate-limited operations through authorized actors called cBeams. It provides timelocked proposal execution, configurable rate limits, and role-based access control for interacting with external controllers. + +## Contracts + +### BeamState + +Central registry that manages system configuration and access control. Stores which cBeams are authorized to operate on which rate limit contracts and controllers, defines default rate limits and allowed controller actions, and configures parameters like `hop` (minimum time between increases) and `maxChange` (maximum rate of change). Supports role-based permissions with actions split between timelocked and direct access. + +### Configurator + +The operational interface used by cBeams to modify rate limits and execute controller actions. Enforces that rate limit changes respect the configured ceilings and requires waiting for `hop` between increases. Also gates controller calls through pre-approved action hashes stored in BeamState. + +### Timelock + +Extended OpenZeppelin TimelockController with pausing support, permissionless execution, and operation tracking for keeper integration. Disables self-calls to prevent proposals from modifying admin settings. Provides helper functions (`getNextExecutableOperation`, `getOperationIndex`) for automated execution via keeper jobs. + +### TimelockWrapper + +Convenience contract for proposers to schedule batched BeamState configuration changes through the timelock. Provides type-safe wrappers for common operations like adding rate limits, controllers, cBeams, and their initial configurations. Authorized users (`buds`) can schedule proposals that execute after the timelock delay. From c4d8dd1971e22aa44d43ae1dff56276c35023f1c Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:44:26 +0700 Subject: [PATCH 084/150] Remove ROLES.md and ROLES-TIMELOCK.md --- ROLES-TIMELOCK.md | 6 ------ ROLES.md | 34 ---------------------------------- 2 files changed, 40 deletions(-) delete mode 100644 ROLES-TIMELOCK.md delete mode 100644 ROLES.md diff --git a/ROLES-TIMELOCK.md b/ROLES-TIMELOCK.md deleted file mode 100644 index 7f68416..0000000 --- a/ROLES-TIMELOCK.md +++ /dev/null @@ -1,6 +0,0 @@ -## Timelock Intended Roles and Actors: - -- admin - Pause Proxy -- proposers (can also cancel by default) - Core Council -- (additional) cancellers - empty, or possibly add Mom and/or Emergency-Wallet -- pauser - Core Council, Mom/Emergency-Wallet \ No newline at end of file diff --git a/ROLES.md b/ROLES.md deleted file mode 100644 index b727bf4..0000000 --- a/ROLES.md +++ /dev/null @@ -1,34 +0,0 @@ -# Roles functions - -## PauseProxy - -- BeamState.* - -## Core Council timelocked - -- BeamState.start -- BeamState.setHop -- BeamState.setMaxChange -- BeamState.addRateLimits -- BeamState.addController -- BeamState.addCBeam -- BeamState.addInitRateLimits -- BeamState.addInitControllerActions - -## Core Council direct - -- BeamState.stop -- BeamState.delRateLimits -- BeamState.delController -- BeamState.delCBeam -- BeamState.setCBeamForRateLimits -- BeamState.unsetCBeamForRateLimits -- BeamState.setCBeamForController -- BeamState.unsetCBeamForController -- BeamState.delInitRateLimits -- BeamState.delInitControllerActions - -## Gov Ops - -- Configurator.setRateLimit -- Configurator.callControllerAction From 44411341a99bdb694e1755b28e95520498c360dd Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:41:13 -0300 Subject: [PATCH 085/150] Remove unnecessary things from configurator test Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- test/Configurator.t.sol | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 086f827..e75d656 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -63,11 +63,6 @@ contract MockTarget { require(!shouldFail, "MockTarget/controller-function-failed"); return value * 2; } - - fallback() external { - lastCallData = msg.data; - require(!shouldFail, "MockTarget/fallback-failed"); - } } contract ConfiguratorTest is DssTest { @@ -370,25 +365,6 @@ contract ConfiguratorTest is DssTest { assertEq(data.slope, 0, "slope should be 0"); } - function testSetUnlimitedFromLimited() public { - bytes32 key = keccak256("limited-to-unlimited-key"); - _setupCBeam(address(target1), CBEAM1); - _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); - - // Set defaults that allow unlimited for specific TARGET - _setupDefaultRateLimits(key, address(target1), type(uint256).max, 0); - - // Must pass unlimited values when defaults are unlimited - vm.prank(CBEAM1); - vm.expectEmit(); - emit SetRateLimit(address(target1), key, type(uint256).max, 0); - configurator.setRateLimit(address(target1), key, type(uint256).max, 0); - - RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); - assertEq(data.maxAmount, type(uint256).max, "should set unlimited when defaults are unlimited"); - assertEq(data.slope, 0, "slope should be 0 for unlimited"); - } - function testRevertSetLimitedWhenDefaultsUnlimited() public { bytes32 key = keccak256("unlimited-revert-key"); _setupCBeam(address(target1), CBEAM1); From b23f152f447b5677c0e4e6a46ac43dfed24e7699 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 26 Jan 2026 18:37:36 +0700 Subject: [PATCH 086/150] Add TimelockWrapperForeign (untested) --- src/timelock/TimelockWrapperForeign.sol | 465 ++++++++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 src/timelock/TimelockWrapperForeign.sol diff --git a/src/timelock/TimelockWrapperForeign.sol b/src/timelock/TimelockWrapperForeign.sol new file mode 100644 index 0000000..58fffad --- /dev/null +++ b/src/timelock/TimelockWrapperForeign.sol @@ -0,0 +1,465 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +interface TimelockLike { + function scheduleBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external; + + function hashOperationBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) external view returns (bytes32); +} + +interface BeamStateLike { + function start() external; + function setHop(address rateLimits_, uint256 value) external; + function setMaxChange(address rateLimits_, uint256 value) external; + function addRateLimits(address rateLimits_) external; + function addController(address controller) external; + function addCBeam(address cBeam) external; + function addInitRateLimits(bytes32 key, address rateLimits_, uint256 maxAmount, uint256 slope) external; + function addInitControllerActions(bytes calldata data, address controller) external returns (bytes32 key); +} + +interface ControllerLike { + // Shared functions (Spark v1.8.0 & Grove v1.8.0) + function grantRole(bytes32 role, address account) external; + function revokeRole(bytes32 role, address account) external; + function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; + function setLayerZeroRecipient(uint32 destinationEndpointId, bytes32 layerZeroRecipient) external; + function setMaxSlippage(address pool, uint256 maxSlippage) external; + function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; + // Grove-only functions (v1.8.0) + function setUniswapV3PoolMaxTickDelta(address pool, uint24 maxTickDelta) external; + function setUniswapV3AddLiquidityLowerTickBound(address pool, int24 lowerTickBound) external; + function setUniswapV3AddLiquidityUpperTickBound(address pool, int24 upperTickBound) external; + function setUniswapV3TwapSecondsAgo(address pool, uint32 twapSecondsAgo) external; + function setCentrifugeRecipient(uint16 centrifugeId, bytes32 recipient) external; + function setMerklDistributor(address merklDistributor) external; +} + +struct RateLimitConfig { + bytes32 key; + address rateLimits; + uint256 maxAmount; + uint256 slope; +} + +// Spark Foreign controller aligned to v1.8.0: https://github.com/sparkdotfi/spark-alm-controller/blob/7be959378fe48117f7a06796f94e240345428982/src/ForeignController.sol +// Grove Foreign controller aligned to v1.8.0: https://github.com/grove-labs/grove-alm-controller/blob/2c6e3d4297d5f244894d05f3dbbe47bcada34712/src/ForeignController.sol + +// Notes: +// - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). +// - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. +// - The actual downstream changes only take effect when cBEAMs use the BEAMState configurations, so atomicity in configurations can not be assumed (which is a known issue). +// - As part of a controller onboarding it might need to be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. +contract TimelockWrapperForeign { + // --- Auth --- + mapping(address => uint256) public wards; + mapping(address => uint256) public buds; + + modifier auth() { + require(wards[msg.sender] == 1, "TimelockWrapperForeign/not-authorized"); + _; + } + + modifier toll() { + require(buds[msg.sender] == 1, "TimelockWrapperForeign/not-whitelisted"); + _; + } + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + function kiss(address usr) external auth { + buds[usr] = 1; + emit Kiss(usr); + } + + function diss(address usr) external auth { + buds[usr] = 0; + emit Diss(usr); + } + + // --- Events --- + event Rely(address indexed usr); + event Deny(address indexed usr); + event Kiss(address indexed usr); + event Diss(address indexed usr); + event ProposalSubmitted(bytes32 indexed operationId, string functionName); + + TimelockLike public immutable timelock; + BeamStateLike public immutable beamState; + + constructor(address timelock_, address beamState_) { + timelock = TimelockLike(payable(timelock_)); + beamState = BeamStateLike(beamState_); + + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + function _submitProposal(bytes memory payload, bytes32 predecessor, bytes32 salt, uint256 delay) internal returns (bytes32 operationId) { + address[] memory targets = new address[](1); + targets[0] = address(beamState); + + uint256[] memory values = new uint256[](1); + values[0] = 0; + + bytes[] memory payloads = new bytes[](1); + payloads[0] = payload; + + operationId = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + } + + function start(bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.start.selector); + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "start"); + } + + function setHop(address rateLimits, uint256 hop, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.setHop.selector, rateLimits, hop); + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setHop"); + } + + function setMaxChange(address rateLimits, uint256 maxChange, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.setMaxChange.selector, rateLimits, maxChange); + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMaxChange"); + } + + function addRateLimits(address rateLimits, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addRateLimits.selector, rateLimits); + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "addRateLimits"); + } + + function addController(address controller, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addController.selector, controller); + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "addController"); + } + + function addCBeam(address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addCBeam.selector, cBeam); + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "addCBeam"); + } + + function addInitRateLimits( + RateLimitConfig calldata config, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory payload = abi.encodeWithSelector( + BeamStateLike.addInitRateLimits.selector, + config.key, + config.rateLimits, + config.maxAmount, + config.slope + ); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "addInitRateLimits"); + } + + function batchAddInitRateLimits( + RateLimitConfig[] calldata configs, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + uint256 len = configs.length; + + address[] memory targets = new address[](len); + uint256[] memory values = new uint256[](len); + bytes[] memory payloads = new bytes[](len); + + for (uint256 i = 0; i < len; ++i) { + targets[i] = address(beamState); + payloads[i] = abi.encodeWithSelector( + BeamStateLike.addInitRateLimits.selector, + configs[i].key, + configs[i].rateLimits, + configs[i].maxAmount, + configs[i].slope + ); + } + + operationId = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + + emit ProposalSubmitted(operationId, "batchAddInitRateLimits"); + } + + // --- Controller Actions --- + + // Spark functions + + // Role bytes32 can be computed off-chain and passed as parameter, e.g keccak256("RELAYER"), keccak256("FREEZER") + function grantRole( + address controller, + bytes32 role, + address account, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.grantRole.selector, + role, + account + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "grantRole"); + } + + function revokeRole( + address controller, + bytes32 role, + address account, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.revokeRole.selector, + role, + account + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "revokeRole"); + } + + function setMintRecipient( + uint32 destinationDomain, + bytes32 mintRecipient, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setMintRecipient.selector, + destinationDomain, + mintRecipient + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMintRecipient"); + } + + function setLayerZeroRecipient( + uint32 destinationEndpointId, + bytes32 layerZeroRecipient, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setLayerZeroRecipient.selector, + destinationEndpointId, + layerZeroRecipient + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setLayerZeroRecipient"); + } + + function setMaxSlippage( + address pool, + uint256 maxSlippage, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setMaxSlippage.selector, + pool, + maxSlippage + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMaxSlippage"); + } + + function setMaxExchangeRate( + address token, + uint256 shares, + uint256 maxExpectedAssets, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setMaxExchangeRate.selector, + token, + shares, + maxExpectedAssets + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMaxExchangeRate"); + } + + // Grove-only functions + + function setUniswapV3PoolMaxTickDelta( + address pool, + uint24 maxTickDelta, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setUniswapV3PoolMaxTickDelta.selector, + pool, + maxTickDelta + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV3PoolMaxTickDelta"); + } + + function setUniswapV3AddLiquidityLowerTickBound( + address pool, + int24 lowerTickBound, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setUniswapV3AddLiquidityLowerTickBound.selector, + pool, + lowerTickBound + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityLowerTickBound"); + } + + function setUniswapV3AddLiquidityUpperTickBound( + address pool, + int24 upperTickBound, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setUniswapV3AddLiquidityUpperTickBound.selector, + pool, + upperTickBound + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityUpperTickBound"); + } + + function setUniswapV3TwapSecondsAgo( + address pool, + uint32 twapSecondsAgo, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setUniswapV3TwapSecondsAgo.selector, + pool, + twapSecondsAgo + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV3TwapSecondsAgo"); + } + + function setCentrifugeRecipient( + uint16 centrifugeId, + bytes32 recipient, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setCentrifugeRecipient.selector, + centrifugeId, + recipient + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setCentrifugeRecipient"); + } + + function setMerklDistributor( + address merklDistributor, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setMerklDistributor.selector, + merklDistributor + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMerklDistributor"); + } +} From e6cbf6abbdf4ef7e943720945dc883bfff47ad1d Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:40:01 +0700 Subject: [PATCH 087/150] Merge pull request #2 from sky-ecosystem/mom Add PASMom --- README.md | 4 ++ deploy/PASDeploy.sol | 7 ++ deploy/PASInit.sol | 20 ++++++ deploy/PASInstance.sol | 1 + src/PASMom.sol | 106 ++++++++++++++++++++++++++++++ test/Integration.t.sol | 144 +++++++++++++++++++++++++++++++++++++++++ test/PASMom.t.sol | 141 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 423 insertions(+) create mode 100644 src/PASMom.sol create mode 100644 test/PASMom.t.sol diff --git a/README.md b/README.md index 1d894f9..3bcdba6 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,7 @@ Extended OpenZeppelin TimelockController with pausing support, permissionless ex ### TimelockWrapper Convenience contract for proposers to schedule batched BeamState configuration changes through the timelock. Provides type-safe wrappers for common operations like adding rate limits, controllers, cBeams, and their initial configurations. Authorized users (`buds`) can schedule proposals that execute after the timelock delay. + +### PASMom + +Emergency governance contract that allows authorized parties to trigger circuit breakers. Can call `stop()` on BeamState to halt Configurator operations and `pause()` on Timelock to block scheduling and execution. Callable by the owner or via the Chief's hat through the authority. diff --git a/deploy/PASDeploy.sol b/deploy/PASDeploy.sol index a6dc163..c006eb8 100644 --- a/deploy/PASDeploy.sol +++ b/deploy/PASDeploy.sol @@ -22,6 +22,7 @@ import { BeamState } from "src/BeamState.sol"; import { Configurator } from "src/Configurator.sol"; import { Timelock } from "src/timelock/Timelock.sol"; import { TimelockWrapper } from "src/timelock/TimelockWrapper.sol"; +import { PASMom } from "src/PASMom.sol"; library PASDeploy { @@ -44,7 +45,13 @@ library PASDeploy { pasInstance.beamState )); + pasInstance.mom = address(new PASMom( + pasInstance.beamState, + pasInstance.timelock + )); + ScriptTools.switchOwner(pasInstance.beamState, deployer, owner); ScriptTools.switchOwner(pasInstance.timelockWrapper, deployer, owner); + PASMom(pasInstance.mom).setOwner(owner); } } diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 482a3da..d4d63de 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -20,6 +20,7 @@ import { DssInstance } from "dss-test/MCD.sol"; import { PASInstance } from "./PASInstance.sol"; interface BeamStateLike { + function rely(address) external; function setUserRole(address, uint8, bool) external; function setRoleAction(uint8, bytes4, bool) external; function stop() external; @@ -60,6 +61,12 @@ interface TimelockWrapperLike { function kiss(address) external; } +interface PASMomLike { + function beamState() external view returns (address); + function timelock() external view returns (address); + function setAuthority(address) external; +} + library PASInit { uint256 constant internal WAD = 10**18; @@ -81,6 +88,7 @@ library PASInit { ConfiguratorLike configurator = ConfiguratorLike(pasInstance.configurator); TimelockLike timelock = TimelockLike(pasInstance.timelock); TimelockWrapperLike timelockWrapper = TimelockWrapperLike(pasInstance.timelockWrapper); + PASMomLike mom = PASMomLike(pasInstance.mom); // --- Sanity checks --- @@ -88,6 +96,8 @@ library PASInit { require(timelock.getMinDelay() == minDelay, "PASInit/timelock-minDelay-mismatch"); require(timelockWrapper.timelock() == address(timelock), "PASInit/wrapper-timelock-mismatch"); require(timelockWrapper.beamState() == address(beamState), "PASInit/wrapper-beamState-mismatch"); + require(mom.beamState() == address(beamState), "PASInit/mom-beamState-mismatch"); + require(mom.timelock() == address(timelock), "PASInit/mom-timelock-mismatch"); // --- Configure BeamState --- @@ -130,10 +140,20 @@ library PASInit { } timelockWrapper.kiss(coreCouncil); + // --- Configure Mom --- + + // Rely Mom on BeamState to call stop() + beamState.rely(address(mom)); + // Give Mom the PAUSER_ROLE to call pause() on Timelock + timelock.grantRole(timelock.PAUSER_ROLE(), address(mom)); + // Set Mom's authority to MCD_ADM + mom.setAuthority(dss.chainlog.getAddress("MCD_ADM")); + // --- Chainlog --- dss.chainlog.setAddress("PAS_STATE", address(beamState)); dss.chainlog.setAddress("PAS_CONFIGURATOR", address(configurator)); dss.chainlog.setAddress("PAS_TIMELOCK", address(timelock)); + dss.chainlog.setAddress("PAS_MOM", address(mom)); } } diff --git a/deploy/PASInstance.sol b/deploy/PASInstance.sol index b9ecd8e..77f299c 100644 --- a/deploy/PASInstance.sol +++ b/deploy/PASInstance.sol @@ -21,4 +21,5 @@ struct PASInstance { address configurator; address timelock; address timelockWrapper; + address mom; } diff --git a/src/PASMom.sol b/src/PASMom.sol new file mode 100644 index 0000000..4cebaa8 --- /dev/null +++ b/src/PASMom.sol @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +interface AuthorityLike { + function canCall(address src, address dst, bytes4 sig) external view returns (bool); +} + +interface BeamStateLike { + function stop() external; +} + +interface TimelockLike { + function pause() external; +} + +contract PASMom { + + // --- Storage variables --- + + address public owner; + address public authority; + + // --- Immutables --- + + BeamStateLike public immutable beamState; + TimelockLike public immutable timelock; + + // --- Events --- + + event SetOwner(address indexed newOwner); + event SetAuthority(address indexed newAuthority); + event StopBeamState(); + event PauseTimelock(); + + // --- Modifiers --- + + modifier onlyOwner() { + require(msg.sender == owner, "PASMom/not-owner"); + _; + } + + modifier auth() { + require(isAuthorized(msg.sender, msg.sig), "PASMom/not-authorized"); + _; + } + + // --- Constructor --- + + constructor(address beamState_, address timelock_) { + beamState = BeamStateLike(beamState_); + timelock = TimelockLike(timelock_); + + owner = msg.sender; + emit SetOwner(msg.sender); + } + + // --- Internal functions --- + + function isAuthorized(address src, bytes4 sig) internal view returns (bool) { + if (src == owner) { + return true; + } else if (authority != address(0)) { + return AuthorityLike(authority).canCall(src, address(this), sig); + } + return false; + } + + // --- Admin functions --- + + function setOwner(address owner_) external onlyOwner { + owner = owner_; + emit SetOwner(owner_); + } + + function setAuthority(address authority_) external onlyOwner { + authority = authority_; + emit SetAuthority(authority_); + } + + // --- Emergency functions --- + + function stop() external auth { + beamState.stop(); + emit StopBeamState(); + } + + function pause() external auth { + timelock.pause(); + emit PauseTimelock(); + } +} diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 9a83402..f656201 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -25,6 +25,11 @@ import { BeamState } from "src/BeamState.sol"; import { Configurator, RateLimitsLike } from "src/Configurator.sol"; import { Timelock } from "src/timelock/Timelock.sol"; import { TimelockWrapper, RateLimitConfig } from "src/timelock/TimelockWrapper.sol"; +import { PASMom } from "src/PASMom.sol"; + +interface ChiefLike { + function hat() external view returns (address); +} // Interface for SparkController interface ControllerLike { @@ -68,6 +73,8 @@ contract IntegrationTest is DssTest { Configurator configurator; Timelock timelock; TimelockWrapper timelockWrapper; + PASMom mom; + ChiefLike chief; address pauseProxy; // Timelock admin address coreCouncil; // Has IMMEDIATE role on BeamState, proposer/canceller on Timelock @@ -119,6 +126,8 @@ contract IntegrationTest is DssTest { configurator = Configurator(pas.configurator); timelock = Timelock(payable(pas.timelock)); timelockWrapper = TimelockWrapper(pas.timelockWrapper); + mom = PASMom(pas.mom); + chief = ChiefLike(dss.chainlog.getAddress("MCD_ADM")); // Initialize using PASInit (must be called by pauseProxy who has auth on BeamState and Timelock) address[] memory cancellers = new address[](1); @@ -141,6 +150,13 @@ contract IntegrationTest is DssTest { assertTrue(pas.configurator != address(0), "configurator should be deployed"); assertTrue(pas.timelock != address(0), "timelock should be deployed"); assertTrue(pas.timelockWrapper != address(0), "timelockWrapper should be deployed"); + assertTrue(pas.mom != address(0), "mom should be deployed"); + } + + function testMomLinkedCorrectly() public view { + assertEq(address(mom.beamState()), address(beamState), "mom should reference beamState"); + assertEq(address(mom.timelock()), address(timelock), "mom should reference timelock"); + assertEq(mom.owner(), pauseProxy, "mom should be owned by pauseProxy"); } function testConfiguratorLinkedToBeamState() public view { @@ -156,6 +172,14 @@ contract IntegrationTest is DssTest { assertEq(dss.chainlog.getAddress("PAS_STATE"), address(beamState), "PAS_STATE should be set in chainlog"); assertEq(dss.chainlog.getAddress("PAS_CONFIGURATOR"), address(configurator), "PAS_CONFIGURATOR should be set in chainlog"); assertEq(dss.chainlog.getAddress("PAS_TIMELOCK"), address(timelock), "PAS_TIMELOCK should be set in chainlog"); + assertEq(dss.chainlog.getAddress("PAS_MOM"), address(mom), "PAS_MOM should be set in chainlog"); + } + + function testMomConfigAfterInit() public view { + assertEq(mom.owner(), pauseProxy, "mom owner should be pauseProxy"); + assertEq(mom.authority(), address(chief), "mom authority should be MCD_ADM"); + assertEq(beamState.wards(address(mom)), 1, "mom should have wards on beamState"); + assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), address(mom)), "mom should have PAUSER_ROLE on timelock"); } function testTimelockRolesAfterInit() public view { @@ -660,6 +684,126 @@ contract IntegrationTest is DssTest { timelock.updateDelayImmediately(2 days); } + // ============================================================================ + // PASMom Integration Tests + // ============================================================================ + + function testMomOwnerCanStopBeamState() public { + assertFalse(beamState.stopped(), "should not be stopped initially"); + + vm.prank(pauseProxy); + mom.stop(); + + assertTrue(beamState.stopped(), "beamState should be stopped via mom"); + } + + function testMomOwnerCanPauseTimelock() public { + assertFalse(timelock.paused(), "timelock should not be paused initially"); + + vm.prank(pauseProxy); + mom.pause(); + + assertTrue(timelock.paused(), "timelock should be paused via mom"); + } + + function testMomHatCanStopBeamState() public { + address hat = chief.hat(); + assertFalse(beamState.stopped(), "should not be stopped initially"); + + vm.prank(hat); + mom.stop(); + + assertTrue(beamState.stopped(), "beamState should be stopped via mom by hat"); + } + + function testMomHatCanPauseTimelock() public { + address hat = chief.hat(); + assertFalse(timelock.paused(), "timelock should not be paused initially"); + + vm.prank(hat); + mom.pause(); + + assertTrue(timelock.paused(), "timelock should be paused via mom by hat"); + } + + function testMomUnauthorizedCannotStop() public { + address unauthorized = address(0x999); + + vm.prank(unauthorized); + vm.expectRevert("PASMom/not-authorized"); + mom.stop(); + } + + function testMomUnauthorizedCannotPause() public { + address unauthorized = address(0x999); + + vm.prank(unauthorized); + vm.expectRevert("PASMom/not-authorized"); + mom.pause(); + } + + function testMomEmergencyStopBlocksOperations() public { + // Setup: onboard a cBeam and associate it with rateLimits + bytes32 rateLimitKey = keccak256("mom-test-key"); + + vm.startPrank(pauseProxy); + beamState.addCBeam(cBeam); + beamState.addRateLimits(SPARK_RATE_LIMITS); + beamState.addInitRateLimits(rateLimitKey, SPARK_RATE_LIMITS, 1_000_000e18, 100_000e18); + vm.stopPrank(); + + vm.prank(coreCouncil); + beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); + + // Grant configurator admin role + vm.prank(SPARK_PROXY); + RateLimitsWithRolesLike(SPARK_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); + + // cBeam can operate normally + vm.prank(cBeam); + configurator.setRateLimit(SPARK_RATE_LIMITS, rateLimitKey, 500_000e18, 50_000e18); + + // Hat triggers emergency stop via mom + address hat = chief.hat(); + vm.prank(hat); + mom.stop(); + + // cBeam operations are now blocked + vm.prank(cBeam); + vm.expectRevert("Configurator/stopped"); + configurator.setRateLimit(SPARK_RATE_LIMITS, rateLimitKey, 400_000e18, 40_000e18); + } + + function testMomPauseBlocksTimelockScheduling() public { + // Hat pauses timelock via mom + address hat = chief.hat(); + vm.prank(hat); + mom.pause(); + + // Scheduling via wrapper is blocked + vm.prank(coreCouncil); + vm.expectRevert(); + timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + } + + function testMomPauseBlocksTimelockExecution() public { + // Schedule an operation first + vm.prank(coreCouncil); + bytes32 operationId = timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Hat pauses timelock via mom + address hat = chief.hat(); + vm.prank(hat); + mom.pause(); + + // Execution is blocked + Timelock.Operation memory op = timelock.getOperation(operationId); + vm.expectRevert(); + timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); + } + // ============================================================================ // Post-Onboarding cBeam Operations Test with Real Contracts // ============================================================================ diff --git a/test/PASMom.t.sol b/test/PASMom.t.sol new file mode 100644 index 0000000..330faa1 --- /dev/null +++ b/test/PASMom.t.sol @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +import "dss-test/DssTest.sol"; + +import { BeamState } from "src/BeamState.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; +import { PASMom } from "src/PASMom.sol"; + +contract MockChief { + address hat; + + constructor(address hat_) { + hat = hat_; + } + + function canCall(address caller, address, bytes4) external view returns (bool ok) { + ok = caller == hat; + } +} + +contract PASMomTest is DssTest { + address owner; + address hat; + MockChief chief; + BeamState beamState; + Timelock timelock; + PASMom mom; + + uint256 constant MIN_DELAY = 1 days; + + event SetOwner(address indexed newOwner); + event SetAuthority(address indexed newAuthority); + event StopBeamState(); + event PauseTimelock(); + + function setUp() public { + owner = makeAddr("owner"); + hat = makeAddr("hat"); + chief = new MockChief(hat); + + vm.startPrank(owner); + beamState = new BeamState(); + timelock = new Timelock(MIN_DELAY, owner); + mom = new PASMom(address(beamState), address(timelock)); + + beamState.rely(address(mom)); + timelock.grantRole(timelock.PAUSER_ROLE(), address(mom)); + mom.setAuthority(address(chief)); + vm.stopPrank(); + } + + function testConstructor() public { + vm.expectEmit(); + emit SetOwner(address(this)); + PASMom mom2 = new PASMom(address(beamState), address(timelock)); + + assertEq(address(mom2.beamState()), address(beamState)); + assertEq(address(mom2.timelock()), address(timelock)); + assertEq(mom2.owner(), address(this)); + } + + function testOnlyOwnerMethods() public { + checkModifier( + address(mom), "PASMom/not-owner", [PASMom.setOwner.selector, PASMom.setAuthority.selector] + ); + } + + function testAuthMethods() public { + checkModifier(address(mom), "PASMom/not-authorized", [PASMom.stop.selector, PASMom.pause.selector]); + + vm.prank(owner); + mom.setAuthority(address(0)); + checkModifier(address(mom), "PASMom/not-authorized", [PASMom.stop.selector, PASMom.pause.selector]); + } + + function testSetOwner() public { + vm.prank(owner); + vm.expectEmit(); + emit SetOwner(address(0x1234)); + mom.setOwner(address(0x1234)); + assertEq(mom.owner(), address(0x1234)); + } + + function testSetAuthority() public { + vm.prank(owner); + vm.expectEmit(); + emit SetAuthority(address(0x123)); + mom.setAuthority(address(0x123)); + assertEq(mom.authority(), address(0x123)); + } + + function _checkStop(address who) internal { + assertEq(beamState.stopped(), false); + vm.prank(who); + vm.expectEmit(); + emit StopBeamState(); + mom.stop(); + assertEq(beamState.stopped(), true); + } + + function testStopOwner() public { + _checkStop(owner); + } + + function testStopHat() public { + _checkStop(hat); + } + + function _checkPause(address who) internal { + assertEq(timelock.paused(), false); + vm.prank(who); + vm.expectEmit(); + emit PauseTimelock(); + mom.pause(); + assertEq(timelock.paused(), true); + } + + function testPauseOwner() public { + _checkPause(owner); + } + + function testPauseHat() public { + _checkPause(hat); + } +} From 895df0265a13c57f7bb0ea38f206ad447990d86b Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 26 Jan 2026 13:19:21 -0300 Subject: [PATCH 088/150] Simplify Configurator.setRateLimit making it a bit more restrictive and update tests --- src/Configurator.sol | 18 ++++--- test/Configurator.t.sol | 110 +++++++++++++++++++++++++--------------- 2 files changed, 82 insertions(+), 46 deletions(-) diff --git a/src/Configurator.sol b/src/Configurator.sol index acbfc08..3c2a3f6 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -84,6 +84,10 @@ contract Configurator { // --- Internal functions --- + function _max(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x > y ? x : y; + } + function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { z = x < y ? x : y; } @@ -98,13 +102,15 @@ contract Configurator { emit SetRateLimit(rateLimits, key, type(uint256).max, 0); } else { RateLimitsLike.RateLimitData memory current = RateLimitsLike(rateLimits).getRateLimitData(key); - bool withinCurrent = maxAmount <= current.maxAmount && slope <= current.slope; - bool safe = maxAmount <= defMaxAmount && slope <= defSlope || withinCurrent; uint256 maxChange = beamState.getMaxChange(rateLimits); - require(safe || block.timestamp >= zzz[rateLimits][key] + beamState.getHop(rateLimits), "Configurator/increment-too-soon"); - require(safe || maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/maxChange-maxAmount"); // maxChange always >= WAD - require(safe || slope <= current.slope * maxChange / WAD, "Configurator/maxChange-slope"); - if (!withinCurrent) { + + // Ceiling is the max of (current * maxChange) and default + require(maxAmount <= _max(current.maxAmount * maxChange / WAD, defMaxAmount), "Configurator/exceeds-max-amount"); + require(slope <= _max(current.slope * maxChange / WAD, defSlope), "Configurator/exceeds-max-slope"); + + // Any increase requires hop + if (maxAmount > current.maxAmount || slope > current.slope) { + require(block.timestamp >= zzz[rateLimits][key] + beamState.getHop(rateLimits), "Configurator/increment-too-soon"); zzz[rateLimits][key] = block.timestamp; } uint256 lastAmount = RateLimitsLike(rateLimits).getCurrentRateLimit(key); diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index e75d656..efc73b5 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -23,10 +23,13 @@ import { BeamState } from "../src/BeamState.sol"; // Mock TARGET contract implementing RateLimits interface contract MockTarget { mapping(bytes32 key => RateLimitsLike.RateLimitData) public rateLimitData; - mapping(bytes32 key => uint256) public currentRateLimit; bool public shouldFail; bytes public lastCallData; + function _min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + function setRateLimitData( bytes32 key, uint256 maxAmount, @@ -45,12 +48,16 @@ contract MockTarget { return rateLimitData[key]; } + // Mimics real RateLimits: regenerates based on slope and elapsed time function getCurrentRateLimit(bytes32 key) external view returns (uint256) { - return currentRateLimit[key]; - } - - function setCurrentRateLimit(bytes32 key, uint256 _current) external { - currentRateLimit[key] = _current; + RateLimitsLike.RateLimitData memory d = rateLimitData[key]; + if (d.maxAmount == type(uint256).max) { + return type(uint256).max; + } + return _min( + d.slope * (block.timestamp - d.lastUpdated) + d.lastAmount, + d.maxAmount + ); } function setShouldFail(bool _fail) external { @@ -116,7 +123,6 @@ contract ConfiguratorTest is DssTest { uint256 lastUpdated ) internal { target.setRateLimitData(key, maxAmount, slope, lastAmount, lastUpdated); - target.setCurrentRateLimit(key, lastAmount); } function _setupDefaultRateLimits(bytes32 key, address target, uint256 maxAmount, uint256 slope) internal { @@ -156,7 +162,7 @@ contract ConfiguratorTest is DssTest { configurator.setRateLimit(address(target2), key, 1_000 * WAD, 10 * WAD); } - // --- Safe Rate Limit Changes Tests --- + // --- Decrease Rate Limit Tests (no hop required) --- function testSetRateLimitDecreasing() public { bytes32 key = keccak256("decreasing-key"); @@ -180,7 +186,9 @@ contract ConfiguratorTest is DssTest { _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); - // Increase but stay within defaults is safe + vm.warp(block.timestamp + 86_400); // Any increase requires hop + + // Increase but stay within defaults (ceiling is max of current*maxChange and default) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 800 * WAD, 8 * WAD); @@ -195,7 +203,7 @@ contract ConfiguratorTest is DssTest { _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); - // Decrease maxAmount, keep slope same - still safe + // Decrease maxAmount, keep slope same - no hop required vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 400 * WAD, 5 * WAD); @@ -204,7 +212,7 @@ contract ConfiguratorTest is DssTest { assertEq(data.slope, 5 * WAD, "slope should stay same"); } - // --- Unsafe Rate Limit Changes Tests --- + // --- Increase Rate Limit Tests (hop required) --- function testSetRateLimitIncreasingTooSoon() public { bytes32 key = keccak256("too-soon-key"); @@ -216,7 +224,7 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 3_600); // Warp to allow first increase - // First increase beyond defaults (unsafe, respects maxChange) + // First increase (respects maxChange ceiling) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_300 * WAD, 13 * WAD); // 1.3x from current @@ -244,7 +252,7 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 3_600); // Warp to allow first increase - // First increase beyond defaults (unsafe, respects maxChange 1.5x) + // First increase (respects maxChange ceiling 1.5x) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 3_000 * WAD, 25 * WAD); // Exactly at defaults, 1.5x maxAmount, 1.25x slope @@ -270,9 +278,9 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 1); // Warp past hop to allow attempt - // Try to increase more than maxChange allows (beyond defaults and beyond maxChange) + // Try to increase more than ceiling allows vm.prank(CBEAM1); - vm.expectRevert("Configurator/maxChange-maxAmount"); + vm.expectRevert("Configurator/exceeds-max-amount"); configurator.setRateLimit(address(target1), key, 1_300 * WAD, 8 * WAD); // 1.625x increase (> 1.5x) } @@ -286,9 +294,9 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 1); // Warp past hop to allow attempt - // Try to increase slope more than maxChange allows (beyond defaults and beyond maxChange) + // Try to increase slope more than ceiling allows vm.prank(CBEAM1); - vm.expectRevert("Configurator/maxChange-slope"); + vm.expectRevert("Configurator/exceeds-max-slope"); configurator.setRateLimit(address(target1), key, 1_000 * WAD, 13 * WAD); // 1.625x increase (> 1.5x) } @@ -385,10 +393,12 @@ contract ConfiguratorTest is DssTest { bytes32 key = keccak256("cap-key"); _setupCBeam(address(target1), CBEAM1); _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + // Current: maxAmount=2000, slope=20, lastAmount=1500 + // getCurrentRateLimit (no time elapsed) = min(20*0 + 1500, 2000) = 1500 _setupRateLimitData(target1, key, 2_000 * WAD, 20 * WAD, 1_500 * WAD, block.timestamp); - target1.setCurrentRateLimit(key, 1_500 * WAD); - // Decrease maxAmount below current lastAmount + // Decrease maxAmount below current rate limit (1500 -> 1000) + // lastAmount = min(newMaxAmount, currentRateLimit) = min(1000, 1500) = 1000 vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); @@ -400,15 +410,21 @@ contract ConfiguratorTest is DssTest { bytes32 key = keccak256("preserve-key"); _setupCBeam(address(target1), CBEAM1); _setupDefaultRateLimits(key, address(target1), 2_000 * WAD, 20 * WAD); - _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 500 * WAD, block.timestamp); - target1.setCurrentRateLimit(key, 500 * WAD); + // Current: maxAmount=1000, slope=10, lastAmount=100 + // Use small lastAmount so regeneration after hop doesn't hit maxAmount + _setupRateLimitData(target1, key, 1_000 * WAD, 10 * WAD, 100 * WAD, block.timestamp); + beamState.setHop(address(target1), 1); // 1 second hop + + vm.warp(block.timestamp + 1); // Minimal warp to satisfy hop + // After 1s: getCurrentRateLimit = min(10*1 + 100, 1000) = 110 WAD - // Increase maxAmount + // Increase maxAmount to 1500 (above current rate limit of 110) + // lastAmount = min(newMaxAmount, currentRateLimit) = min(1500, 110) = 110 vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_500 * WAD, 15 * WAD); RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); - assertEq(data.lastAmount, 500 * WAD, "lastAmount should be preserved when below new max"); + assertEq(data.lastAmount, 110 * WAD, "lastAmount should be preserved when below new max"); } // --- Global Default Fallback Tests --- @@ -419,6 +435,8 @@ contract ConfiguratorTest is DssTest { _setupDefaultRateLimits(key, address(0), 1_000 * WAD, 10 * WAD); // Global defaults _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + vm.warp(block.timestamp + 86_400); // Any increase requires hop + // Should use global defaults vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 900 * WAD, 9 * WAD); @@ -435,6 +453,8 @@ contract ConfiguratorTest is DssTest { _setupDefaultRateLimits(key, address(target1), 2_000 * WAD, 20 * WAD); // Specific _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + vm.warp(block.timestamp + 86_400); // Any increase requires hop + // Should use TARGET1 specific defaults (higher limits) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_500 * WAD, 15 * WAD); @@ -456,11 +476,11 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 86_400); // Warp to allow first increase - // First increase beyond defaults (unsafe) + // First increase (sets zzz) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_100 * WAD, 11 * WAD); - // Try second increase immediately - should fail due to global hop + // Try second increase immediately - should fail due to hop vm.prank(CBEAM1); vm.expectRevert("Configurator/increment-too-soon"); configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); @@ -478,9 +498,9 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 86_400); // Wait for hop - // Try to increase by 1.67x - should fail due to global maxChange of 1.5x (beyond defaults) + // Try to increase by 1.67x - should fail due to global maxChange ceiling of 1.5x vm.prank(CBEAM1); - vm.expectRevert("Configurator/maxChange-maxAmount"); + vm.expectRevert("Configurator/exceeds-max-amount"); configurator.setRateLimit(address(target1), key, 1_500 * WAD, 9 * WAD); } @@ -499,11 +519,11 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 3_600); // Warp to specific hop (shorter than global) - // First increase beyond defaults (unsafe) + // First increase (sets zzz) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_100 * WAD, 11 * WAD); - // Try second increase immediately - should fail due to specific hop + // Try second increase immediately - should fail due to hop vm.prank(CBEAM1); vm.expectRevert("Configurator/increment-too-soon"); configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); @@ -547,7 +567,7 @@ contract ConfiguratorTest is DssTest { // Try to exceed the specific maxChange (2x) - should fail vm.prank(CBEAM1); - vm.expectRevert("Configurator/maxChange-maxAmount"); + vm.expectRevert("Configurator/exceeds-max-amount"); configurator.setRateLimit(address(target1), key, 3_100 * WAD, 15 * WAD); // >2x from 1500 } @@ -634,16 +654,16 @@ contract ConfiguratorTest is DssTest { beamState.setHop(address(target1), 3_600); beamState.setMaxChange(address(target1), 2 * WAD); // 2x - vm.warp(block.timestamp + 3_600); // Warp to allow first unsafe increase + vm.warp(block.timestamp + 3_600); // Warp to allow first increase - // 1. Decrease limits (safe, immediate) + // 1. Decrease limits (immediate, no hop required) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 700 * WAD, 7 * WAD); RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); assertEq(data.maxAmount, 700 * WAD, "step 1: decrease should work immediately"); - // 2. Increase limits beyond defaults (unsafe, sets timestamp) + // 2. Increase limits (sets zzz timestamp) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); @@ -671,7 +691,7 @@ contract ConfiguratorTest is DssTest { // 6. Try to exceed maxChange vm.warp(block.timestamp + 3_600); vm.prank(CBEAM1); - vm.expectRevert("Configurator/maxChange-maxAmount"); + vm.expectRevert("Configurator/exceeds-max-amount"); configurator.setRateLimit(address(target1), key, 4_900 * WAD, 24 * WAD); // >2x (2.04x) } @@ -688,6 +708,8 @@ contract ConfiguratorTest is DssTest { _setupDefaultRateLimits(key, address(target2), 2_000 * WAD, 20 * WAD); _setupRateLimitData(target2, key, 1_000 * WAD, 10 * WAD, 1_000 * WAD, block.timestamp); + vm.warp(block.timestamp + 86_400); // Any increase requires hop + // CBEAM1 configures TARGET1 vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 600 * WAD, 6 * WAD); @@ -718,27 +740,33 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 3_600); // Warp to allow first increase - // Increase beyond defaults (sets zzz) + // Increase, sets zzz vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_100 * WAD, 11 * WAD); uint256 zzzAfterIncrease = configurator.zzz(address(target1), key); assertGt(zzzAfterIncrease, 0, "zzz should be set after increase"); - // Decrease (doesn't update zzz for the decrease itself, but allows future increases) + // Decrease (doesn't update zzz) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 900 * WAD, 9 * WAD); assertEq(configurator.zzz(address(target1), key), zzzAfterIncrease, "zzz should not change on decrease"); - // Immediate increase (within defaults now) should work + // Immediate increase doesn't work, requires hop + vm.prank(CBEAM1); + vm.expectRevert("Configurator/increment-too-soon"); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); + + // After hop, increase within defaults should work + vm.warp(block.timestamp + 3_600); vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); - // Increase beyond defaults should respect hop from first increase + // Immediate increase should fail (hop just used) vm.prank(CBEAM1); vm.expectRevert("Configurator/increment-too-soon"); configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); - // After hop, increase should work + // After another hop, increase should work vm.warp(block.timestamp + 3_600); vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_200 * WAD, 12 * WAD); @@ -775,7 +803,7 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 3_600); // Warp to allow first increase - // Increase key1 beyond defaults + // Increase key1 (sets zzz for key1) vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key1, 1_100 * WAD, 11 * WAD); @@ -801,6 +829,8 @@ contract ConfiguratorTest is DssTest { _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + vm.warp(block.timestamp + 86_400); // Any increase requires hop + // Stop the BeamState beamState.stop(); From bb27effd090a59660667cc23e3c411ea0ef0f58c Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 26 Jan 2026 17:02:25 -0300 Subject: [PATCH 089/150] Add comment --- src/BeamState.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/BeamState.sol b/src/BeamState.sol index 7e79daf..1720c7f 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -216,6 +216,9 @@ contract BeamState { emit DelCBeam(cBeam); } + // Note: Once cBEAMS are whitelisted they can be assigned to controllers/rate-limits (potentially without delay), + // so theoretically they can be positioned to interfere with each other. This is known and assumed to be monitored + function setCBeamForRateLimits(address rateLimits_, address cBeam) external roleAuth { require(rateLimits[rateLimits_] == 1, "BeamState/not-existing-rateLimits"); require(cBeams[cBeam] == 1, "BeamState/not-existing-cBeam"); From f8486ef016ece3a8628cd79c25f97ce3162da30d Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:12:58 -0300 Subject: [PATCH 090/150] =?UTF-8?q?Split=20deployment=20and=20init=20of=20?= =?UTF-8?q?Mom=20and=20TimelockWrapper=20to=20separate=20func=E2=80=A6=20(?= =?UTF-8?q?#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Split deployment and init of Mom and TimelockWrapper to separate functions * Extract setting core contracts in chainlog + some improvements --- deploy/PASDeploy.sol | 36 ++++++++----- deploy/PASInit.sol | 72 ++++++++++++++++++-------- deploy/PASInstance.sol | 2 - test/Integration.t.sol | 78 +++++++++++++++-------------- test/timelock/TimelockWrapper.t.sol | 12 ++--- 5 files changed, 122 insertions(+), 78 deletions(-) diff --git a/deploy/PASDeploy.sol b/deploy/PASDeploy.sol index c006eb8..4afc7bd 100644 --- a/deploy/PASDeploy.sol +++ b/deploy/PASDeploy.sol @@ -32,26 +32,36 @@ library PASDeploy { uint256 minDelay ) internal returns (PASInstance memory pasInstance) { pasInstance.beamState = address(new BeamState()); - pasInstance.configurator = address(new Configurator(pasInstance.beamState)); - pasInstance.timelock = address(new Timelock( minDelay, - owner // admin + owner )); + ScriptTools.switchOwner(pasInstance.beamState, deployer, owner); + } - pasInstance.timelockWrapper = address(new TimelockWrapper( - pasInstance.timelock, - pasInstance.beamState + function deployMom( + address owner, + address beamState, + address timelock + ) internal returns (address mom) { + mom = address(new PASMom( + beamState, + timelock )); + PASMom(mom).setOwner(owner); + } - pasInstance.mom = address(new PASMom( - pasInstance.beamState, - pasInstance.timelock + function deployTimelockWrapper( + address deployer, + address owner, + address timelock, + address beamState + ) internal returns (address timelockWrapper) { + timelockWrapper = address(new TimelockWrapper( + timelock, + beamState )); - - ScriptTools.switchOwner(pasInstance.beamState, deployer, owner); - ScriptTools.switchOwner(pasInstance.timelockWrapper, deployer, owner); - PASMom(pasInstance.mom).setOwner(owner); + ScriptTools.switchOwner(timelockWrapper, deployer, owner); } } diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index d4d63de..3aa6962 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -77,27 +77,20 @@ library PASInit { } function init( - DssInstance memory dss, PASInstance memory pasInstance, uint256 minDelay, address coreCouncil, address[] memory cancellers, address[] memory pausers ) internal { - BeamStateLike beamState = BeamStateLike(pasInstance.beamState); - ConfiguratorLike configurator = ConfiguratorLike(pasInstance.configurator); - TimelockLike timelock = TimelockLike(pasInstance.timelock); - TimelockWrapperLike timelockWrapper = TimelockWrapperLike(pasInstance.timelockWrapper); - PASMomLike mom = PASMomLike(pasInstance.mom); + BeamStateLike beamState = BeamStateLike(pasInstance.beamState); + ConfiguratorLike configurator = ConfiguratorLike(pasInstance.configurator); + TimelockLike timelock = TimelockLike(pasInstance.timelock); // --- Sanity checks --- - require(configurator.beamState() == address(beamState), "PASInit/configurator-beamState-mismatch"); - require(timelock.getMinDelay() == minDelay, "PASInit/timelock-minDelay-mismatch"); - require(timelockWrapper.timelock() == address(timelock), "PASInit/wrapper-timelock-mismatch"); - require(timelockWrapper.beamState() == address(beamState), "PASInit/wrapper-beamState-mismatch"); - require(mom.beamState() == address(beamState), "PASInit/mom-beamState-mismatch"); - require(mom.timelock() == address(timelock), "PASInit/mom-timelock-mismatch"); + require(configurator.beamState() == address(beamState), "PASInit/configurator-beamState-mismatch"); + require(timelock.getMinDelay() == minDelay, "PASInit/timelock-minDelay-mismatch"); // --- Configure BeamState --- @@ -127,10 +120,9 @@ library PASInit { // --- Configure Timelock and Wrapper --- - // Grant the coreCouncil as the proposer in the timelock either directly or through the wrapper + // Grant the coreCouncil as the proposer in the timelock directly // Grant cancellers and pausers in timelock with their respective roles timelock.grantRole(timelock.PROPOSER_ROLE(), coreCouncil); - timelock.grantRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)); timelock.grantRole(timelock.CANCELLER_ROLE(), coreCouncil); for (uint256 i = 0; i < cancellers.length; ++i) { timelock.grantRole(timelock.CANCELLER_ROLE(), cancellers[i]); @@ -138,9 +130,32 @@ library PASInit { for (uint256 i = 0; i < pausers.length; ++i) { timelock.grantRole(timelock.PAUSER_ROLE(), pausers[i]); } - timelockWrapper.kiss(coreCouncil); + } + + function addCoreToChainlog( + DssInstance memory dss, + PASInstance memory pasInstance + ) internal { + dss.chainlog.setAddress("PAS_STATE", pasInstance.beamState); + dss.chainlog.setAddress("PAS_CONFIGURATOR", pasInstance.configurator); + dss.chainlog.setAddress("PAS_TIMELOCK", pasInstance.timelock); + } + + function initMom( + DssInstance memory dss, + PASInstance memory pasInstance, + address mom_ + ) internal { + BeamStateLike beamState = BeamStateLike(pasInstance.beamState); + TimelockLike timelock = TimelockLike(pasInstance.timelock); + PASMomLike mom = PASMomLike(mom_); + + // --- Sanity checks --- + + require(mom.beamState() == address(beamState), "PASInit/mom-beamState-mismatch"); + require(mom.timelock() == address(timelock), "PASInit/mom-timelock-mismatch"); - // --- Configure Mom --- + // --- Set permissions --- // Rely Mom on BeamState to call stop() beamState.rely(address(mom)); @@ -151,9 +166,26 @@ library PASInit { // --- Chainlog --- - dss.chainlog.setAddress("PAS_STATE", address(beamState)); - dss.chainlog.setAddress("PAS_CONFIGURATOR", address(configurator)); - dss.chainlog.setAddress("PAS_TIMELOCK", address(timelock)); - dss.chainlog.setAddress("PAS_MOM", address(mom)); + dss.chainlog.setAddress("PAS_MOM", address(mom)); + } + + function initTimelockWrapper( + PASInstance memory pasInstance, + address timelockWrapper_, + address coreCouncil + ) internal { + TimelockLike timelock = TimelockLike(pasInstance.timelock); + TimelockWrapperLike timelockWrapper = TimelockWrapperLike(timelockWrapper_); + + // --- Sanity checks --- + + require(timelockWrapper.timelock() == pasInstance.timelock, "PASInit/wrapper-timelock-mismatch"); + require(timelockWrapper.beamState() == pasInstance.beamState, "PASInit/wrapper-beamState-mismatch"); + + // --- Set permissions --- + + // Grant the coreCouncil as the proposer through the wrapper + timelock.grantRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)); + timelockWrapper.kiss(coreCouncil); } } diff --git a/deploy/PASInstance.sol b/deploy/PASInstance.sol index 77f299c..2e43739 100644 --- a/deploy/PASInstance.sol +++ b/deploy/PASInstance.sol @@ -20,6 +20,4 @@ struct PASInstance { address beamState; address configurator; address timelock; - address timelockWrapper; - address mom; } diff --git a/test/Integration.t.sol b/test/Integration.t.sol index f656201..466b619 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -120,14 +120,15 @@ contract IntegrationTest is DssTest { // Deploy using PASDeploy pas = PASDeploy.deploy(address(this), pauseProxy, MIN_DELAY); - // Cast to typed contracts - beamState = BeamState(pas.beamState); - configurator = Configurator(pas.configurator); - timelock = Timelock(payable(pas.timelock)); - timelockWrapper = TimelockWrapper(pas.timelockWrapper); - mom = PASMom(pas.mom); - chief = ChiefLike(dss.chainlog.getAddress("MCD_ADM")); + beamState = BeamState(pas.beamState); + configurator = Configurator(pas.configurator); + timelock = Timelock(payable(pas.timelock)); + // Deploy Mom and TimelockWrapper separately + mom = PASMom(PASDeploy.deployMom(pauseProxy, pas.beamState, pas.timelock)); + timelockWrapper = TimelockWrapper(PASDeploy.deployTimelockWrapper(address(this), pauseProxy, pas.timelock, pas.beamState)); + + chief = ChiefLike(dss.chainlog.getAddress("MCD_ADM")); // Initialize using PASInit (must be called by pauseProxy who has auth on BeamState and Timelock) address[] memory cancellers = new address[](1); @@ -137,7 +138,10 @@ contract IntegrationTest is DssTest { pausers[0] = pauser; vm.startPrank(pauseProxy); - PASInit.init(dss, pas, MIN_DELAY, coreCouncil, cancellers, pausers); + PASInit.init(pas, MIN_DELAY, coreCouncil, cancellers, pausers); + PASInit.addCoreToChainlog(dss, pas); + PASInit.initMom(dss, pas, address(mom)); + PASInit.initTimelockWrapper(pas, address(timelockWrapper), coreCouncil); vm.stopPrank(); } @@ -149,8 +153,12 @@ contract IntegrationTest is DssTest { assertTrue(pas.beamState != address(0), "beamState should be deployed"); assertTrue(pas.configurator != address(0), "configurator should be deployed"); assertTrue(pas.timelock != address(0), "timelock should be deployed"); - assertTrue(pas.timelockWrapper != address(0), "timelockWrapper should be deployed"); - assertTrue(pas.mom != address(0), "mom should be deployed"); + assertTrue(address(mom) != address(0), "mom should be deployed"); + assertTrue(address(timelockWrapper) != address(0), "timelockWrapper should be deployed"); + } + + function testConfiguratorLinkedToBeamState() public view { + assertEq(address(configurator.beamState()), address(beamState), "configurator should reference beamState"); } function testMomLinkedCorrectly() public view { @@ -159,10 +167,6 @@ contract IntegrationTest is DssTest { assertEq(mom.owner(), pauseProxy, "mom should be owned by pauseProxy"); } - function testConfiguratorLinkedToBeamState() public view { - assertEq(address(configurator.beamState()), address(beamState), "configurator should reference beamState"); - } - function testTimelockWrapperLinkedCorrectly() public view { assertEq(address(timelockWrapper.timelock()), address(timelock), "wrapper should reference timelock"); assertEq(address(timelockWrapper.beamState()), address(beamState), "wrapper should reference beamState"); @@ -175,29 +179,6 @@ contract IntegrationTest is DssTest { assertEq(dss.chainlog.getAddress("PAS_MOM"), address(mom), "PAS_MOM should be set in chainlog"); } - function testMomConfigAfterInit() public view { - assertEq(mom.owner(), pauseProxy, "mom owner should be pauseProxy"); - assertEq(mom.authority(), address(chief), "mom authority should be MCD_ADM"); - assertEq(beamState.wards(address(mom)), 1, "mom should have wards on beamState"); - assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), address(mom)), "mom should have PAUSER_ROLE on timelock"); - } - - function testTimelockRolesAfterInit() public view { - assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), pauseProxy), "pauseProxy should be admin"); - assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should be proposer"); - assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)), "wrapper should be proposer"); - assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), coreCouncil), "coreCouncil should be canceller"); - assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller), "canceller should have role"); - assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser), "pauser should have role"); - } - - function testBeamStateRolesAfterInit() public view { - // Timelock has DELAYED role - assertTrue(beamState.hasUserRole(address(timelock), uint8(PASInit.Role.DELAYED)), "timelock should have DELAYED role"); - // CoreCouncil has IMMEDIATE role - assertTrue(beamState.hasUserRole(coreCouncil, uint8(PASInit.Role.IMMEDIATE)), "coreCouncil should have IMMEDIATE role"); - } - function testBeamStateActionsConfigured() public view { // DELAYED role actions (timelocked) assertTrue(beamState.isActionInRole(BeamState.start.selector, uint8(PASInit.Role.DELAYED)), "start should be DELAYED"); @@ -222,10 +203,33 @@ contract IntegrationTest is DssTest { assertTrue(beamState.isActionInRole(BeamState.delInitControllerActions.selector, uint8(PASInit.Role.IMMEDIATE)), "delInitControllerActions should be IMMEDIATE"); } + function testBeamStateRolesAfterInit() public view { + // Timelock has DELAYED role + assertTrue(beamState.hasUserRole(address(timelock), uint8(PASInit.Role.DELAYED)), "timelock should have DELAYED role"); + // CoreCouncil has IMMEDIATE role + assertTrue(beamState.hasUserRole(coreCouncil, uint8(PASInit.Role.IMMEDIATE)), "coreCouncil should have IMMEDIATE role"); + } + function testTimelockWrapperBudsAfterInit() public view { assertEq(timelockWrapper.buds(coreCouncil), 1, "coreCouncil should be whitelisted on wrapper"); } + function testTimelockRolesAfterInit() public view { + assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), pauseProxy), "pauseProxy should be admin"); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should be proposer"); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)), "wrapper should be proposer"); + assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), coreCouncil), "coreCouncil should be canceller"); + assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller), "canceller should be canceller"); + assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser), "pauser should be pauser"); + assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), address(mom)), "mom should be pauser"); + } + + function testMomConfigAfterInit() public view { + assertEq(mom.owner(), pauseProxy, "mom owner should be pauseProxy"); + assertEq(mom.authority(), address(chief), "mom authority should be MCD_ADM"); + assertEq(beamState.wards(address(mom)), 1, "mom should have wards on beamState"); + } + // ============================================================================ // CoreCouncil Direct Actions (IMMEDIATE Role) Tests // ============================================================================ diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol index 2977ca9..601640f 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapper.t.sol @@ -106,15 +106,15 @@ contract TimelockWrapperTest is DssTest { pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); PASInstance memory pas = PASDeploy.deploy(address(this), pauseProxy, MIN_DELAY); - - vm.startPrank(pauseProxy); - PASInit.init(dss, pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); - vm.stopPrank(); - beamState = BeamState(pas.beamState); configurator = Configurator(pas.configurator); timelock = Timelock(payable(pas.timelock)); - wrapper = TimelockWrapper(pas.timelockWrapper); + wrapper = TimelockWrapper(PASDeploy.deployTimelockWrapper(address(this), pauseProxy, pas.timelock, pas.beamState)); + + vm.startPrank(pauseProxy); + PASInit.init(pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); + PASInit.initTimelockWrapper(pas, address(wrapper), coreCouncil); + vm.stopPrank(); // Grant configurator admin role on mainnet controllers and rate limiters vm.startPrank(SPARK_PROXY); From 55c6aa2959b3fdd106bdd3115484a673956e11f8 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:20:22 +0700 Subject: [PATCH 091/150] Add deploy function and test for foreign wrapper --- deploy/PASDeploy.sol | 14 + test/timelock/TimelockWrapperForeign.t.sol | 644 +++++++++++++++++++++ 2 files changed, 658 insertions(+) create mode 100644 test/timelock/TimelockWrapperForeign.t.sol diff --git a/deploy/PASDeploy.sol b/deploy/PASDeploy.sol index 4afc7bd..d033069 100644 --- a/deploy/PASDeploy.sol +++ b/deploy/PASDeploy.sol @@ -22,6 +22,7 @@ import { BeamState } from "src/BeamState.sol"; import { Configurator } from "src/Configurator.sol"; import { Timelock } from "src/timelock/Timelock.sol"; import { TimelockWrapper } from "src/timelock/TimelockWrapper.sol"; +import { TimelockWrapperForeign } from "src/timelock/TimelockWrapperForeign.sol"; import { PASMom } from "src/PASMom.sol"; library PASDeploy { @@ -64,4 +65,17 @@ library PASDeploy { )); ScriptTools.switchOwner(timelockWrapper, deployer, owner); } + + function deployTimelockWrapperForeign( + address deployer, + address owner, + address timelock, + address beamState + ) internal returns (address timelockWrapperForeign) { + timelockWrapperForeign = address(new TimelockWrapperForeign( + timelock, + beamState + )); + ScriptTools.switchOwner(timelockWrapperForeign, deployer, owner); + } } diff --git a/test/timelock/TimelockWrapperForeign.t.sol b/test/timelock/TimelockWrapperForeign.t.sol new file mode 100644 index 0000000..a859139 --- /dev/null +++ b/test/timelock/TimelockWrapperForeign.t.sol @@ -0,0 +1,644 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +import "dss-test/DssTest.sol"; +import { TimelockWrapperForeign, RateLimitConfig } from "src/timelock/TimelockWrapperForeign.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; +import { BeamState } from "src/BeamState.sol"; +import { Configurator } from "src/Configurator.sol"; +import { PASDeploy } from "deploy/PASDeploy.sol"; +import { PASInit } from "deploy/PASInit.sol"; +import { PASInstance } from "deploy/PASInstance.sol"; + +interface ControllerLike { + function hasRole(bytes32 role, address account) external view returns (bool); + function grantRole(bytes32 role, address account) external; + function revokeRole(bytes32 role, address account) external; + function RELAYER() external view returns (bytes32); + function mintRecipients(uint32) external view returns (bytes32); + function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; + function rateLimits() external view returns (address); + // Shared getters + function layerZeroRecipients(uint32) external view returns (bytes32); + function maxSlippages(address) external view returns (uint256); + function maxExchangeRates(address) external view returns (uint256); + // Grove-specific getters + function centrifugeRecipients(uint16) external view returns (bytes32); + // uniswapV3PoolParams returns: (swapMaxTickDelta, addLiquidityLowerTick, addLiquidityUpperTick, twapSecondsAgo) + function uniswapV3PoolParams(address) external view returns (uint24, int24, int24, uint32); + function merklDistributor() external view returns (address); +} + +interface RateLimitsLike { + function grantRole(bytes32 role, address account) external; + function getRateLimitData(bytes32 key) external view returns (uint256, uint256, uint256, uint256); +} + +contract TimelockWrapperForeignTest is DssTest { + // --- Events --- + event Kiss(address indexed usr); + event Diss(address indexed usr); + + // --- Base Addresses --- + // https://github.com/sparkdotfi/spark-address-registry/blob/master/src/Base.sol + address constant SPARK_CONTROLLER = 0x86036CE5d2f792367C0AA43164e688d13c5A60A8; + address constant SPARK_PROXY = 0xF93B7122450A50AF3e5A76E1d546e95Ac1d0F579; + + // https://github.com/grove-labs/grove-address-registry/blob/master/src/Base.sol + address constant GROVE_CONTROLLER = 0x7f8408eBbBC3504F83eeDa52910dd75Eba92C955; + address constant GROVE_PROXY = 0x491EDFB0B8b608044e227225C715981a30F3A44E; + + address constant GOV_RELAY = 0xdD0BCc201C9E47c6F6eE68E4dB05b652Bb6aC255; + + uint256 constant FORK_BLOCK = 41300000; + uint256 constant MIN_DELAY = 1 days; + + bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); + + // --- Fetched from controllers --- + address SPARK_RATE_LIMITS; + address GROVE_RATE_LIMITS; + + // --- PAS Instance --- + BeamState beamState; + Configurator configurator; + Timelock timelock; + TimelockWrapperForeign wrapper; + + address coreCouncil; + address cBeam; + + function setUp() public { + vm.createSelectFork(vm.envString("BASE_RPC_URL"), FORK_BLOCK); + + // Fetch rate limits from controllers + SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); + GROVE_RATE_LIMITS = ControllerLike(GROVE_CONTROLLER).rateLimits(); + + coreCouncil = makeAddr("coreCouncil"); + cBeam = makeAddr("cBeam"); + + PASInstance memory pas = PASDeploy.deploy(address(this), GOV_RELAY, MIN_DELAY); + beamState = BeamState(pas.beamState); + configurator = Configurator(pas.configurator); + timelock = Timelock(payable(pas.timelock)); + wrapper = TimelockWrapperForeign(PASDeploy.deployTimelockWrapperForeign(address(this), GOV_RELAY, pas.timelock, pas.beamState)); + + vm.startPrank(GOV_RELAY); + PASInit.init(pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); + PASInit.initTimelockWrapper(pas, address(wrapper), coreCouncil); + vm.stopPrank(); + + // Grant configurator admin role on Base controllers and rate limiters + vm.startPrank(SPARK_PROXY); + ControllerLike(SPARK_CONTROLLER).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); + RateLimitsLike(SPARK_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); + vm.stopPrank(); + + vm.startPrank(GROVE_PROXY); + ControllerLike(GROVE_CONTROLLER).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); + RateLimitsLike(GROVE_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); + vm.stopPrank(); + + // Setup controllers, rate limiters, and cBeam in BeamState + vm.startPrank(coreCouncil); + bytes32 id; + id = wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("spark-ctrl"), MIN_DELAY); + _execute(id); + id = wrapper.addController(GROVE_CONTROLLER, bytes32(0), keccak256("grove-ctrl"), MIN_DELAY); + _execute(id); + id = wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("spark-rl"), MIN_DELAY); + _execute(id); + id = wrapper.addRateLimits(GROVE_RATE_LIMITS, bytes32(0), keccak256("grove-rl"), MIN_DELAY); + _execute(id); + id = wrapper.addCBeam(cBeam, bytes32(0), keccak256("cbeam"), MIN_DELAY); + _execute(id); + + // Verify wrapper correctly configured BeamState + assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "Spark controller not added"); + assertEq(beamState.controllers(GROVE_CONTROLLER), 1, "Grove controller not added"); + assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1, "Spark rate limits not added"); + assertEq(beamState.rateLimits(GROVE_RATE_LIMITS), 1, "Grove rate limits not added"); + assertEq(beamState.cBeams(cBeam), 1, "cBeam not added"); + + // Link cBeam to controllers/rate limiters + beamState.setCBeamForController(SPARK_CONTROLLER, cBeam); + beamState.setCBeamForController(GROVE_CONTROLLER, cBeam); + beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); + beamState.setCBeamForRateLimits(GROVE_RATE_LIMITS, cBeam); + vm.stopPrank(); + } + + // ============================================================================ + // Helpers + // ============================================================================ + + function _execute(bytes32 id) internal { + vm.warp(block.timestamp + MIN_DELAY); + Timelock.Operation memory op = timelock.getOperation(id); + timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); + } + + function _getControllerAction(bytes32 id) internal view returns (bytes memory data, address controller) { + Timelock.Operation memory op = timelock.getOperation(id); + // Decode payload: selector (4 bytes) || abi.encode(data, controller) + bytes memory payload = op.payloads[0]; + assembly { + payload := add(payload, 4) + } + (data, controller) = abi.decode(payload, (bytes, address)); + } + + // ============================================================================ + // Authorization Tests + // ============================================================================ + + function testConstructor() public { + vm.expectEmit(true, false, false, true); + emit Rely(address(this)); + TimelockWrapperForeign newWrapper = new TimelockWrapperForeign(address(timelock), address(beamState)); + + assertEq(address(newWrapper.timelock()), address(timelock), "Timelock set correctly"); + assertEq(address(newWrapper.beamState()), address(beamState), "BeamState set correctly"); + assertEq(newWrapper.wards(address(this)), 1, "Deployer is ward"); + } + + function testAuth() public { + checkAuth(address(wrapper), "TimelockWrapperForeign"); + } + + function testKissDiss() public { + address usr = makeAddr("usr"); + + vm.startPrank(GOV_RELAY); + assertEq(wrapper.buds(usr), 0); + vm.expectEmit(true, false, false, true); + emit Kiss(usr); + wrapper.kiss(usr); + assertEq(wrapper.buds(usr), 1); + vm.expectEmit(true, false, false, true); + emit Diss(usr); + wrapper.diss(usr); + assertEq(wrapper.buds(usr), 0); + vm.stopPrank(); + } + + function testAuthModifiers() public { + bytes4[] memory authedMethods = new bytes4[](2); + authedMethods[0] = wrapper.kiss.selector; + authedMethods[1] = wrapper.diss.selector; + + vm.startPrank(address(0xBEEF)); + checkModifier(address(wrapper), "TimelockWrapperForeign/not-authorized", authedMethods); + vm.stopPrank(); + } + + function testTollModifiers() public { + bytes4[] memory tolledMethods = new bytes4[](20); + tolledMethods[0] = wrapper.start.selector; + tolledMethods[1] = wrapper.setHop.selector; + tolledMethods[2] = wrapper.setMaxChange.selector; + tolledMethods[3] = wrapper.addRateLimits.selector; + tolledMethods[4] = wrapper.addController.selector; + tolledMethods[5] = wrapper.addCBeam.selector; + tolledMethods[6] = wrapper.addInitRateLimits.selector; + tolledMethods[7] = wrapper.batchAddInitRateLimits.selector; + tolledMethods[8] = wrapper.grantRole.selector; + tolledMethods[9] = wrapper.revokeRole.selector; + tolledMethods[10] = wrapper.setMintRecipient.selector; + tolledMethods[11] = wrapper.setLayerZeroRecipient.selector; + tolledMethods[12] = wrapper.setMaxSlippage.selector; + tolledMethods[13] = wrapper.setMaxExchangeRate.selector; + tolledMethods[14] = wrapper.setUniswapV3PoolMaxTickDelta.selector; + tolledMethods[15] = wrapper.setUniswapV3AddLiquidityLowerTickBound.selector; + tolledMethods[16] = wrapper.setUniswapV3AddLiquidityUpperTickBound.selector; + tolledMethods[17] = wrapper.setUniswapV3TwapSecondsAgo.selector; + tolledMethods[18] = wrapper.setCentrifugeRecipient.selector; + tolledMethods[19] = wrapper.setMerklDistributor.selector; + + vm.startPrank(address(0xBEEF)); + checkModifier(address(wrapper), "TimelockWrapperForeign/not-whitelisted", tolledMethods); + vm.stopPrank(); + } + + // ============================================================================ + // BeamState Configuration Tests + // ============================================================================ + + function testStart() public { + vm.prank(coreCouncil); + beamState.stop(); + assertTrue(beamState.stopped()); + + vm.prank(coreCouncil); + bytes32 id = wrapper.start(bytes32(0), keccak256("start"), MIN_DELAY); + _execute(id); + + assertFalse(beamState.stopped()); + } + + function testSetHop() public { + vm.prank(coreCouncil); + bytes32 id = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), keccak256("hop"), MIN_DELAY); + _execute(id); + + assertEq(beamState.getHop(SPARK_RATE_LIMITS), 3600); + } + + function testSetMaxChange() public { + vm.prank(coreCouncil); + bytes32 id = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), keccak256("mc"), MIN_DELAY); + _execute(id); + + assertEq(beamState.maxChange(GROVE_RATE_LIMITS), 2e18); + } + + function testAddRateLimits() public { + address rateLimits = makeAddr("rateLimits"); + + vm.prank(coreCouncil); + bytes32 id = wrapper.addRateLimits(rateLimits, bytes32(0), keccak256("rl"), MIN_DELAY); + _execute(id); + + assertEq(beamState.rateLimits(rateLimits), 1); + } + + function testAddController() public { + address controller = makeAddr("controller"); + + vm.prank(coreCouncil); + bytes32 id = wrapper.addController(controller, bytes32(0), keccak256("ctrl"), MIN_DELAY); + _execute(id); + + assertEq(beamState.controllers(controller), 1); + } + + function testAddCBeam() public { + address beam = makeAddr("beam"); + + vm.prank(coreCouncil); + bytes32 id = wrapper.addCBeam(beam, bytes32(0), keccak256("cbeam"), MIN_DELAY); + _execute(id); + + assertEq(beamState.cBeams(beam), 1); + } + + function _checkAddInitRateLimits(address rateLimits, bytes32 key, bytes32 salt) internal { + RateLimitConfig memory config = RateLimitConfig({ + key: key, + rateLimits: rateLimits, + maxAmount: 10_000_000e18, + slope: 1_000_000e18 + }); + + vm.prank(coreCouncil); + bytes32 id = wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); + _execute(id); + + // Verify stored in BeamState + BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(config.key, rateLimits); + assertEq(limits.maxAmount, config.maxAmount); + assertEq(limits.slope, config.slope); + + // Execute on real rate limiter via Configurator + vm.prank(cBeam); + configurator.setRateLimit(rateLimits, config.key, config.maxAmount, config.slope); + + // Verify set on real rate limiter + (uint256 setMax, uint256 setSlope,,) = RateLimitsLike(rateLimits).getRateLimitData(config.key); + assertEq(setMax, config.maxAmount); + assertEq(setSlope, config.slope); + } + + function testAddInitRateLimitsOnSpark() public { + _checkAddInitRateLimits(SPARK_RATE_LIMITS, keccak256("spark-deposit"), keccak256("initS")); + } + + function testAddInitRateLimitsOnGrove() public { + _checkAddInitRateLimits(GROVE_RATE_LIMITS, keccak256("grove-deposit"), keccak256("initG")); + } + + function _checkBatchAddInitRateLimits(address rateLimits, bytes32 key1, bytes32 key2, bytes32 salt) internal { + RateLimitConfig[] memory configs = new RateLimitConfig[](2); + configs[0] = RateLimitConfig(key1, rateLimits, 5_000_000e18, 500_000e18); + configs[1] = RateLimitConfig(key2, rateLimits, 3_000_000e18, 300_000e18); + + vm.prank(coreCouncil); + wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); + assertEq(timelock.getOperationCount(), 1); + + vm.warp(block.timestamp + MIN_DELAY); + address[] memory targets = new address[](2); + uint256[] memory values = new uint256[](2); + bytes[] memory payloads = new bytes[](2); + for (uint256 i = 0; i < 2; i++) { + targets[i] = address(beamState); + payloads[i] = abi.encodeWithSelector(BeamState.addInitRateLimits.selector, configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope); + } + timelock.executeBatch(targets, values, payloads, bytes32(0), salt); + + // Verify stored in BeamState + assertEq(beamState.getInitRateLimits(configs[0].key, rateLimits).maxAmount, configs[0].maxAmount); + assertEq(beamState.getInitRateLimits(configs[1].key, rateLimits).maxAmount, configs[1].maxAmount); + + // Execute on real rate limiter via Configurator + vm.startPrank(cBeam); + configurator.setRateLimit(rateLimits, configs[0].key, configs[0].maxAmount, configs[0].slope); + configurator.setRateLimit(rateLimits, configs[1].key, configs[1].maxAmount, configs[1].slope); + vm.stopPrank(); + + // Verify set on real rate limiter + (uint256 max0, uint256 slope0,,) = RateLimitsLike(rateLimits).getRateLimitData(configs[0].key); + assertEq(max0, configs[0].maxAmount); + assertEq(slope0, configs[0].slope); + + (uint256 max1, uint256 slope1,,) = RateLimitsLike(rateLimits).getRateLimitData(configs[1].key); + assertEq(max1, configs[1].maxAmount); + assertEq(slope1, configs[1].slope); + } + + function testBatchAddInitRateLimitsOnSpark() public { + _checkBatchAddInitRateLimits(SPARK_RATE_LIMITS, keccak256("spark-batch-1"), keccak256("spark-batch-2"), keccak256("batchS")); + } + + function testBatchAddInitRateLimitsOnGrove() public { + _checkBatchAddInitRateLimits(GROVE_RATE_LIMITS, keccak256("grove-batch-1"), keccak256("grove-batch-2"), keccak256("batchG")); + } + + // ============================================================================ + // Controller Action Tests + // ============================================================================ + + // --- grantRole (Spark & Grove) --- + + function _checkGrantRole(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + bytes32 role = ctrl.RELAYER(); + address account = makeAddr("relayer"); + + assertFalse(ctrl.hasRole(role, account)); + + vm.prank(coreCouncil); + bytes32 id = wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertTrue(ctrl.hasRole(role, account)); + } + + function testGrantRoleOnSparkController() public { + _checkGrantRole(SPARK_CONTROLLER, keccak256("grantS")); + } + + function testGrantRoleOnGroveController() public { + _checkGrantRole(GROVE_CONTROLLER, keccak256("grantG")); + } + + // --- revokeRole (Spark & Grove) --- + + function _checkRevokeRole(address controller, address admin, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + bytes32 role = ctrl.RELAYER(); + address account = makeAddr("toRevoke"); + + vm.prank(admin); + ctrl.grantRole(role, account); + + vm.prank(coreCouncil); + bytes32 id = wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertFalse(ctrl.hasRole(role, account)); + } + + function testRevokeRoleOnSparkController() public { + _checkRevokeRole(SPARK_CONTROLLER, SPARK_PROXY, keccak256("revokeS")); + } + + function testRevokeRoleOnGroveController() public { + _checkRevokeRole(GROVE_CONTROLLER, GROVE_PROXY, keccak256("revokeG")); + } + + // --- setMintRecipient (Spark & Grove) --- + + function _checkSetMintRecipient(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + uint32 domain = 6; + bytes32 recipient = bytes32(uint256(uint160(makeAddr("recipient")))); + + vm.prank(coreCouncil); + bytes32 id = wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertEq(ctrl.mintRecipients(domain), recipient); + } + + function testSetMintRecipientOnSparkController() public { + _checkSetMintRecipient(SPARK_CONTROLLER, keccak256("mintS")); + } + + function testSetMintRecipientOnGroveController() public { + _checkSetMintRecipient(GROVE_CONTROLLER, keccak256("mintG")); + } + + // --- setLayerZeroRecipient (Spark & Grove) --- + + function _checkSetLayerZeroRecipient(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + uint32 endpointId = 111; + bytes32 recipient = bytes32(uint256(uint160(makeAddr("lzRecipient")))); + + vm.prank(coreCouncil); + bytes32 id = wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertEq(ctrl.layerZeroRecipients(endpointId), recipient); + } + + function testSetLayerZeroRecipientOnSparkController() public { + _checkSetLayerZeroRecipient(SPARK_CONTROLLER, keccak256("lzS")); + } + + function testSetLayerZeroRecipientOnGroveController() public { + _checkSetLayerZeroRecipient(GROVE_CONTROLLER, keccak256("lzG")); + } + + // --- setMaxSlippage (Spark & Grove) --- + + function _checkSetMaxSlippage(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + address pool = makeAddr("pool"); + uint256 slippage = 100; + + vm.prank(coreCouncil); + bytes32 id = wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + assertEq(ctrl.maxSlippages(pool), slippage); + } + + function testSetMaxSlippageOnSparkController() public { + _checkSetMaxSlippage(SPARK_CONTROLLER, keccak256("slipS")); + } + + function testSetMaxSlippageOnGroveController() public { + _checkSetMaxSlippage(GROVE_CONTROLLER, keccak256("slipG")); + } + + // --- setMaxExchangeRate (Spark & Grove) --- + + function _checkSetMaxExchangeRate(address controller, bytes32 salt) internal { + ControllerLike ctrl = ControllerLike(controller); + address sUSDS = 0x5875eEE11Cf8398102FdAd704C9E96607675467a; + uint256 shares = 1e18; + uint256 maxExpectedAssets = 1.1e18; + + vm.prank(coreCouncil); + bytes32 id = wrapper.setMaxExchangeRate(sUSDS, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(controller, data); + // maxExchangeRates stores value in 1e36 precision: maxExpectedAssets * 1e18 / shares * 1e18 = 1.1e36 + assertEq(ctrl.maxExchangeRates(sUSDS), 1.1e36); + } + + function testSetMaxExchangeRateOnSparkController() public { + _checkSetMaxExchangeRate(SPARK_CONTROLLER, keccak256("exrS")); + } + + function testSetMaxExchangeRateOnGroveController() public { + _checkSetMaxExchangeRate(GROVE_CONTROLLER, keccak256("exrG")); + } + + // --- setUniswapV3PoolMaxTickDelta (Grove Only) --- + + function testSetUniswapV3PoolMaxTickDeltaOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + address pool = makeAddr("pool"); + + vm.prank(coreCouncil); + bytes32 id = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), keccak256("tick"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + (uint24 swapMaxTickDelta,,,) = grove.uniswapV3PoolParams(pool); + assertEq(swapMaxTickDelta, 1000); + } + + // --- setUniswapV3AddLiquidityLowerTickBound (Grove Only) --- + + function testSetUniswapV3AddLiquidityLowerTickBoundOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + address pool = makeAddr("pool"); + + vm.prank(coreCouncil); + bytes32 id = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), keccak256("lower"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + (, int24 lowerTick,,) = grove.uniswapV3PoolParams(pool); + assertEq(lowerTick, -887220); + } + + // --- setUniswapV3AddLiquidityUpperTickBound (Grove Only) --- + + function testSetUniswapV3AddLiquidityUpperTickBoundOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + address pool = makeAddr("pool"); + + vm.prank(coreCouncil); + bytes32 id = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), keccak256("upper"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + (,, int24 upperTick,) = grove.uniswapV3PoolParams(pool); + assertEq(upperTick, 887220); + } + + // --- setUniswapV3TwapSecondsAgo (Grove Only) --- + + function testSetUniswapV3TwapSecondsAgoOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + address pool = makeAddr("pool"); + + vm.prank(coreCouncil); + bytes32 id = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), keccak256("twap"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + (,,, uint32 twapSecondsAgo) = grove.uniswapV3PoolParams(pool); + assertEq(twapSecondsAgo, 1800); + } + + // --- setCentrifugeRecipient (Grove Only) --- + + function testSetCentrifugeRecipientOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + uint16 centrifugeId = 1; + bytes32 recipient = bytes32(uint256(uint160(makeAddr("centrifuge")))); + + vm.prank(coreCouncil); + bytes32 id = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), keccak256("cent"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + assertEq(grove.centrifugeRecipients(centrifugeId), recipient); + } + + // --- setMerklDistributor (Grove Only) --- + + function testSetMerklDistributorOnGroveController() public { + ControllerLike grove = ControllerLike(GROVE_CONTROLLER); + address distributor = makeAddr("merklDistributor"); + + vm.prank(coreCouncil); + bytes32 id = wrapper.setMerklDistributor(distributor, GROVE_CONTROLLER, bytes32(0), keccak256("merkl"), MIN_DELAY); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(GROVE_CONTROLLER, data); + assertEq(grove.merklDistributor(), distributor); + } +} From 9a7fc5d639be5aed3ef1b3e64aa46068cab24b59 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:13:17 +0700 Subject: [PATCH 092/150] Add L2PASSpell --- deploy/L2PASSpell.sol | 54 ++++++++++++++++++++++ test/timelock/TimelockWrapperForeign.t.sol | 37 ++++++++++----- 2 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 deploy/L2PASSpell.sol diff --git a/deploy/L2PASSpell.sol b/deploy/L2PASSpell.sol new file mode 100644 index 0000000..912c476 --- /dev/null +++ b/deploy/L2PASSpell.sol @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +import { PASInit } from "./PASInit.sol"; +import { PASInstance } from "./PASInstance.sol"; + +// A reusable L2 spell for L2GovernanceRelay to initialize PAS contracts on a foreign chain. +// This spell should be viewed as skeleton/sample and can be altered prior to being used +// if further configurations are needed (for example adding more proposers). +contract L2PASSpell { + + address public immutable beamState; + address public immutable configurator; + address public immutable timelock; + address public immutable wrapper; + + constructor(address beamState_, address configurator_, address timelock_, address wrapper_) { + beamState = beamState_; + configurator = configurator_; + timelock = timelock_; + wrapper = wrapper_; + } + + function init( + uint256 minDelay, + address coreCouncil, + address[] memory cancellers, + address[] memory pausers + ) external { + PASInstance memory pas = PASInstance({ + beamState: beamState, + configurator: configurator, + timelock: timelock + }); + + PASInit.init(pas, minDelay, coreCouncil, cancellers, pausers); + PASInit.initTimelockWrapper(pas, wrapper, coreCouncil); + } +} diff --git a/test/timelock/TimelockWrapperForeign.t.sol b/test/timelock/TimelockWrapperForeign.t.sol index a859139..eb79981 100644 --- a/test/timelock/TimelockWrapperForeign.t.sol +++ b/test/timelock/TimelockWrapperForeign.t.sol @@ -22,8 +22,8 @@ import { Timelock } from "src/timelock/Timelock.sol"; import { BeamState } from "src/BeamState.sol"; import { Configurator } from "src/Configurator.sol"; import { PASDeploy } from "deploy/PASDeploy.sol"; -import { PASInit } from "deploy/PASInit.sol"; import { PASInstance } from "deploy/PASInstance.sol"; +import { L2PASSpell } from "deploy/L2PASSpell.sol"; interface ControllerLike { function hasRole(bytes32 role, address account) external view returns (bool); @@ -49,6 +49,18 @@ interface RateLimitsLike { function getRateLimitData(bytes32 key) external view returns (uint256, uint256, uint256, uint256); } +contract GovRelayMock { + function relay(address target, bytes calldata targetData) external { + (bool success, bytes memory result) = target.delegatecall(targetData); + if (!success) { + if (result.length == 0) revert("L2GovernanceRelay/delegatecall-error"); + assembly ("memory-safe") { + revert(add(32, result), mload(result)) + } + } + } +} + contract TimelockWrapperForeignTest is DssTest { // --- Events --- event Kiss(address indexed usr); @@ -63,8 +75,6 @@ contract TimelockWrapperForeignTest is DssTest { address constant GROVE_CONTROLLER = 0x7f8408eBbBC3504F83eeDa52910dd75Eba92C955; address constant GROVE_PROXY = 0x491EDFB0B8b608044e227225C715981a30F3A44E; - address constant GOV_RELAY = 0xdD0BCc201C9E47c6F6eE68E4dB05b652Bb6aC255; - uint256 constant FORK_BLOCK = 41300000; uint256 constant MIN_DELAY = 1 days; @@ -80,8 +90,9 @@ contract TimelockWrapperForeignTest is DssTest { Timelock timelock; TimelockWrapperForeign wrapper; - address coreCouncil; - address cBeam; + GovRelayMock govRelay; + address coreCouncil; + address cBeam; function setUp() public { vm.createSelectFork(vm.envString("BASE_RPC_URL"), FORK_BLOCK); @@ -90,19 +101,21 @@ contract TimelockWrapperForeignTest is DssTest { SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); GROVE_RATE_LIMITS = ControllerLike(GROVE_CONTROLLER).rateLimits(); + govRelay = new GovRelayMock(); coreCouncil = makeAddr("coreCouncil"); cBeam = makeAddr("cBeam"); - PASInstance memory pas = PASDeploy.deploy(address(this), GOV_RELAY, MIN_DELAY); + PASInstance memory pas = PASDeploy.deploy(address(this), address(govRelay), MIN_DELAY); beamState = BeamState(pas.beamState); configurator = Configurator(pas.configurator); timelock = Timelock(payable(pas.timelock)); - wrapper = TimelockWrapperForeign(PASDeploy.deployTimelockWrapperForeign(address(this), GOV_RELAY, pas.timelock, pas.beamState)); + wrapper = TimelockWrapperForeign(PASDeploy.deployTimelockWrapperForeign(address(this), address(govRelay), pas.timelock, pas.beamState)); - vm.startPrank(GOV_RELAY); - PASInit.init(pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); - PASInit.initTimelockWrapper(pas, address(wrapper), coreCouncil); - vm.stopPrank(); + L2PASSpell spell = new L2PASSpell(pas.beamState, pas.configurator, pas.timelock, address(wrapper)); + govRelay.relay( + address(spell), + abi.encodeWithSelector(spell.init.selector, MIN_DELAY, coreCouncil, new address[](0), new address[](0)) + ); // Grant configurator admin role on Base controllers and rate limiters vm.startPrank(SPARK_PROXY); @@ -185,7 +198,7 @@ contract TimelockWrapperForeignTest is DssTest { function testKissDiss() public { address usr = makeAddr("usr"); - vm.startPrank(GOV_RELAY); + vm.startPrank(address(govRelay)); assertEq(wrapper.buds(usr), 0); vm.expectEmit(true, false, false, true); emit Kiss(usr); From e3a5c19b086da6d3e18ccd3942146ee227ff0212 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Tue, 27 Jan 2026 07:52:43 -0300 Subject: [PATCH 093/150] Minor changes Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- deploy/PASInit.sol | 2 +- src/BeamState.sol | 2 +- src/timelock/TimelockWrapper.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 3aa6962..ae32854 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -118,7 +118,7 @@ library PASInit { beamState.setUserRole(address(timelock), uint8(Role.DELAYED), true); beamState.setUserRole(coreCouncil, uint8(Role.IMMEDIATE), true); - // --- Configure Timelock and Wrapper --- + // --- Configure Timelock --- // Grant the coreCouncil as the proposer in the timelock directly // Grant cancellers and pausers in timelock with their respective roles diff --git a/src/BeamState.sol b/src/BeamState.sol index 1720c7f..d4335a4 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -32,7 +32,7 @@ contract BeamState { mapping(address rateLimits_ => mapping(address cBeam => uint256 allowed)) public rateLimitsCBeams; // allowed == 0 => false, allowed == 1 => true mapping(address controller => mapping(address cBeam => uint256 allowed)) public controllersCBeams; // allowed == 0 => false, allowed == 1 => true mapping(bytes32 key => mapping(address rateLimits_ => DefaultRateLimits limit)) public initRateLimits; // rateLimits == address(0) every rateLimits allowed - mapping(bytes32 key => mapping(address rateLimits_ => bool allowed)) public initControllerActions; // rateLimits == address(0) every rateLimits allowed + mapping(bytes32 key => mapping(address controller => bool allowed)) public initControllerActions; // controller == address(0) every controller allowed mapping(address rateLimits_ => uint256 value) public hop; // rateLimits == address(0) => general backup configuration mapping(address rateLimits_ => uint256 value) public maxChange; // rateLimits == address(0) => general backup configuration diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index f761e5e..576455c 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -79,7 +79,7 @@ struct RateLimitConfig { // Notes: // - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). // - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. -// - The actual downstream changes only take effect when cBEAMs use the BEAMState configurations, so atomicity in configurations can not be assumed (which is a known issue). +// - The actual downstream changes only take effect when cBEAMs use the BeamState configurations, so atomicity in configurations can not be assumed (which is a known issue). // - As part of a controller onboarding it might need to be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. contract TimelockWrapper { // --- Auth --- From 9ef53d659ef692a05155ad73ebe1770d9e687163 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 27 Jan 2026 14:57:12 -0300 Subject: [PATCH 094/150] CI: fix base rpc url --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5515439..a82605e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,4 +33,5 @@ jobs: forge test -vvv id: test env: - ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} + ETH_RPC_URL : ${{ secrets.ETH_RPC_URL }} + BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} From 57c67c270b4c34e8191edb32186fb04b5516509e Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 27 Jan 2026 15:03:20 -0300 Subject: [PATCH 095/150] Remove specific block from rpc tests --- test/Integration.t.sol | 3 +-- test/timelock/TimelockWrapper.t.sol | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 466b619..d304680 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -61,7 +61,6 @@ contract IntegrationTest is DssTest { address constant SPARK_CONTROLLER = 0xE52d643B27601D4d2BAB2052f30cf936ed413cec; address constant SPARK_PROXY = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; - uint256 constant FORK_BLOCK = 24250000; bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); // Fetched from controller @@ -103,7 +102,7 @@ contract IntegrationTest is DssTest { function setUp() public { // Fork mainnet at specified block - vm.createSelectFork(vm.envString("ETH_RPC_URL"), FORK_BLOCK); + vm.createSelectFork(vm.envString("ETH_RPC_URL")); // Load DssInstance from chainlog dss = MCD.loadFromChainlog(CHAINLOG); diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapper.t.sol index 601640f..3ab09da 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapper.t.sol @@ -69,7 +69,6 @@ contract TimelockWrapperTest is DssTest { address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; - uint256 constant FORK_BLOCK = 24250000; uint256 constant MIN_DELAY = 1 days; bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); @@ -91,7 +90,7 @@ contract TimelockWrapperTest is DssTest { address cBeam; function setUp() public { - vm.createSelectFork(vm.envString("ETH_RPC_URL"), FORK_BLOCK); + vm.createSelectFork(vm.envString("ETH_RPC_URL")); // Load DssInstance from chainlog dss = MCD.loadFromChainlog(CHAINLOG); From 287c461430fca5cf8b1311df830bba2da868ea92 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 27 Jan 2026 15:47:15 -0300 Subject: [PATCH 096/150] Remove specific block from rpc tests --- test/timelock/TimelockWrapperForeign.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/timelock/TimelockWrapperForeign.t.sol b/test/timelock/TimelockWrapperForeign.t.sol index eb79981..49548e8 100644 --- a/test/timelock/TimelockWrapperForeign.t.sol +++ b/test/timelock/TimelockWrapperForeign.t.sol @@ -75,7 +75,6 @@ contract TimelockWrapperForeignTest is DssTest { address constant GROVE_CONTROLLER = 0x7f8408eBbBC3504F83eeDa52910dd75Eba92C955; address constant GROVE_PROXY = 0x491EDFB0B8b608044e227225C715981a30F3A44E; - uint256 constant FORK_BLOCK = 41300000; uint256 constant MIN_DELAY = 1 days; bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); @@ -95,7 +94,7 @@ contract TimelockWrapperForeignTest is DssTest { address cBeam; function setUp() public { - vm.createSelectFork(vm.envString("BASE_RPC_URL"), FORK_BLOCK); + vm.createSelectFork(vm.envString("BASE_RPC_URL")); // Fetch rate limits from controllers SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); From 4e46251b52990f18e51cefe1fcc48af0ae424f3c Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 27 Jan 2026 18:43:48 -0300 Subject: [PATCH 097/150] Reorder function in TimelockWrapper and fix comments --- src/timelock/TimelockWrapper.sol | 47 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapper.sol index 576455c..b709168 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapper.sol @@ -233,7 +233,7 @@ contract TimelockWrapper { // --- Controller Actions --- - // Spark functions + // Shared Spark & Grove functions // Role bytes32 can be computed off-chain and passed as parameter, e.g keccak256("RELAYER"), keccak256("FREEZER") function grantRole( @@ -331,6 +331,29 @@ contract TimelockWrapper { emit ProposalSubmitted(operationId, "setMaxSlippage"); } + function setMaxExchangeRate( + address token, + uint256 shares, + uint256 maxExpectedAssets, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setMaxExchangeRate.selector, + token, + shares, + maxExpectedAssets + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setMaxExchangeRate"); + } + + // Spark-only functions + function setOTCBuffer( address exchange, address otcBuffer, @@ -389,27 +412,6 @@ contract TimelockWrapper { operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCWhitelistedAsset"); } - - function setMaxExchangeRate( - address token, - uint256 shares, - uint256 maxExpectedAssets, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setMaxExchangeRate.selector, - token, - shares, - maxExpectedAssets - ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMaxExchangeRate"); - } // Grove-only functions @@ -508,4 +510,3 @@ contract TimelockWrapper { emit ProposalSubmitted(operationId, "setCentrifugeRecipient"); } } - From cc3a937f82d8e73802cd872ba8186955fcceab61 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 27 Jan 2026 19:25:06 -0300 Subject: [PATCH 098/150] Rename TimelockWrapper to TimelockWrapperMainnet --- README.md | 2 +- deploy/PASDeploy.sol | 10 +-- deploy/PASInit.sol | 18 ++--- ...Wrapper.sol => TimelockWrapperMainnet.sol} | 6 +- test/Integration.t.sol | 78 +++++++++---------- ...per.t.sol => TimelockWrapperMainnet.t.sol} | 24 +++--- 6 files changed, 69 insertions(+), 69 deletions(-) rename src/timelock/{TimelockWrapper.sol => TimelockWrapperMainnet.sol} (99%) rename test/timelock/{TimelockWrapper.t.sol => TimelockWrapperMainnet.t.sol} (97%) diff --git a/README.md b/README.md index 3bcdba6..dbce5c0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The operational interface used by cBeams to modify rate limits and execute contr Extended OpenZeppelin TimelockController with pausing support, permissionless execution, and operation tracking for keeper integration. Disables self-calls to prevent proposals from modifying admin settings. Provides helper functions (`getNextExecutableOperation`, `getOperationIndex`) for automated execution via keeper jobs. -### TimelockWrapper +### TimelockWrapperMainnet Convenience contract for proposers to schedule batched BeamState configuration changes through the timelock. Provides type-safe wrappers for common operations like adding rate limits, controllers, cBeams, and their initial configurations. Authorized users (`buds`) can schedule proposals that execute after the timelock delay. diff --git a/deploy/PASDeploy.sol b/deploy/PASDeploy.sol index 4afc7bd..6a84ba7 100644 --- a/deploy/PASDeploy.sol +++ b/deploy/PASDeploy.sol @@ -21,7 +21,7 @@ import { PASInstance } from "./PASInstance.sol"; import { BeamState } from "src/BeamState.sol"; import { Configurator } from "src/Configurator.sol"; import { Timelock } from "src/timelock/Timelock.sol"; -import { TimelockWrapper } from "src/timelock/TimelockWrapper.sol"; +import { TimelockWrapperMainnet } from "src/timelock/TimelockWrapperMainnet.sol"; import { PASMom } from "src/PASMom.sol"; library PASDeploy { @@ -52,16 +52,16 @@ library PASDeploy { PASMom(mom).setOwner(owner); } - function deployTimelockWrapper( + function deployTimelockWrapperMainnet( address deployer, address owner, address timelock, address beamState - ) internal returns (address timelockWrapper) { - timelockWrapper = address(new TimelockWrapper( + ) internal returns (address timelockWrapperMainnet) { + timelockWrapperMainnet = address(new TimelockWrapperMainnet( timelock, beamState )); - ScriptTools.switchOwner(timelockWrapper, deployer, owner); + ScriptTools.switchOwner(timelockWrapperMainnet, deployer, owner); } } diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index ae32854..74035a5 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -55,7 +55,7 @@ interface TimelockLike { function grantRole(bytes32, address) external; } -interface TimelockWrapperLike { +interface TimelockWrapperMainnetLike { function timelock() external view returns (address); function beamState() external view returns (address); function kiss(address) external; @@ -169,23 +169,23 @@ library PASInit { dss.chainlog.setAddress("PAS_MOM", address(mom)); } - function initTimelockWrapper( + function initTimelockWrapperMainnet( PASInstance memory pasInstance, - address timelockWrapper_, + address timelockWrapperMainnet_, address coreCouncil ) internal { - TimelockLike timelock = TimelockLike(pasInstance.timelock); - TimelockWrapperLike timelockWrapper = TimelockWrapperLike(timelockWrapper_); + TimelockLike timelock = TimelockLike(pasInstance.timelock); + TimelockWrapperMainnetLike timelockWrapperMainnet = TimelockWrapperMainnetLike(timelockWrapperMainnet_); // --- Sanity checks --- - require(timelockWrapper.timelock() == pasInstance.timelock, "PASInit/wrapper-timelock-mismatch"); - require(timelockWrapper.beamState() == pasInstance.beamState, "PASInit/wrapper-beamState-mismatch"); + require(timelockWrapperMainnet.timelock() == pasInstance.timelock, "PASInit/wrapper-timelock-mismatch"); + require(timelockWrapperMainnet.beamState() == pasInstance.beamState, "PASInit/wrapper-beamState-mismatch"); // --- Set permissions --- // Grant the coreCouncil as the proposer through the wrapper - timelock.grantRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)); - timelockWrapper.kiss(coreCouncil); + timelock.grantRole(timelock.PROPOSER_ROLE(), address(timelockWrapperMainnet)); + timelockWrapperMainnet.kiss(coreCouncil); } } diff --git a/src/timelock/TimelockWrapper.sol b/src/timelock/TimelockWrapperMainnet.sol similarity index 99% rename from src/timelock/TimelockWrapper.sol rename to src/timelock/TimelockWrapperMainnet.sol index b709168..ac2f2b0 100644 --- a/src/timelock/TimelockWrapper.sol +++ b/src/timelock/TimelockWrapperMainnet.sol @@ -81,18 +81,18 @@ struct RateLimitConfig { // - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. // - The actual downstream changes only take effect when cBEAMs use the BeamState configurations, so atomicity in configurations can not be assumed (which is a known issue). // - As part of a controller onboarding it might need to be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. -contract TimelockWrapper { +contract TimelockWrapperMainnet { // --- Auth --- mapping(address => uint256) public wards; mapping(address => uint256) public buds; modifier auth() { - require(wards[msg.sender] == 1, "TimelockWrapper/not-authorized"); + require(wards[msg.sender] == 1, "TimelockWrapperMainnet/not-authorized"); _; } modifier toll() { - require(buds[msg.sender] == 1, "TimelockWrapper/not-whitelisted"); + require(buds[msg.sender] == 1, "TimelockWrapperMainnet/not-whitelisted"); _; } diff --git a/test/Integration.t.sol b/test/Integration.t.sol index d304680..12fd663 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -24,7 +24,7 @@ import { PASInit } from "deploy/PASInit.sol"; import { BeamState } from "src/BeamState.sol"; import { Configurator, RateLimitsLike } from "src/Configurator.sol"; import { Timelock } from "src/timelock/Timelock.sol"; -import { TimelockWrapper, RateLimitConfig } from "src/timelock/TimelockWrapper.sol"; +import { TimelockWrapperMainnet, RateLimitConfig } from "src/timelock/TimelockWrapperMainnet.sol"; import { PASMom } from "src/PASMom.sol"; interface ChiefLike { @@ -66,14 +66,14 @@ contract IntegrationTest is DssTest { // Fetched from controller address SPARK_RATE_LIMITS; - DssInstance dss; - PASInstance pas; - BeamState beamState; - Configurator configurator; - Timelock timelock; - TimelockWrapper timelockWrapper; - PASMom mom; - ChiefLike chief; + DssInstance dss; + PASInstance pas; + BeamState beamState; + Configurator configurator; + Timelock timelock; + TimelockWrapperMainnet timelockWrapperMainnet; + PASMom mom; + ChiefLike chief; address pauseProxy; // Timelock admin address coreCouncil; // Has IMMEDIATE role on BeamState, proposer/canceller on Timelock @@ -123,9 +123,9 @@ contract IntegrationTest is DssTest { beamState = BeamState(pas.beamState); configurator = Configurator(pas.configurator); timelock = Timelock(payable(pas.timelock)); - // Deploy Mom and TimelockWrapper separately - mom = PASMom(PASDeploy.deployMom(pauseProxy, pas.beamState, pas.timelock)); - timelockWrapper = TimelockWrapper(PASDeploy.deployTimelockWrapper(address(this), pauseProxy, pas.timelock, pas.beamState)); + // Deploy Mom and TimelockWrapperMainnet separately + mom = PASMom(PASDeploy.deployMom(pauseProxy, pas.beamState, pas.timelock)); + timelockWrapperMainnet = TimelockWrapperMainnet(PASDeploy.deployTimelockWrapperMainnet(address(this), pauseProxy, pas.timelock, pas.beamState)); chief = ChiefLike(dss.chainlog.getAddress("MCD_ADM")); @@ -140,7 +140,7 @@ contract IntegrationTest is DssTest { PASInit.init(pas, MIN_DELAY, coreCouncil, cancellers, pausers); PASInit.addCoreToChainlog(dss, pas); PASInit.initMom(dss, pas, address(mom)); - PASInit.initTimelockWrapper(pas, address(timelockWrapper), coreCouncil); + PASInit.initTimelockWrapperMainnet(pas, address(timelockWrapperMainnet), coreCouncil); vm.stopPrank(); } @@ -153,7 +153,7 @@ contract IntegrationTest is DssTest { assertTrue(pas.configurator != address(0), "configurator should be deployed"); assertTrue(pas.timelock != address(0), "timelock should be deployed"); assertTrue(address(mom) != address(0), "mom should be deployed"); - assertTrue(address(timelockWrapper) != address(0), "timelockWrapper should be deployed"); + assertTrue(address(timelockWrapperMainnet) != address(0), "timelockWrapperMainnet should be deployed"); } function testConfiguratorLinkedToBeamState() public view { @@ -166,9 +166,9 @@ contract IntegrationTest is DssTest { assertEq(mom.owner(), pauseProxy, "mom should be owned by pauseProxy"); } - function testTimelockWrapperLinkedCorrectly() public view { - assertEq(address(timelockWrapper.timelock()), address(timelock), "wrapper should reference timelock"); - assertEq(address(timelockWrapper.beamState()), address(beamState), "wrapper should reference beamState"); + function testTimelockWrapperMainnetLinkedCorrectly() public view { + assertEq(address(timelockWrapperMainnet.timelock()), address(timelock), "wrapper should reference timelock"); + assertEq(address(timelockWrapperMainnet.beamState()), address(beamState), "wrapper should reference beamState"); } function testChainlogEntriesAfterInit() public view { @@ -209,14 +209,14 @@ contract IntegrationTest is DssTest { assertTrue(beamState.hasUserRole(coreCouncil, uint8(PASInit.Role.IMMEDIATE)), "coreCouncil should have IMMEDIATE role"); } - function testTimelockWrapperBudsAfterInit() public view { - assertEq(timelockWrapper.buds(coreCouncil), 1, "coreCouncil should be whitelisted on wrapper"); + function testTimelockWrapperMainnetBudsAfterInit() public view { + assertEq(timelockWrapperMainnet.buds(coreCouncil), 1, "coreCouncil should be whitelisted on wrapper"); } function testTimelockRolesAfterInit() public view { assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), pauseProxy), "pauseProxy should be admin"); assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should be proposer"); - assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)), "wrapper should be proposer"); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), address(timelockWrapperMainnet)), "wrapper should be proposer"); assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), coreCouncil), "coreCouncil should be canceller"); assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller), "canceller should be canceller"); assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser), "pauser should be pauser"); @@ -468,7 +468,7 @@ contract IntegrationTest is DssTest { } // ============================================================================ - // TimelockWrapper Tests + // TimelockWrapperMainnet Tests // ============================================================================ function testWrapperCanSchedule() public { @@ -479,7 +479,7 @@ contract IntegrationTest is DssTest { // Schedule start via wrapper vm.prank(coreCouncil); - bytes32 operationId = timelockWrapper.start(bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = timelockWrapperMainnet.start(bytes32(0), SALT, MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); @@ -496,7 +496,7 @@ contract IntegrationTest is DssTest { function testCoreCouncilCanCancelOperation() public { // Schedule operation vm.prank(coreCouncil); - bytes32 operationId = timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); assertTrue(timelock.isOperationPending(operationId), "operation should be pending"); @@ -510,7 +510,7 @@ contract IntegrationTest is DssTest { function testCancellerCanCancelOperation() public { vm.prank(coreCouncil); - bytes32 operationId = timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); vm.prank(canceller); timelock.cancel(operationId); @@ -535,13 +535,13 @@ contract IntegrationTest is DssTest { vm.prank(coreCouncil); vm.expectRevert(); - timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); } function testPausedTimelockBlocksExecution() public { // Schedule first vm.prank(coreCouncil); - bytes32 operationId = timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); @@ -621,15 +621,15 @@ contract IntegrationTest is DssTest { function testFullOnboardingWorkflow() public { // 1. CoreCouncil schedules adding a cBeam via wrapper vm.prank(coreCouncil); - bytes32 addCBeamOp = timelockWrapper.addCBeam(cBeam, bytes32(0), keccak256("step1"), MIN_DELAY); + bytes32 addCBeamOp = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), keccak256("step1"), MIN_DELAY); // 2. Schedule addRateLimits with predecessor vm.prank(coreCouncil); - bytes32 addRateLimitsOp = timelockWrapper.addRateLimits(SPARK_RATE_LIMITS, addCBeamOp, keccak256("step2"), MIN_DELAY); + bytes32 addRateLimitsOp = timelockWrapperMainnet.addRateLimits(SPARK_RATE_LIMITS, addCBeamOp, keccak256("step2"), MIN_DELAY); // 3. Schedule setHop with predecessor vm.prank(coreCouncil); - bytes32 setHopOp = timelockWrapper.setHop(SPARK_RATE_LIMITS, 4 hours, addRateLimitsOp, keccak256("step3"), MIN_DELAY); + bytes32 setHopOp = timelockWrapperMainnet.setHop(SPARK_RATE_LIMITS, 4 hours, addRateLimitsOp, keccak256("step3"), MIN_DELAY); // Wait for delay vm.warp(block.timestamp + MIN_DELAY); @@ -659,7 +659,7 @@ contract IntegrationTest is DssTest { // 7. Restart requires timelock vm.prank(coreCouncil); - bytes32 startOp = timelockWrapper.start(bytes32(0), keccak256("restart"), MIN_DELAY); + bytes32 startOp = timelockWrapperMainnet.start(bytes32(0), keccak256("restart"), MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); @@ -786,13 +786,13 @@ contract IntegrationTest is DssTest { // Scheduling via wrapper is blocked vm.prank(coreCouncil); vm.expectRevert(); - timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); } function testMomPauseBlocksTimelockExecution() public { // Schedule an operation first vm.prank(coreCouncil); - bytes32 operationId = timelockWrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); @@ -842,27 +842,27 @@ contract IntegrationTest is DssTest { { // 1a. Schedule addCBeam vm.prank(coreCouncil); - opIds[0] = timelockWrapper.addCBeam(cBeam, bytes32(0), keccak256("addCBeam"), MIN_DELAY); + opIds[0] = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), keccak256("addCBeam"), MIN_DELAY); // 1b. Schedule addRateLimits for SPARK_RATE_LIMITS vm.prank(coreCouncil); - opIds[1] = timelockWrapper.addRateLimits(SPARK_RATE_LIMITS, opIds[0], keccak256("addRateLimits"), MIN_DELAY); + opIds[1] = timelockWrapperMainnet.addRateLimits(SPARK_RATE_LIMITS, opIds[0], keccak256("addRateLimits"), MIN_DELAY); // 1c. Schedule addController for SPARK_CONTROLLER vm.prank(coreCouncil); - opIds[2] = timelockWrapper.addController(SPARK_CONTROLLER, opIds[1], keccak256("addController"), MIN_DELAY); + opIds[2] = timelockWrapperMainnet.addController(SPARK_CONTROLLER, opIds[1], keccak256("addController"), MIN_DELAY); // 1d. Schedule setHop for rateLimits vm.prank(coreCouncil); - opIds[3] = timelockWrapper.setHop(SPARK_RATE_LIMITS, 1 hours, opIds[2], keccak256("setHop"), MIN_DELAY); + opIds[3] = timelockWrapperMainnet.setHop(SPARK_RATE_LIMITS, 1 hours, opIds[2], keccak256("setHop"), MIN_DELAY); // 1e. Schedule setMaxChange for rateLimits vm.prank(coreCouncil); - opIds[4] = timelockWrapper.setMaxChange(SPARK_RATE_LIMITS, 2 ether, opIds[3], keccak256("setMaxChange"), MIN_DELAY); + opIds[4] = timelockWrapperMainnet.setMaxChange(SPARK_RATE_LIMITS, 2 ether, opIds[3], keccak256("setMaxChange"), MIN_DELAY); // 1f. Schedule addInitRateLimits (via wrapper with RateLimitConfig) vm.prank(coreCouncil); - opIds[5] = timelockWrapper.addInitRateLimits( + opIds[5] = timelockWrapperMainnet.addInitRateLimits( RateLimitConfig({key: rateLimitKey, rateLimits: SPARK_RATE_LIMITS, maxAmount: 1_000_000e18, slope: 100_000e18}), opIds[4], keccak256("addInitRateLimits"), MIN_DELAY ); @@ -975,7 +975,7 @@ contract IntegrationTest is DssTest { // 4e. Operations resume after restart (via timelock) vm.prank(coreCouncil); - bytes32 startOp = timelockWrapper.start(bytes32(0), keccak256("restart"), MIN_DELAY); + bytes32 startOp = timelockWrapperMainnet.start(bytes32(0), keccak256("restart"), MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); { diff --git a/test/timelock/TimelockWrapper.t.sol b/test/timelock/TimelockWrapperMainnet.t.sol similarity index 97% rename from test/timelock/TimelockWrapper.t.sol rename to test/timelock/TimelockWrapperMainnet.t.sol index 3ab09da..94da83f 100644 --- a/test/timelock/TimelockWrapper.t.sol +++ b/test/timelock/TimelockWrapperMainnet.t.sol @@ -18,7 +18,7 @@ pragma solidity ^0.8.24; import "dss-test/DssTest.sol"; import { MCD, DssInstance } from "dss-test/MCD.sol"; -import { TimelockWrapper, RateLimitConfig } from "src/timelock/TimelockWrapper.sol"; +import { TimelockWrapperMainnet, RateLimitConfig } from "src/timelock/TimelockWrapperMainnet.sol"; import { Timelock } from "src/timelock/Timelock.sol"; import { BeamState } from "src/BeamState.sol"; import { Configurator } from "src/Configurator.sol"; @@ -53,7 +53,7 @@ interface RateLimitsLike { function getRateLimitData(bytes32 key) external view returns (uint256, uint256, uint256, uint256); } -contract TimelockWrapperTest is DssTest { +contract TimelockWrapperMainnetTest is DssTest { // --- Events --- event Kiss(address indexed usr); event Diss(address indexed usr); @@ -80,10 +80,10 @@ contract TimelockWrapperTest is DssTest { address GROVE_RATE_LIMITS; // --- PAS Instance --- - BeamState beamState; - Configurator configurator; - Timelock timelock; - TimelockWrapper wrapper; + BeamState beamState; + Configurator configurator; + Timelock timelock; + TimelockWrapperMainnet wrapper; address pauseProxy; address coreCouncil; @@ -108,11 +108,11 @@ contract TimelockWrapperTest is DssTest { beamState = BeamState(pas.beamState); configurator = Configurator(pas.configurator); timelock = Timelock(payable(pas.timelock)); - wrapper = TimelockWrapper(PASDeploy.deployTimelockWrapper(address(this), pauseProxy, pas.timelock, pas.beamState)); + wrapper = TimelockWrapperMainnet(PASDeploy.deployTimelockWrapperMainnet(address(this), pauseProxy, pas.timelock, pas.beamState)); vm.startPrank(pauseProxy); PASInit.init(pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); - PASInit.initTimelockWrapper(pas, address(wrapper), coreCouncil); + PASInit.initTimelockWrapperMainnet(pas, address(wrapper), coreCouncil); vm.stopPrank(); // Grant configurator admin role on mainnet controllers and rate limiters @@ -182,7 +182,7 @@ contract TimelockWrapperTest is DssTest { function testConstructor() public { vm.expectEmit(true, false, false, true); emit Rely(address(this)); - TimelockWrapper newWrapper = new TimelockWrapper(address(timelock), address(beamState)); + TimelockWrapperMainnet newWrapper = new TimelockWrapperMainnet(address(timelock), address(beamState)); assertEq(address(newWrapper.timelock()), address(timelock), "Timelock set correctly"); assertEq(address(newWrapper.beamState()), address(beamState), "BeamState set correctly"); @@ -190,7 +190,7 @@ contract TimelockWrapperTest is DssTest { } function testAuth() public { - checkAuth(address(wrapper), "TimelockWrapper"); + checkAuth(address(wrapper), "TimelockWrapperMainnet"); } function testKissDiss() public { @@ -215,7 +215,7 @@ contract TimelockWrapperTest is DssTest { authedMethods[1] = wrapper.diss.selector; vm.startPrank(address(0xBEEF)); - checkModifier(address(wrapper), "TimelockWrapper/not-authorized", authedMethods); + checkModifier(address(wrapper), "TimelockWrapperMainnet/not-authorized", authedMethods); vm.stopPrank(); } @@ -245,7 +245,7 @@ contract TimelockWrapperTest is DssTest { tolledMethods[21] = wrapper.setCentrifugeRecipient.selector; vm.startPrank(address(0xBEEF)); - checkModifier(address(wrapper), "TimelockWrapper/not-whitelisted", tolledMethods); + checkModifier(address(wrapper), "TimelockWrapperMainnet/not-whitelisted", tolledMethods); vm.stopPrank(); } From 40fba65d4d38b255ee4bdf582e43d600eee0069a Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 27 Jan 2026 19:36:52 -0300 Subject: [PATCH 099/150] Rollback rename in PASInit and simplify some tests variable name --- deploy/PASInit.sol | 18 +++---- test/Integration.t.sol | 60 +++++++++++----------- test/timelock/TimelockWrapperMainnet.t.sol | 2 +- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 74035a5..ae32854 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -55,7 +55,7 @@ interface TimelockLike { function grantRole(bytes32, address) external; } -interface TimelockWrapperMainnetLike { +interface TimelockWrapperLike { function timelock() external view returns (address); function beamState() external view returns (address); function kiss(address) external; @@ -169,23 +169,23 @@ library PASInit { dss.chainlog.setAddress("PAS_MOM", address(mom)); } - function initTimelockWrapperMainnet( + function initTimelockWrapper( PASInstance memory pasInstance, - address timelockWrapperMainnet_, + address timelockWrapper_, address coreCouncil ) internal { - TimelockLike timelock = TimelockLike(pasInstance.timelock); - TimelockWrapperMainnetLike timelockWrapperMainnet = TimelockWrapperMainnetLike(timelockWrapperMainnet_); + TimelockLike timelock = TimelockLike(pasInstance.timelock); + TimelockWrapperLike timelockWrapper = TimelockWrapperLike(timelockWrapper_); // --- Sanity checks --- - require(timelockWrapperMainnet.timelock() == pasInstance.timelock, "PASInit/wrapper-timelock-mismatch"); - require(timelockWrapperMainnet.beamState() == pasInstance.beamState, "PASInit/wrapper-beamState-mismatch"); + require(timelockWrapper.timelock() == pasInstance.timelock, "PASInit/wrapper-timelock-mismatch"); + require(timelockWrapper.beamState() == pasInstance.beamState, "PASInit/wrapper-beamState-mismatch"); // --- Set permissions --- // Grant the coreCouncil as the proposer through the wrapper - timelock.grantRole(timelock.PROPOSER_ROLE(), address(timelockWrapperMainnet)); - timelockWrapperMainnet.kiss(coreCouncil); + timelock.grantRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)); + timelockWrapper.kiss(coreCouncil); } } diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 12fd663..d948d89 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -71,7 +71,7 @@ contract IntegrationTest is DssTest { BeamState beamState; Configurator configurator; Timelock timelock; - TimelockWrapperMainnet timelockWrapperMainnet; + TimelockWrapperMainnet wrapper; PASMom mom; ChiefLike chief; @@ -123,9 +123,9 @@ contract IntegrationTest is DssTest { beamState = BeamState(pas.beamState); configurator = Configurator(pas.configurator); timelock = Timelock(payable(pas.timelock)); - // Deploy Mom and TimelockWrapperMainnet separately - mom = PASMom(PASDeploy.deployMom(pauseProxy, pas.beamState, pas.timelock)); - timelockWrapperMainnet = TimelockWrapperMainnet(PASDeploy.deployTimelockWrapperMainnet(address(this), pauseProxy, pas.timelock, pas.beamState)); + // Deploy Mom and TimelockWrapper separately + mom = PASMom(PASDeploy.deployMom(pauseProxy, pas.beamState, pas.timelock)); + wrapper = TimelockWrapperMainnet(PASDeploy.deployTimelockWrapperMainnet(address(this), pauseProxy, pas.timelock, pas.beamState)); chief = ChiefLike(dss.chainlog.getAddress("MCD_ADM")); @@ -140,7 +140,7 @@ contract IntegrationTest is DssTest { PASInit.init(pas, MIN_DELAY, coreCouncil, cancellers, pausers); PASInit.addCoreToChainlog(dss, pas); PASInit.initMom(dss, pas, address(mom)); - PASInit.initTimelockWrapperMainnet(pas, address(timelockWrapperMainnet), coreCouncil); + PASInit.initTimelockWrapper(pas, address(wrapper), coreCouncil); vm.stopPrank(); } @@ -153,7 +153,7 @@ contract IntegrationTest is DssTest { assertTrue(pas.configurator != address(0), "configurator should be deployed"); assertTrue(pas.timelock != address(0), "timelock should be deployed"); assertTrue(address(mom) != address(0), "mom should be deployed"); - assertTrue(address(timelockWrapperMainnet) != address(0), "timelockWrapperMainnet should be deployed"); + assertTrue(address(wrapper) != address(0), "wrapper should be deployed"); } function testConfiguratorLinkedToBeamState() public view { @@ -166,9 +166,9 @@ contract IntegrationTest is DssTest { assertEq(mom.owner(), pauseProxy, "mom should be owned by pauseProxy"); } - function testTimelockWrapperMainnetLinkedCorrectly() public view { - assertEq(address(timelockWrapperMainnet.timelock()), address(timelock), "wrapper should reference timelock"); - assertEq(address(timelockWrapperMainnet.beamState()), address(beamState), "wrapper should reference beamState"); + function testTimelockWrapperLinkedCorrectly() public view { + assertEq(address(wrapper.timelock()), address(timelock), "wrapper should reference timelock"); + assertEq(address(wrapper.beamState()), address(beamState), "wrapper should reference beamState"); } function testChainlogEntriesAfterInit() public view { @@ -209,14 +209,14 @@ contract IntegrationTest is DssTest { assertTrue(beamState.hasUserRole(coreCouncil, uint8(PASInit.Role.IMMEDIATE)), "coreCouncil should have IMMEDIATE role"); } - function testTimelockWrapperMainnetBudsAfterInit() public view { - assertEq(timelockWrapperMainnet.buds(coreCouncil), 1, "coreCouncil should be whitelisted on wrapper"); + function testTimelockWrapperBudsAfterInit() public view { + assertEq(wrapper.buds(coreCouncil), 1, "coreCouncil should be whitelisted on wrapper"); } function testTimelockRolesAfterInit() public view { assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), pauseProxy), "pauseProxy should be admin"); assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should be proposer"); - assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), address(timelockWrapperMainnet)), "wrapper should be proposer"); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), address(wrapper)), "wrapper should be proposer"); assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), coreCouncil), "coreCouncil should be canceller"); assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller), "canceller should be canceller"); assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser), "pauser should be pauser"); @@ -479,7 +479,7 @@ contract IntegrationTest is DssTest { // Schedule start via wrapper vm.prank(coreCouncil); - bytes32 operationId = timelockWrapperMainnet.start(bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = wrapper.start(bytes32(0), SALT, MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); @@ -496,7 +496,7 @@ contract IntegrationTest is DssTest { function testCoreCouncilCanCancelOperation() public { // Schedule operation vm.prank(coreCouncil); - bytes32 operationId = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); assertTrue(timelock.isOperationPending(operationId), "operation should be pending"); @@ -510,7 +510,7 @@ contract IntegrationTest is DssTest { function testCancellerCanCancelOperation() public { vm.prank(coreCouncil); - bytes32 operationId = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); vm.prank(canceller); timelock.cancel(operationId); @@ -535,13 +535,13 @@ contract IntegrationTest is DssTest { vm.prank(coreCouncil); vm.expectRevert(); - timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); } function testPausedTimelockBlocksExecution() public { // Schedule first vm.prank(coreCouncil); - bytes32 operationId = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); @@ -621,15 +621,15 @@ contract IntegrationTest is DssTest { function testFullOnboardingWorkflow() public { // 1. CoreCouncil schedules adding a cBeam via wrapper vm.prank(coreCouncil); - bytes32 addCBeamOp = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), keccak256("step1"), MIN_DELAY); + bytes32 addCBeamOp = wrapper.addCBeam(cBeam, bytes32(0), keccak256("step1"), MIN_DELAY); // 2. Schedule addRateLimits with predecessor vm.prank(coreCouncil); - bytes32 addRateLimitsOp = timelockWrapperMainnet.addRateLimits(SPARK_RATE_LIMITS, addCBeamOp, keccak256("step2"), MIN_DELAY); + bytes32 addRateLimitsOp = wrapper.addRateLimits(SPARK_RATE_LIMITS, addCBeamOp, keccak256("step2"), MIN_DELAY); // 3. Schedule setHop with predecessor vm.prank(coreCouncil); - bytes32 setHopOp = timelockWrapperMainnet.setHop(SPARK_RATE_LIMITS, 4 hours, addRateLimitsOp, keccak256("step3"), MIN_DELAY); + bytes32 setHopOp = wrapper.setHop(SPARK_RATE_LIMITS, 4 hours, addRateLimitsOp, keccak256("step3"), MIN_DELAY); // Wait for delay vm.warp(block.timestamp + MIN_DELAY); @@ -659,7 +659,7 @@ contract IntegrationTest is DssTest { // 7. Restart requires timelock vm.prank(coreCouncil); - bytes32 startOp = timelockWrapperMainnet.start(bytes32(0), keccak256("restart"), MIN_DELAY); + bytes32 startOp = wrapper.start(bytes32(0), keccak256("restart"), MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); @@ -786,13 +786,13 @@ contract IntegrationTest is DssTest { // Scheduling via wrapper is blocked vm.prank(coreCouncil); vm.expectRevert(); - timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); } function testMomPauseBlocksTimelockExecution() public { // Schedule an operation first vm.prank(coreCouncil); - bytes32 operationId = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); @@ -842,27 +842,27 @@ contract IntegrationTest is DssTest { { // 1a. Schedule addCBeam vm.prank(coreCouncil); - opIds[0] = timelockWrapperMainnet.addCBeam(cBeam, bytes32(0), keccak256("addCBeam"), MIN_DELAY); + opIds[0] = wrapper.addCBeam(cBeam, bytes32(0), keccak256("addCBeam"), MIN_DELAY); // 1b. Schedule addRateLimits for SPARK_RATE_LIMITS vm.prank(coreCouncil); - opIds[1] = timelockWrapperMainnet.addRateLimits(SPARK_RATE_LIMITS, opIds[0], keccak256("addRateLimits"), MIN_DELAY); + opIds[1] = wrapper.addRateLimits(SPARK_RATE_LIMITS, opIds[0], keccak256("addRateLimits"), MIN_DELAY); // 1c. Schedule addController for SPARK_CONTROLLER vm.prank(coreCouncil); - opIds[2] = timelockWrapperMainnet.addController(SPARK_CONTROLLER, opIds[1], keccak256("addController"), MIN_DELAY); + opIds[2] = wrapper.addController(SPARK_CONTROLLER, opIds[1], keccak256("addController"), MIN_DELAY); // 1d. Schedule setHop for rateLimits vm.prank(coreCouncil); - opIds[3] = timelockWrapperMainnet.setHop(SPARK_RATE_LIMITS, 1 hours, opIds[2], keccak256("setHop"), MIN_DELAY); + opIds[3] = wrapper.setHop(SPARK_RATE_LIMITS, 1 hours, opIds[2], keccak256("setHop"), MIN_DELAY); // 1e. Schedule setMaxChange for rateLimits vm.prank(coreCouncil); - opIds[4] = timelockWrapperMainnet.setMaxChange(SPARK_RATE_LIMITS, 2 ether, opIds[3], keccak256("setMaxChange"), MIN_DELAY); + opIds[4] = wrapper.setMaxChange(SPARK_RATE_LIMITS, 2 ether, opIds[3], keccak256("setMaxChange"), MIN_DELAY); // 1f. Schedule addInitRateLimits (via wrapper with RateLimitConfig) vm.prank(coreCouncil); - opIds[5] = timelockWrapperMainnet.addInitRateLimits( + opIds[5] = wrapper.addInitRateLimits( RateLimitConfig({key: rateLimitKey, rateLimits: SPARK_RATE_LIMITS, maxAmount: 1_000_000e18, slope: 100_000e18}), opIds[4], keccak256("addInitRateLimits"), MIN_DELAY ); @@ -975,7 +975,7 @@ contract IntegrationTest is DssTest { // 4e. Operations resume after restart (via timelock) vm.prank(coreCouncil); - bytes32 startOp = timelockWrapperMainnet.start(bytes32(0), keccak256("restart"), MIN_DELAY); + bytes32 startOp = wrapper.start(bytes32(0), keccak256("restart"), MIN_DELAY); vm.warp(block.timestamp + MIN_DELAY); { diff --git a/test/timelock/TimelockWrapperMainnet.t.sol b/test/timelock/TimelockWrapperMainnet.t.sol index 94da83f..180db89 100644 --- a/test/timelock/TimelockWrapperMainnet.t.sol +++ b/test/timelock/TimelockWrapperMainnet.t.sol @@ -112,7 +112,7 @@ contract TimelockWrapperMainnetTest is DssTest { vm.startPrank(pauseProxy); PASInit.init(pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); - PASInit.initTimelockWrapperMainnet(pas, address(wrapper), coreCouncil); + PASInit.initTimelockWrapper(pas, address(wrapper), coreCouncil); vm.stopPrank(); // Grant configurator admin role on mainnet controllers and rate limiters From 255ed5f6221c066a14099146237e362dc26716a8 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:45:42 +0700 Subject: [PATCH 100/150] Apply suggestions from code review Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- deploy/L2PASSpell.sol | 2 +- src/timelock/TimelockWrapperForeign.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/L2PASSpell.sol b/deploy/L2PASSpell.sol index 912c476..cf3b69d 100644 --- a/deploy/L2PASSpell.sol +++ b/deploy/L2PASSpell.sol @@ -19,7 +19,7 @@ pragma solidity >=0.8.0; import { PASInit } from "./PASInit.sol"; import { PASInstance } from "./PASInstance.sol"; -// A reusable L2 spell for L2GovernanceRelay to initialize PAS contracts on a foreign chain. +// An L2 spell template for L2GovernanceRelay to initialize PAS contracts on a foreign chain. // This spell should be viewed as skeleton/sample and can be altered prior to being used // if further configurations are needed (for example adding more proposers). contract L2PASSpell { diff --git a/src/timelock/TimelockWrapperForeign.sol b/src/timelock/TimelockWrapperForeign.sol index 58fffad..45a32e6 100644 --- a/src/timelock/TimelockWrapperForeign.sol +++ b/src/timelock/TimelockWrapperForeign.sol @@ -76,7 +76,7 @@ struct RateLimitConfig { // Notes: // - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). // - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. -// - The actual downstream changes only take effect when cBEAMs use the BEAMState configurations, so atomicity in configurations can not be assumed (which is a known issue). +// - The actual downstream changes only take effect when cBEAMs use the BeamState configurations, so atomicity in configurations can not be assumed (which is a known issue). // - As part of a controller onboarding it might need to be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. contract TimelockWrapperForeign { // --- Auth --- @@ -230,7 +230,7 @@ contract TimelockWrapperForeign { // --- Controller Actions --- - // Spark functions + // Spark and Grove functions // Role bytes32 can be computed off-chain and passed as parameter, e.g keccak256("RELAYER"), keccak256("FREEZER") function grantRole( From a04909e2485898de48ad560b914852c089dc82c7 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:05:20 +0700 Subject: [PATCH 101/150] Alignments of mainnet and foreign wrappers --- src/timelock/TimelockWrapperForeign.sol | 2 +- src/timelock/TimelockWrapperMainnet.sol | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/timelock/TimelockWrapperForeign.sol b/src/timelock/TimelockWrapperForeign.sol index 45a32e6..50c6a90 100644 --- a/src/timelock/TimelockWrapperForeign.sol +++ b/src/timelock/TimelockWrapperForeign.sol @@ -230,7 +230,7 @@ contract TimelockWrapperForeign { // --- Controller Actions --- - // Spark and Grove functions + // Shared Spark & Grove functions // Role bytes32 can be computed off-chain and passed as parameter, e.g keccak256("RELAYER"), keccak256("FREEZER") function grantRole( diff --git a/src/timelock/TimelockWrapperMainnet.sol b/src/timelock/TimelockWrapperMainnet.sol index ac2f2b0..0727d31 100644 --- a/src/timelock/TimelockWrapperMainnet.sol +++ b/src/timelock/TimelockWrapperMainnet.sol @@ -115,7 +115,7 @@ contract TimelockWrapperMainnet { buds[usr] = 0; emit Diss(usr); } - + // --- Events --- event Rely(address indexed usr); event Deny(address indexed usr); @@ -125,15 +125,15 @@ contract TimelockWrapperMainnet { TimelockLike public immutable timelock; BeamStateLike public immutable beamState; - + constructor(address timelock_, address beamState_) { timelock = TimelockLike(payable(timelock_)); beamState = BeamStateLike(beamState_); - + wards[msg.sender] = 1; emit Rely(msg.sender); } - + function _submitProposal(bytes memory payload, bytes32 predecessor, bytes32 salt, uint256 delay) internal returns (bytes32 operationId) { address[] memory targets = new address[](1); targets[0] = address(beamState); @@ -292,7 +292,7 @@ contract TimelockWrapperMainnet { operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMintRecipient"); } - + function setLayerZeroRecipient( uint32 destinationEndpointId, bytes32 layerZeroRecipient, @@ -311,7 +311,7 @@ contract TimelockWrapperMainnet { operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setLayerZeroRecipient"); } - + function setMaxSlippage( address pool, uint256 maxSlippage, @@ -330,7 +330,7 @@ contract TimelockWrapperMainnet { operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxSlippage"); } - + function setMaxExchangeRate( address token, uint256 shares, @@ -452,7 +452,7 @@ contract TimelockWrapperMainnet { operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityLowerTickBound"); } - + function setUniswapV3AddLiquidityUpperTickBound( address pool, int24 upperTickBound, @@ -471,7 +471,7 @@ contract TimelockWrapperMainnet { operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityUpperTickBound"); } - + function setUniswapV3TwapSecondsAgo( address pool, uint32 twapSecondsAgo, From 91b76edddfc9682d24507ec8970d4ff3b90c3d1c Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Wed, 28 Jan 2026 07:34:33 -0300 Subject: [PATCH 102/150] Remove unused events Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- test/Integration.t.sol | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/Integration.t.sol b/test/Integration.t.sol index d948d89..ace8275 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -84,22 +84,6 @@ contract IntegrationTest is DssTest { uint256 constant MIN_DELAY = 1 days; bytes32 constant SALT = keccak256("integration-test"); - // Events - event CallScheduled( - bytes32 indexed id, - uint256 indexed index, - address target, - uint256 value, - bytes data, - bytes32 predecessor, - uint256 delay - ); - event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data); - event Cancelled(bytes32 indexed id); - event Paused(address account); - event Unpaused(address account); - event ProposalSubmitted(bytes32 indexed operationId, string functionName); - function setUp() public { // Fork mainnet at specified block vm.createSelectFork(vm.envString("ETH_RPC_URL")); From 95a5ab6e3c8725e140fedd6fef8afd1b647c5061 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:44:58 +0700 Subject: [PATCH 103/150] Change l2 spell pragma to ^0.8.24 --- deploy/L2PASSpell.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/L2PASSpell.sol b/deploy/L2PASSpell.sol index cf3b69d..8705ee3 100644 --- a/deploy/L2PASSpell.sol +++ b/deploy/L2PASSpell.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity >=0.8.0; +pragma solidity ^0.8.24; import { PASInit } from "./PASInit.sol"; import { PASInstance } from "./PASInstance.sol"; From d83d0a771f659c308a20344be4b806b563419de3 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Wed, 28 Jan 2026 12:44:14 -0300 Subject: [PATCH 104/150] nit change Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- src/BeamState.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index d4335a4..4f131c0 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -32,7 +32,7 @@ contract BeamState { mapping(address rateLimits_ => mapping(address cBeam => uint256 allowed)) public rateLimitsCBeams; // allowed == 0 => false, allowed == 1 => true mapping(address controller => mapping(address cBeam => uint256 allowed)) public controllersCBeams; // allowed == 0 => false, allowed == 1 => true mapping(bytes32 key => mapping(address rateLimits_ => DefaultRateLimits limit)) public initRateLimits; // rateLimits == address(0) every rateLimits allowed - mapping(bytes32 key => mapping(address controller => bool allowed)) public initControllerActions; // controller == address(0) every controller allowed + mapping(bytes32 key => mapping(address controller => bool allowed)) public initControllerActions; // controller == address(0) every controller allowed mapping(address rateLimits_ => uint256 value) public hop; // rateLimits == address(0) => general backup configuration mapping(address rateLimits_ => uint256 value) public maxChange; // rateLimits == address(0) => general backup configuration From 5fcf1e41c35a97b1c57be3bed8bca6345d7be433 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 28 Jan 2026 17:34:16 -0300 Subject: [PATCH 105/150] Add more checks to Wrappers tests and some other minor changes --- test/timelock/TimelockWrapperForeign.t.sol | 261 +++++++++++++---- test/timelock/TimelockWrapperMainnet.t.sol | 326 ++++++++++++++++----- 2 files changed, 468 insertions(+), 119 deletions(-) diff --git a/test/timelock/TimelockWrapperForeign.t.sol b/test/timelock/TimelockWrapperForeign.t.sol index 49548e8..fa0a090 100644 --- a/test/timelock/TimelockWrapperForeign.t.sol +++ b/test/timelock/TimelockWrapperForeign.t.sol @@ -27,11 +27,8 @@ import { L2PASSpell } from "deploy/L2PASSpell.sol"; interface ControllerLike { function hasRole(bytes32 role, address account) external view returns (bool); - function grantRole(bytes32 role, address account) external; - function revokeRole(bytes32 role, address account) external; function RELAYER() external view returns (bytes32); function mintRecipients(uint32) external view returns (bytes32); - function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; function rateLimits() external view returns (address); // Shared getters function layerZeroRecipients(uint32) external view returns (bytes32); @@ -42,6 +39,8 @@ interface ControllerLike { // uniswapV3PoolParams returns: (swapMaxTickDelta, addLiquidityLowerTick, addLiquidityUpperTick, twapSecondsAgo) function uniswapV3PoolParams(address) external view returns (uint24, int24, int24, uint32); function merklDistributor() external view returns (address); + + function grantRole(bytes32 role, address account) external; } interface RateLimitsLike { @@ -65,6 +64,7 @@ contract TimelockWrapperForeignTest is DssTest { // --- Events --- event Kiss(address indexed usr); event Diss(address indexed usr); + event ProposalSubmitted(bytes32 indexed operationId, string functionName); // --- Base Addresses --- // https://github.com/sparkdotfi/spark-address-registry/blob/master/src/Base.sol @@ -176,12 +176,21 @@ contract TimelockWrapperForeignTest is DssTest { (data, controller) = abi.decode(payload, (bytes, address)); } + function _expectedOperationId(bytes memory payload, bytes32 predecessor, bytes32 salt) internal view returns (bytes32) { + address[] memory targets = new address[](1); + targets[0] = address(beamState); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + payloads[0] = payload; + return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + } + // ============================================================================ // Authorization Tests // ============================================================================ function testConstructor() public { - vm.expectEmit(true, false, false, true); + vm.expectEmit(); emit Rely(address(this)); TimelockWrapperForeign newWrapper = new TimelockWrapperForeign(address(timelock), address(beamState)); @@ -199,11 +208,11 @@ contract TimelockWrapperForeignTest is DssTest { vm.startPrank(address(govRelay)); assertEq(wrapper.buds(usr), 0); - vm.expectEmit(true, false, false, true); + vm.expectEmit(); emit Kiss(usr); wrapper.kiss(usr); assertEq(wrapper.buds(usr), 1); - vm.expectEmit(true, false, false, true); + vm.expectEmit(); emit Diss(usr); wrapper.diss(usr); assertEq(wrapper.buds(usr), 0); @@ -257,24 +266,42 @@ contract TimelockWrapperForeignTest is DssTest { beamState.stop(); assertTrue(beamState.stopped()); + bytes32 salt = keccak256("start"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.start.selector), bytes32(0), salt); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "start"); vm.prank(coreCouncil); - bytes32 id = wrapper.start(bytes32(0), keccak256("start"), MIN_DELAY); + bytes32 id = wrapper.start(bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertFalse(beamState.stopped()); } function testSetHop() public { + bytes32 salt = keccak256("hop"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 3600), bytes32(0), salt); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setHop"); vm.prank(coreCouncil); - bytes32 id = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), keccak256("hop"), MIN_DELAY); + bytes32 id = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.getHop(SPARK_RATE_LIMITS), 3600); } function testSetMaxChange() public { + bytes32 salt = keccak256("mc"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.setMaxChange.selector, GROVE_RATE_LIMITS, 2e18), bytes32(0), salt); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setMaxChange"); vm.prank(coreCouncil); - bytes32 id = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), keccak256("mc"), MIN_DELAY); + bytes32 id = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.maxChange(GROVE_RATE_LIMITS), 2e18); @@ -282,9 +309,14 @@ contract TimelockWrapperForeignTest is DssTest { function testAddRateLimits() public { address rateLimits = makeAddr("rateLimits"); + bytes32 salt = keccak256("rl"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addRateLimits.selector, rateLimits), bytes32(0), salt); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "addRateLimits"); vm.prank(coreCouncil); - bytes32 id = wrapper.addRateLimits(rateLimits, bytes32(0), keccak256("rl"), MIN_DELAY); + bytes32 id = wrapper.addRateLimits(rateLimits, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.rateLimits(rateLimits), 1); @@ -292,9 +324,14 @@ contract TimelockWrapperForeignTest is DssTest { function testAddController() public { address controller = makeAddr("controller"); + bytes32 salt = keccak256("ctrl"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addController.selector, controller), bytes32(0), salt); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "addController"); vm.prank(coreCouncil); - bytes32 id = wrapper.addController(controller, bytes32(0), keccak256("ctrl"), MIN_DELAY); + bytes32 id = wrapper.addController(controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.controllers(controller), 1); @@ -302,9 +339,14 @@ contract TimelockWrapperForeignTest is DssTest { function testAddCBeam() public { address beam = makeAddr("beam"); + bytes32 salt = keccak256("cbeam"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addCBeam.selector, beam), bytes32(0), salt); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "addCBeam"); vm.prank(coreCouncil); - bytes32 id = wrapper.addCBeam(beam, bytes32(0), keccak256("cbeam"), MIN_DELAY); + bytes32 id = wrapper.addCBeam(beam, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.cBeams(beam), 1); @@ -318,8 +360,17 @@ contract TimelockWrapperForeignTest is DssTest { slope: 1_000_000e18 }); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits, config.maxAmount, config.slope), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "addInitRateLimits"); vm.prank(coreCouncil); bytes32 id = wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); // Verify stored in BeamState @@ -350,11 +401,7 @@ contract TimelockWrapperForeignTest is DssTest { configs[0] = RateLimitConfig(key1, rateLimits, 5_000_000e18, 500_000e18); configs[1] = RateLimitConfig(key2, rateLimits, 3_000_000e18, 300_000e18); - vm.prank(coreCouncil); - wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); - assertEq(timelock.getOperationCount(), 1); - - vm.warp(block.timestamp + MIN_DELAY); + // Pre-compute expected operationId for batch address[] memory targets = new address[](2); uint256[] memory values = new uint256[](2); bytes[] memory payloads = new bytes[](2); @@ -362,6 +409,16 @@ contract TimelockWrapperForeignTest is DssTest { targets[i] = address(beamState); payloads[i] = abi.encodeWithSelector(BeamState.addInitRateLimits.selector, configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope); } + bytes32 expectedId = timelock.hashOperationBatch(targets, values, payloads, bytes32(0), salt); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "batchAddInitRateLimits"); + vm.prank(coreCouncil); + bytes32 id = wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); + assertEq(timelock.getOperationCount(), 1); + + vm.warp(block.timestamp + MIN_DELAY); timelock.executeBatch(targets, values, payloads, bytes32(0), salt); // Verify stored in BeamState @@ -399,20 +456,28 @@ contract TimelockWrapperForeignTest is DssTest { // --- grantRole (Spark & Grove) --- function _checkGrantRole(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); - bytes32 role = ctrl.RELAYER(); + bytes32 role = ControllerLike(controller).RELAYER(); address account = makeAddr("relayer"); - assertFalse(ctrl.hasRole(role, account)); + assertFalse(ControllerLike(controller).hasRole(role, account)); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("grantRole(bytes32,address)", role, account), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "grantRole"); vm.prank(coreCouncil); bytes32 id = wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertTrue(ctrl.hasRole(role, account)); + assertTrue(ControllerLike(controller).hasRole(role, account)); } function testGrantRoleOnSparkController() public { @@ -426,21 +491,29 @@ contract TimelockWrapperForeignTest is DssTest { // --- revokeRole (Spark & Grove) --- function _checkRevokeRole(address controller, address admin, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); - bytes32 role = ctrl.RELAYER(); + bytes32 role = ControllerLike(controller).RELAYER(); address account = makeAddr("toRevoke"); vm.prank(admin); - ctrl.grantRole(role, account); + ControllerLike(controller).grantRole(role, account); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("revokeRole(bytes32,address)", role, account), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "revokeRole"); vm.prank(coreCouncil); bytes32 id = wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertFalse(ctrl.hasRole(role, account)); + assertFalse(ControllerLike(controller).hasRole(role, account)); } function testRevokeRoleOnSparkController() public { @@ -454,18 +527,26 @@ contract TimelockWrapperForeignTest is DssTest { // --- setMintRecipient (Spark & Grove) --- function _checkSetMintRecipient(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); uint32 domain = 6; bytes32 recipient = bytes32(uint256(uint160(makeAddr("recipient")))); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMintRecipient(uint32,bytes32)", domain, recipient), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setMintRecipient"); vm.prank(coreCouncil); bytes32 id = wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertEq(ctrl.mintRecipients(domain), recipient); + assertEq(ControllerLike(controller).mintRecipients(domain), recipient); } function testSetMintRecipientOnSparkController() public { @@ -479,18 +560,26 @@ contract TimelockWrapperForeignTest is DssTest { // --- setLayerZeroRecipient (Spark & Grove) --- function _checkSetLayerZeroRecipient(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); uint32 endpointId = 111; bytes32 recipient = bytes32(uint256(uint160(makeAddr("lzRecipient")))); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setLayerZeroRecipient(uint32,bytes32)", endpointId, recipient), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setLayerZeroRecipient"); vm.prank(coreCouncil); bytes32 id = wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertEq(ctrl.layerZeroRecipients(endpointId), recipient); + assertEq(ControllerLike(controller).layerZeroRecipients(endpointId), recipient); } function testSetLayerZeroRecipientOnSparkController() public { @@ -504,18 +593,26 @@ contract TimelockWrapperForeignTest is DssTest { // --- setMaxSlippage (Spark & Grove) --- function _checkSetMaxSlippage(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); address pool = makeAddr("pool"); uint256 slippage = 100; + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMaxSlippage(address,uint256)", pool, slippage), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setMaxSlippage"); vm.prank(coreCouncil); bytes32 id = wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertEq(ctrl.maxSlippages(pool), slippage); + assertEq(ControllerLike(controller).maxSlippages(pool), slippage); } function testSetMaxSlippageOnSparkController() public { @@ -529,20 +626,28 @@ contract TimelockWrapperForeignTest is DssTest { // --- setMaxExchangeRate (Spark & Grove) --- function _checkSetMaxExchangeRate(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); address sUSDS = 0x5875eEE11Cf8398102FdAd704C9E96607675467a; uint256 shares = 1e18; uint256 maxExpectedAssets = 1.1e18; + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMaxExchangeRate(address,uint256,uint256)", sUSDS, shares, maxExpectedAssets), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setMaxExchangeRate"); vm.prank(coreCouncil); bytes32 id = wrapper.setMaxExchangeRate(sUSDS, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); // maxExchangeRates stores value in 1e36 precision: maxExpectedAssets * 1e18 / shares * 1e18 = 1.1e36 - assertEq(ctrl.maxExchangeRates(sUSDS), 1.1e36); + assertEq(ControllerLike(controller).maxExchangeRates(sUSDS), 1.1e36); } function testSetMaxExchangeRateOnSparkController() public { @@ -556,101 +661,155 @@ contract TimelockWrapperForeignTest is DssTest { // --- setUniswapV3PoolMaxTickDelta (Grove Only) --- function testSetUniswapV3PoolMaxTickDeltaOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = makeAddr("pool"); + bytes32 salt = keccak256("tick"); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3PoolMaxTickDelta(address,uint24)", pool, uint24(1000)), GROVE_CONTROLLER), + bytes32(0), + salt + ); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setUniswapV3PoolMaxTickDelta"); vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), keccak256("tick"), MIN_DELAY); + bytes32 id = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - (uint24 swapMaxTickDelta,,,) = grove.uniswapV3PoolParams(pool); + (uint24 swapMaxTickDelta,,,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); assertEq(swapMaxTickDelta, 1000); } // --- setUniswapV3AddLiquidityLowerTickBound (Grove Only) --- function testSetUniswapV3AddLiquidityLowerTickBoundOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = makeAddr("pool"); + bytes32 salt = keccak256("lower"); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3AddLiquidityLowerTickBound(address,int24)", pool, int24(-887220)), GROVE_CONTROLLER), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setUniswapV3AddLiquidityLowerTickBound"); vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), keccak256("lower"), MIN_DELAY); + bytes32 id = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - (, int24 lowerTick,,) = grove.uniswapV3PoolParams(pool); + (, int24 lowerTick,,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); assertEq(lowerTick, -887220); } // --- setUniswapV3AddLiquidityUpperTickBound (Grove Only) --- function testSetUniswapV3AddLiquidityUpperTickBoundOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = makeAddr("pool"); + bytes32 salt = keccak256("upper"); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3AddLiquidityUpperTickBound(address,int24)", pool, int24(887220)), GROVE_CONTROLLER), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setUniswapV3AddLiquidityUpperTickBound"); vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), keccak256("upper"), MIN_DELAY); + bytes32 id = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - (,, int24 upperTick,) = grove.uniswapV3PoolParams(pool); + (,, int24 upperTick,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); assertEq(upperTick, 887220); } // --- setUniswapV3TwapSecondsAgo (Grove Only) --- function testSetUniswapV3TwapSecondsAgoOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = makeAddr("pool"); + bytes32 salt = keccak256("twap"); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3TwapSecondsAgo(address,uint32)", pool, uint32(1800)), GROVE_CONTROLLER), + bytes32(0), + salt + ); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setUniswapV3TwapSecondsAgo"); vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), keccak256("twap"), MIN_DELAY); + bytes32 id = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - (,,, uint32 twapSecondsAgo) = grove.uniswapV3PoolParams(pool); + (,,, uint32 twapSecondsAgo) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); assertEq(twapSecondsAgo, 1800); } // --- setCentrifugeRecipient (Grove Only) --- function testSetCentrifugeRecipientOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); uint16 centrifugeId = 1; bytes32 recipient = bytes32(uint256(uint160(makeAddr("centrifuge")))); + bytes32 salt = keccak256("cent"); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setCentrifugeRecipient(uint16,bytes32)", centrifugeId, recipient), GROVE_CONTROLLER), + bytes32(0), + salt + ); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setCentrifugeRecipient"); vm.prank(coreCouncil); - bytes32 id = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), keccak256("cent"), MIN_DELAY); + bytes32 id = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - assertEq(grove.centrifugeRecipients(centrifugeId), recipient); + assertEq(ControllerLike(GROVE_CONTROLLER).centrifugeRecipients(centrifugeId), recipient); } // --- setMerklDistributor (Grove Only) --- function testSetMerklDistributorOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address distributor = makeAddr("merklDistributor"); + bytes32 salt = keccak256("merkl"); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMerklDistributor(address)", distributor), GROVE_CONTROLLER), + bytes32(0), + salt + ); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setMerklDistributor"); vm.prank(coreCouncil); - bytes32 id = wrapper.setMerklDistributor(distributor, GROVE_CONTROLLER, bytes32(0), keccak256("merkl"), MIN_DELAY); + bytes32 id = wrapper.setMerklDistributor(distributor, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - assertEq(grove.merklDistributor(), distributor); + assertEq(ControllerLike(GROVE_CONTROLLER).merklDistributor(), distributor); } } diff --git a/test/timelock/TimelockWrapperMainnet.t.sol b/test/timelock/TimelockWrapperMainnet.t.sol index 180db89..03a6dc9 100644 --- a/test/timelock/TimelockWrapperMainnet.t.sol +++ b/test/timelock/TimelockWrapperMainnet.t.sol @@ -28,11 +28,8 @@ import { PASInstance } from "deploy/PASInstance.sol"; interface ControllerLike { function hasRole(bytes32 role, address account) external view returns (bool); - function grantRole(bytes32 role, address account) external; - function revokeRole(bytes32 role, address account) external; function RELAYER() external view returns (bytes32); function mintRecipients(uint32) external view returns (bytes32); - function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; function rateLimits() external view returns (address); // Shared getters function layerZeroRecipients(uint32) external view returns (bytes32); @@ -46,6 +43,8 @@ interface ControllerLike { function centrifugeRecipients(uint16) external view returns (bytes32); // uniswapV3PoolParams returns: (swapMaxTickDelta, addLiquidityLowerTick, addLiquidityUpperTick, twapSecondsAgo) function uniswapV3PoolParams(address) external view returns (uint24, int24, int24, uint32); + + function grantRole(bytes32 role, address account) external; } interface RateLimitsLike { @@ -57,6 +56,7 @@ contract TimelockWrapperMainnetTest is DssTest { // --- Events --- event Kiss(address indexed usr); event Diss(address indexed usr); + event ProposalSubmitted(bytes32 indexed operationId, string functionName); // --- Mainnet Addresses --- // https://github.com/sparkdotfi/spark-address-registry/blob/0a06d13c3e9d36428a6f916ac25528d82154517b/src/Ethereum.sol @@ -175,12 +175,21 @@ contract TimelockWrapperMainnetTest is DssTest { (data, controller) = abi.decode(payload, (bytes, address)); } + function _expectedOperationId(bytes memory payload, bytes32 predecessor, bytes32 salt) internal view returns (bytes32) { + address[] memory targets = new address[](1); + targets[0] = address(beamState); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + payloads[0] = payload; + return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + } + // ============================================================================ // Authorization Tests // ============================================================================ function testConstructor() public { - vm.expectEmit(true, false, false, true); + vm.expectEmit(); emit Rely(address(this)); TimelockWrapperMainnet newWrapper = new TimelockWrapperMainnet(address(timelock), address(beamState)); @@ -198,11 +207,11 @@ contract TimelockWrapperMainnetTest is DssTest { vm.startPrank(pauseProxy); assertEq(wrapper.buds(usr), 0); - vm.expectEmit(true, false, false, true); + vm.expectEmit(); emit Kiss(usr); wrapper.kiss(usr); assertEq(wrapper.buds(usr), 1); - vm.expectEmit(true, false, false, true); + vm.expectEmit(); emit Diss(usr); wrapper.diss(usr); assertEq(wrapper.buds(usr), 0); @@ -258,24 +267,42 @@ contract TimelockWrapperMainnetTest is DssTest { beamState.stop(); assertTrue(beamState.stopped()); + bytes32 salt = keccak256("start"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.start.selector), bytes32(0), salt); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "start"); vm.prank(coreCouncil); - bytes32 id = wrapper.start(bytes32(0), keccak256("start"), MIN_DELAY); + bytes32 id = wrapper.start(bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertFalse(beamState.stopped()); } function testSetHop() public { + bytes32 salt = keccak256("hop"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 3600), bytes32(0), salt); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setHop"); vm.prank(coreCouncil); - bytes32 id = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), keccak256("hop"), MIN_DELAY); + bytes32 id = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.getHop(SPARK_RATE_LIMITS), 3600); } function testSetMaxChange() public { + bytes32 salt = keccak256("mc"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.setMaxChange.selector, GROVE_RATE_LIMITS, 2e18), bytes32(0), salt); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setMaxChange"); vm.prank(coreCouncil); - bytes32 id = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), keccak256("mc"), MIN_DELAY); + bytes32 id = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.maxChange(GROVE_RATE_LIMITS), 2e18); @@ -283,9 +310,14 @@ contract TimelockWrapperMainnetTest is DssTest { function testAddRateLimits() public { address rateLimits = makeAddr("rateLimits"); + bytes32 salt = keccak256("rl"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addRateLimits.selector, rateLimits), bytes32(0), salt); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "addRateLimits"); vm.prank(coreCouncil); - bytes32 id = wrapper.addRateLimits(rateLimits, bytes32(0), keccak256("rl"), MIN_DELAY); + bytes32 id = wrapper.addRateLimits(rateLimits, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.rateLimits(rateLimits), 1); @@ -293,9 +325,14 @@ contract TimelockWrapperMainnetTest is DssTest { function testAddController() public { address controller = makeAddr("controller"); + bytes32 salt = keccak256("ctrl"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addController.selector, controller), bytes32(0), salt); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "addController"); vm.prank(coreCouncil); - bytes32 id = wrapper.addController(controller, bytes32(0), keccak256("ctrl"), MIN_DELAY); + bytes32 id = wrapper.addController(controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.controllers(controller), 1); @@ -303,9 +340,14 @@ contract TimelockWrapperMainnetTest is DssTest { function testAddCBeam() public { address beam = makeAddr("beam"); + bytes32 salt = keccak256("cbeam"); + bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addCBeam.selector, beam), bytes32(0), salt); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "addCBeam"); vm.prank(coreCouncil); - bytes32 id = wrapper.addCBeam(beam, bytes32(0), keccak256("cbeam"), MIN_DELAY); + bytes32 id = wrapper.addCBeam(beam, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); assertEq(beamState.cBeams(beam), 1); @@ -319,8 +361,17 @@ contract TimelockWrapperMainnetTest is DssTest { slope: 1_000_000e18 }); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits, config.maxAmount, config.slope), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "addInitRateLimits"); vm.prank(coreCouncil); bytes32 id = wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); _execute(id); // Verify stored in BeamState @@ -351,11 +402,7 @@ contract TimelockWrapperMainnetTest is DssTest { configs[0] = RateLimitConfig(key1, rateLimits, 5_000_000e18, 500_000e18); configs[1] = RateLimitConfig(key2, rateLimits, 3_000_000e18, 300_000e18); - vm.prank(coreCouncil); - wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); - assertEq(timelock.getOperationCount(), 1); - - vm.warp(block.timestamp + MIN_DELAY); + // Pre-compute expected operationId for batch address[] memory targets = new address[](2); uint256[] memory values = new uint256[](2); bytes[] memory payloads = new bytes[](2); @@ -363,6 +410,16 @@ contract TimelockWrapperMainnetTest is DssTest { targets[i] = address(beamState); payloads[i] = abi.encodeWithSelector(BeamState.addInitRateLimits.selector, configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope); } + bytes32 expectedId = timelock.hashOperationBatch(targets, values, payloads, bytes32(0), salt); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "batchAddInitRateLimits"); + vm.prank(coreCouncil); + bytes32 id = wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); + assertEq(timelock.getOperationCount(), 1); + + vm.warp(block.timestamp + MIN_DELAY); timelock.executeBatch(targets, values, payloads, bytes32(0), salt); // Verify stored in BeamState @@ -400,20 +457,28 @@ contract TimelockWrapperMainnetTest is DssTest { // --- grantRole (Spark & Grove) --- function _checkGrantRole(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); - bytes32 role = ctrl.RELAYER(); + bytes32 role = ControllerLike(controller).RELAYER(); address account = makeAddr("relayer"); - assertFalse(ctrl.hasRole(role, account)); + assertFalse(ControllerLike(controller).hasRole(role, account)); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("grantRole(bytes32,address)", role, account), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "grantRole"); vm.prank(coreCouncil); bytes32 id = wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertTrue(ctrl.hasRole(role, account)); + assertTrue(ControllerLike(controller).hasRole(role, account)); } function testGrantRoleOnSparkController() public { @@ -427,21 +492,29 @@ contract TimelockWrapperMainnetTest is DssTest { // --- revokeRole (Spark & Grove) --- function _checkRevokeRole(address controller, address admin, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); - bytes32 role = ctrl.RELAYER(); + bytes32 role = ControllerLike(controller).RELAYER(); address account = makeAddr("toRevoke"); vm.prank(admin); - ctrl.grantRole(role, account); + ControllerLike(controller).grantRole(role, account); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("revokeRole(bytes32,address)", role, account), controller), + bytes32(0), + salt + ); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "revokeRole"); vm.prank(coreCouncil); bytes32 id = wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertFalse(ctrl.hasRole(role, account)); + assertFalse(ControllerLike(controller).hasRole(role, account)); } function testRevokeRoleOnSparkController() public { @@ -455,18 +528,26 @@ contract TimelockWrapperMainnetTest is DssTest { // --- setMintRecipient (Spark & Grove) --- function _checkSetMintRecipient(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); uint32 domain = 6; bytes32 recipient = bytes32(uint256(uint160(makeAddr("recipient")))); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMintRecipient(uint32,bytes32)", domain, recipient), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setMintRecipient"); vm.prank(coreCouncil); bytes32 id = wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertEq(ctrl.mintRecipients(domain), recipient); + assertEq(ControllerLike(controller).mintRecipients(domain), recipient); } function testSetMintRecipientOnSparkController() public { @@ -480,18 +561,26 @@ contract TimelockWrapperMainnetTest is DssTest { // --- setLayerZeroRecipient (Spark & Grove) --- function _checkSetLayerZeroRecipient(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); uint32 endpointId = 111; bytes32 recipient = bytes32(uint256(uint160(makeAddr("lzRecipient")))); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setLayerZeroRecipient(uint32,bytes32)", endpointId, recipient), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setLayerZeroRecipient"); vm.prank(coreCouncil); bytes32 id = wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertEq(ctrl.layerZeroRecipients(endpointId), recipient); + assertEq(ControllerLike(controller).layerZeroRecipients(endpointId), recipient); } function testSetLayerZeroRecipientOnSparkController() public { @@ -505,18 +594,26 @@ contract TimelockWrapperMainnetTest is DssTest { // --- setMaxSlippage (Spark & Grove) --- function _checkSetMaxSlippage(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; uint256 slippage = 100; + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMaxSlippage(address,uint256)", pool, slippage), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setMaxSlippage"); vm.prank(coreCouncil); bytes32 id = wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); - assertEq(ctrl.maxSlippages(pool), slippage); + assertEq(ControllerLike(controller).maxSlippages(pool), slippage); } function testSetMaxSlippageOnSparkController() public { @@ -530,82 +627,130 @@ contract TimelockWrapperMainnetTest is DssTest { // --- setOTCBuffer (Spark Only) --- function testSetOTCBufferOnSparkController() public { - ControllerLike spark = ControllerLike(SPARK_CONTROLLER); address exchange = makeAddr("exchange"); address buffer = makeAddr("buffer"); + bytes32 salt = keccak256("buf"); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setOTCBuffer(address,address)", exchange, buffer), SPARK_CONTROLLER), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setOTCBuffer"); vm.prank(coreCouncil); - bytes32 id = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("buf"), MIN_DELAY); + bytes32 id = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, data); - (address setBuffer,,,,) = spark.otcs(exchange); + (address setBuffer,,,,) = ControllerLike(SPARK_CONTROLLER).otcs(exchange); assertEq(setBuffer, buffer); } // --- setOTCRechargeRate (Spark Only) --- function testSetOTCRechargeRateOnSparkController() public { - ControllerLike spark = ControllerLike(SPARK_CONTROLLER); address exchange = makeAddr("exchange"); + bytes32 salt = keccak256("rate"); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setOTCRechargeRate(address,uint256)", exchange, 1e18), SPARK_CONTROLLER), + bytes32(0), + salt + ); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setOTCRechargeRate"); vm.prank(coreCouncil); - bytes32 id = wrapper.setOTCRechargeRate(exchange, 1e18, SPARK_CONTROLLER, bytes32(0), keccak256("rate"), MIN_DELAY); + bytes32 id = wrapper.setOTCRechargeRate(exchange, 1e18, SPARK_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(SPARK_CONTROLLER, data); - (, uint256 rechargeRate,,,) = spark.otcs(exchange); + (, uint256 rechargeRate,,,) = ControllerLike(SPARK_CONTROLLER).otcs(exchange); assertEq(rechargeRate, 1e18); } // --- setOTCWhitelistedAsset (Spark Only) --- function testSetOTCWhitelistedAssetOnSparkController() public { - ControllerLike spark = ControllerLike(SPARK_CONTROLLER); address exchange = makeAddr("exchange"); address buffer = makeAddr("buffer"); address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // First set the buffer (required before whitelisting assets) - vm.prank(coreCouncil); - bytes32 bufferId = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("wlbuf"), MIN_DELAY); - (bytes memory bufferData,) = _getControllerAction(bufferId); - _execute(bufferId); - vm.prank(cBeam); - configurator.callControllerAction(SPARK_CONTROLLER, bufferData); + { + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setOTCBuffer(address,address)", exchange, buffer), SPARK_CONTROLLER), + bytes32(0), + keccak256("wlbuf") + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setOTCBuffer"); + vm.prank(coreCouncil); + bytes32 id = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("wlbuf"), MIN_DELAY); + assertEq(id, expectedId); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + vm.prank(cBeam); + configurator.callControllerAction(SPARK_CONTROLLER, data); + } // Now whitelist the asset - vm.prank(coreCouncil); - bytes32 id = wrapper.setOTCWhitelistedAsset(exchange, usdc, true, SPARK_CONTROLLER, bytes32(0), keccak256("wl"), MIN_DELAY); - (bytes memory data,) = _getControllerAction(id); - _execute(id); + { + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setOTCWhitelistedAsset(address,address,bool)", exchange, usdc, true), SPARK_CONTROLLER), + bytes32(0), + keccak256("wl") + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setOTCWhitelistedAsset"); + vm.prank(coreCouncil); + bytes32 id = wrapper.setOTCWhitelistedAsset(exchange, usdc, true, SPARK_CONTROLLER, bytes32(0), keccak256("wl"), MIN_DELAY); + assertEq(id, expectedId); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(SPARK_CONTROLLER, data); + } - vm.prank(cBeam); - configurator.callControllerAction(SPARK_CONTROLLER, data); - assertTrue(spark.otcWhitelistedAssets(exchange, usdc)); + assertTrue(ControllerLike(SPARK_CONTROLLER).otcWhitelistedAssets(exchange, usdc)); } // --- setMaxExchangeRate (Spark & Grove) --- function _checkSetMaxExchangeRate(address controller, bytes32 salt) internal { - ControllerLike ctrl = ControllerLike(controller); address sDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA; uint256 shares = 1e18; uint256 maxExpectedAssets = 1.1e18; + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMaxExchangeRate(address,uint256,uint256)", sDAI, shares, maxExpectedAssets), controller), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setMaxExchangeRate"); vm.prank(coreCouncil); bytes32 id = wrapper.setMaxExchangeRate(sDAI, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(controller, data); // maxExchangeRates stores value in 1e36 precision: maxExpectedAssets * 1e18 / shares * 1e18 = 1.1e36 - assertEq(ctrl.maxExchangeRates(sDAI), 1.1e36); + assertEq(ControllerLike(controller).maxExchangeRates(sDAI), 1.1e36); } function testSetMaxExchangeRateOnSparkController() public { @@ -619,85 +764,130 @@ contract TimelockWrapperMainnetTest is DssTest { // --- setUniswapV3PoolMaxTickDelta (Grove Only) --- function testSetUniswapV3PoolMaxTickDeltaOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + bytes32 salt = keccak256("tick"); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3PoolMaxTickDelta(address,uint24)", pool, uint24(1000)), GROVE_CONTROLLER), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setUniswapV3PoolMaxTickDelta"); vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), keccak256("tick"), MIN_DELAY); + bytes32 id = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - (uint24 swapMaxTickDelta,,,) = grove.uniswapV3PoolParams(pool); + (uint24 swapMaxTickDelta,,,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); assertEq(swapMaxTickDelta, 1000); } // --- setUniswapV3AddLiquidityLowerTickBound (Grove Only) --- function testSetUniswapV3AddLiquidityLowerTickBoundOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + bytes32 salt = keccak256("lower"); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3AddLiquidityLowerTickBound(address,int24)", pool, int24(-887220)), GROVE_CONTROLLER), + bytes32(0), + salt + ); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setUniswapV3AddLiquidityLowerTickBound"); vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), keccak256("lower"), MIN_DELAY); + bytes32 id = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - (, int24 lowerTick,,) = grove.uniswapV3PoolParams(pool); + (, int24 lowerTick,,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); assertEq(lowerTick, -887220); } // --- setUniswapV3AddLiquidityUpperTickBound (Grove Only) --- function testSetUniswapV3AddLiquidityUpperTickBoundOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + bytes32 salt = keccak256("upper"); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3AddLiquidityUpperTickBound(address,int24)", pool, int24(887220)), GROVE_CONTROLLER), + bytes32(0), + salt + ); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setUniswapV3AddLiquidityUpperTickBound"); vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), keccak256("upper"), MIN_DELAY); + bytes32 id = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - (,, int24 upperTick,) = grove.uniswapV3PoolParams(pool); + (,, int24 upperTick,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); assertEq(upperTick, 887220); } // --- setUniswapV3TwapSecondsAgo (Grove Only) --- function testSetUniswapV3TwapSecondsAgoOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + bytes32 salt = keccak256("twap"); + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3TwapSecondsAgo(address,uint32)", pool, uint32(1800)), GROVE_CONTROLLER), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setUniswapV3TwapSecondsAgo"); vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), keccak256("twap"), MIN_DELAY); + bytes32 id = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - (,,, uint32 twapSecondsAgo) = grove.uniswapV3PoolParams(pool); + (,,, uint32 twapSecondsAgo) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); assertEq(twapSecondsAgo, 1800); } // --- setCentrifugeRecipient (Grove Only) --- function testSetCentrifugeRecipientOnGroveController() public { - ControllerLike grove = ControllerLike(GROVE_CONTROLLER); uint16 centrifugeId = 1; bytes32 recipient = bytes32(uint256(uint160(makeAddr("centrifuge")))); + bytes32 salt = keccak256("cent"); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setCentrifugeRecipient(uint16,bytes32)", centrifugeId, recipient), GROVE_CONTROLLER), + bytes32(0), + salt + ); + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setCentrifugeRecipient"); vm.prank(coreCouncil); - bytes32 id = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), keccak256("cent"), MIN_DELAY); + bytes32 id = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); vm.prank(cBeam); configurator.callControllerAction(GROVE_CONTROLLER, data); - assertEq(grove.centrifugeRecipients(centrifugeId), recipient); + assertEq(ControllerLike(GROVE_CONTROLLER).centrifugeRecipients(centrifugeId), recipient); } } From d2e97a8a06ce54f60fe0b4ede02f0e920f2e4b3c Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:45:40 +0700 Subject: [PATCH 106/150] Add note about initial capacity for a new key --- src/Configurator.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Configurator.sol b/src/Configurator.sol index 3c2a3f6..cb59f82 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -113,6 +113,8 @@ contract Configurator { require(block.timestamp >= zzz[rateLimits][key] + beamState.getHop(rateLimits), "Configurator/increment-too-soon"); zzz[rateLimits][key] = block.timestamp; } + + // Note that the initial capacity for a new key will be 0 (which might differ from previous usages) uint256 lastAmount = RateLimitsLike(rateLimits).getCurrentRateLimit(key); RateLimitsLike(rateLimits).setRateLimitData(key, maxAmount, slope, _min(maxAmount, lastAmount), block.timestamp); emit SetRateLimit(rateLimits, key, maxAmount, slope); From fbada477cb71bb5ad0178e2c3c5fd9053916b9cc Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:51:41 +0700 Subject: [PATCH 107/150] Add foreign wrapper to readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index dbce5c0..e171757 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ Extended OpenZeppelin TimelockController with pausing support, permissionless ex Convenience contract for proposers to schedule batched BeamState configuration changes through the timelock. Provides type-safe wrappers for common operations like adding rate limits, controllers, cBeams, and their initial configurations. Authorized users (`buds`) can schedule proposals that execute after the timelock delay. +### TimelockWrapperForeign + +Variant of the timelock wrapper for foreign (non-mainnet) chain deployments. + ### PASMom Emergency governance contract that allows authorized parties to trigger circuit breakers. Can call `stop()` on BeamState to halt Configurator operations and `pause()` on Timelock to block scheduling and execution. Callable by the owner or via the Chief's hat through the authority. From c69508470eed1082093cca54edfdabb87ec8498e Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:40:45 +0700 Subject: [PATCH 108/150] Add rationale for blocking cancel on pause --- src/timelock/Timelock.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 69a9cf1..b76b284 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -107,6 +107,8 @@ contract Timelock is TimelockController, Pausable { operations[id] = Operation(targets, values, payloads, predecessor, salt); } + // As unpausing requires an admin action anyway, it is fine to block canceling while paused. + // If needed, the admin can atomically cancel any proposal right after unpausing. function cancel(bytes32 id) public virtual override whenNotPaused { super.cancel(id); _operationIds.remove(id); From d25a4c3d724a7129fe46423c94c0f924e74c352d Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:38:43 +0700 Subject: [PATCH 109/150] Make mappings to be internal --- src/timelock/Timelock.sol | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index b76b284..77851f1 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -34,15 +34,15 @@ contract Timelock is TimelockController, Pausable { bytes32 salt; } - EnumerableSet.Bytes32Set private _operationIds; - mapping(bytes32 id => Operation) public operations; // Public for keeper access + EnumerableSet.Bytes32Set internal _operationIds; + mapping(bytes32 id => Operation) internal _operations; // Changes from original timelock: // - Do not allow proposers to change admin-only configurations // - Make execution permissionless // - Add pausing logic - // - Allow admin to change the delay immediately - // - Do not allow proposals to change the delay + // - Allow admin to change the min delay immediately + // - Do not allow proposals to change the min delay // Notes: // - By default all proposers can also cancel any proposal, this should be taken into account to make sure that they are trusted and that specific cancellations do not cause big harm. @@ -104,7 +104,7 @@ contract Timelock is TimelockController, Pausable { // Track operation for keeper jobs bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); _operationIds.add(id); - operations[id] = Operation(targets, values, payloads, predecessor, salt); + _operations[id] = Operation(targets, values, payloads, predecessor, salt); } // As unpausing requires an admin action anyway, it is fine to block canceling while paused. @@ -112,7 +112,7 @@ contract Timelock is TimelockController, Pausable { function cancel(bytes32 id) public virtual override whenNotPaused { super.cancel(id); _operationIds.remove(id); - delete operations[id]; + delete _operations[id]; } // ------------------------------------------------------------------------ @@ -133,7 +133,7 @@ contract Timelock is TimelockController, Pausable { super.executeBatch(targets, values, payloads, predecessor, salt); bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); _operationIds.remove(id); - delete operations[id]; + delete _operations[id]; } // ------------------------------------------------------------------------ @@ -158,7 +158,7 @@ contract Timelock is TimelockController, Pausable { if (!isOperationReady(operationId)) continue; // Check if predecessor is done, if any - bytes32 predecessor = operations[operationId].predecessor; + bytes32 predecessor = _operations[operationId].predecessor; if (predecessor != bytes32(0) && !isOperationDone(predecessor)) continue; return operationId; @@ -180,7 +180,7 @@ contract Timelock is TimelockController, Pausable { } function getOperation(bytes32 id) external view returns (Operation memory op) { - return operations[id]; + return _operations[id]; } } From 9785b1e85136553e4add9a753796fe23213f07b2 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 29 Jan 2026 08:53:20 -0300 Subject: [PATCH 110/150] Add variables for keys --- deploy/PASInit.sol | 16 ++++++++++------ test/Integration.t.sol | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index ae32854..39eabd7 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -134,17 +134,21 @@ library PASInit { function addCoreToChainlog( DssInstance memory dss, - PASInstance memory pasInstance + PASInstance memory pasInstance, + bytes32 stateKey, + bytes32 configuratorKey, + bytes32 timelockKey ) internal { - dss.chainlog.setAddress("PAS_STATE", pasInstance.beamState); - dss.chainlog.setAddress("PAS_CONFIGURATOR", pasInstance.configurator); - dss.chainlog.setAddress("PAS_TIMELOCK", pasInstance.timelock); + dss.chainlog.setAddress(stateKey, pasInstance.beamState); + dss.chainlog.setAddress(configuratorKey, pasInstance.configurator); + dss.chainlog.setAddress(timelockKey, pasInstance.timelock); } function initMom( DssInstance memory dss, PASInstance memory pasInstance, - address mom_ + address mom_, + bytes32 key ) internal { BeamStateLike beamState = BeamStateLike(pasInstance.beamState); TimelockLike timelock = TimelockLike(pasInstance.timelock); @@ -166,7 +170,7 @@ library PASInit { // --- Chainlog --- - dss.chainlog.setAddress("PAS_MOM", address(mom)); + dss.chainlog.setAddress(key, address(mom)); } function initTimelockWrapper( diff --git a/test/Integration.t.sol b/test/Integration.t.sol index ace8275..54605de 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -122,8 +122,8 @@ contract IntegrationTest is DssTest { vm.startPrank(pauseProxy); PASInit.init(pas, MIN_DELAY, coreCouncil, cancellers, pausers); - PASInit.addCoreToChainlog(dss, pas); - PASInit.initMom(dss, pas, address(mom)); + PASInit.addCoreToChainlog(dss, pas, "PAS_STATE", "PAS_CONFIGURATOR", "PAS_TIMELOCK"); + PASInit.initMom(dss, pas, address(mom), "PAS_MOM"); PASInit.initTimelockWrapper(pas, address(wrapper), coreCouncil); vm.stopPrank(); } From e33ad80d8c2200042a5e674cb514da804ebdd690 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 29 Jan 2026 14:33:18 -0300 Subject: [PATCH 111/150] Add init function for extras that can also be set by core council via timelock --- deploy/PASInit.sol | 23 ++++++++++++++++++++++ test/Integration.t.sol | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 39eabd7..ee38cad 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -132,6 +132,29 @@ library PASInit { } } + function initExtras( + PASInstance memory pasInstance, + uint256 hop, + uint256 maxChange, + address[] memory cBeams, + address[] memory rateLimits, + address[] memory controllers + ) internal { + 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]); + } + } + function addCoreToChainlog( DssInstance memory dss, PASInstance memory pasInstance, diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 54605de..5c6a567 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -213,6 +213,49 @@ contract IntegrationTest is DssTest { assertEq(beamState.wards(address(mom)), 1, "mom should have wards on beamState"); } + function testInitExtras() public { + // Deploy a fresh PAS instance for this test + PASInstance memory freshPas = PASDeploy.deploy(address(this), pauseProxy, MIN_DELAY); + BeamState freshBeamState = BeamState(freshPas.beamState); + + // Setup test data + uint256 hop = 2 hours; + uint256 maxChange = 1.5 ether; // 150% in WAD + + address[] memory testCBeams = new address[](2); + testCBeams[0] = address(0x10); + testCBeams[1] = address(0x11); + + address[] memory testRateLimits = new address[](2); + testRateLimits[0] = SPARK_RATE_LIMITS; + testRateLimits[1] = address(0x21); + + address[] memory testControllers = new address[](2); + testControllers[0] = SPARK_CONTROLLER; + testControllers[1] = address(0x22); + + // Deploy helper and give it auth on beamState + vm.startPrank(pauseProxy); + PASInit.initExtras(freshPas, hop, maxChange, testCBeams, testRateLimits, testControllers); + 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"); + } + // ============================================================================ // CoreCouncil Direct Actions (IMMEDIATE Role) Tests // ============================================================================ From c05692d79139aa9611586cd07d70affa28ce6057 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 29 Jan 2026 15:05:00 -0300 Subject: [PATCH 112/150] Make setting hop mandatory for increments --- src/Configurator.sol | 4 +++- test/Configurator.t.sol | 19 +++++++++++++++++++ test/Integration.t.sol | 1 + test/timelock/TimelockWrapperForeign.t.sol | 9 +++++++++ test/timelock/TimelockWrapperMainnet.t.sol | 9 +++++++++ 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Configurator.sol b/src/Configurator.sol index cb59f82..0b30e7d 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -110,7 +110,9 @@ contract Configurator { // Any increase requires hop if (maxAmount > current.maxAmount || slope > current.slope) { - require(block.timestamp >= zzz[rateLimits][key] + beamState.getHop(rateLimits), "Configurator/increment-too-soon"); + uint256 hop = beamState.getHop(rateLimits); + require(hop > 0, "Configurator/hop-not-set"); + require(block.timestamp >= zzz[rateLimits][key] + hop, "Configurator/increment-too-soon"); zzz[rateLimits][key] = block.timestamp; } diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index efc73b5..9afe934 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -790,6 +790,25 @@ contract ConfiguratorTest is DssTest { assertEq(data.slope, 0, "slope can be set to 0"); } + function testRevertHopNotSet() public { + bytes32 key = keccak256("hop-not-set-key"); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupRateLimitData(target1, key, 500 * WAD, 5 * WAD, 500 * WAD, block.timestamp); + + // Clear global hop to simulate hop-not-set scenario + beamState.setHop(address(0), 0); + // Ensure no specific hop for target1 + beamState.setHop(address(target1), 0); + + vm.warp(block.timestamp + 86_400); // Warp time (doesn't matter since hop is 0) + + // Try to increase rate limits - should revert + vm.prank(CBEAM1); + vm.expectRevert("Configurator/hop-not-set"); + configurator.setRateLimit(address(target1), key, 600 * WAD, 6 * WAD); + } + function testDifferentKeysIndependentZzz() public { bytes32 key1 = keccak256("key-1"); bytes32 key2 = keccak256("key-2"); diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 5c6a567..9253124 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -780,6 +780,7 @@ contract IntegrationTest is DssTest { beamState.addCBeam(cBeam); beamState.addRateLimits(SPARK_RATE_LIMITS); beamState.addInitRateLimits(rateLimitKey, SPARK_RATE_LIMITS, 1_000_000e18, 100_000e18); + beamState.setHop(SPARK_RATE_LIMITS, 1 hours); // Set hop for rate limit increases vm.stopPrank(); vm.prank(coreCouncil); diff --git a/test/timelock/TimelockWrapperForeign.t.sol b/test/timelock/TimelockWrapperForeign.t.sol index fa0a090..f08b8fb 100644 --- a/test/timelock/TimelockWrapperForeign.t.sol +++ b/test/timelock/TimelockWrapperForeign.t.sol @@ -154,6 +154,15 @@ contract TimelockWrapperForeignTest is DssTest { beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); beamState.setCBeamForRateLimits(GROVE_RATE_LIMITS, cBeam); vm.stopPrank(); + + // Set hop for rate limiters (required for setRateLimit to work on increases) + vm.startPrank(coreCouncil); + bytes32 hopId; + hopId = wrapper.setHop(SPARK_RATE_LIMITS, 1 hours, bytes32(0), keccak256("spark-hop"), MIN_DELAY); + _execute(hopId); + hopId = wrapper.setHop(GROVE_RATE_LIMITS, 1 hours, bytes32(0), keccak256("grove-hop"), MIN_DELAY); + _execute(hopId); + vm.stopPrank(); } // ============================================================================ diff --git a/test/timelock/TimelockWrapperMainnet.t.sol b/test/timelock/TimelockWrapperMainnet.t.sol index 03a6dc9..e2acabf 100644 --- a/test/timelock/TimelockWrapperMainnet.t.sol +++ b/test/timelock/TimelockWrapperMainnet.t.sol @@ -153,6 +153,15 @@ contract TimelockWrapperMainnetTest is DssTest { beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); beamState.setCBeamForRateLimits(GROVE_RATE_LIMITS, cBeam); vm.stopPrank(); + + // Set hop for rate limiters (required for setRateLimit to work on increases) + vm.startPrank(coreCouncil); + bytes32 hopId; + hopId = wrapper.setHop(SPARK_RATE_LIMITS, 1 hours, bytes32(0), keccak256("spark-hop"), MIN_DELAY); + _execute(hopId); + hopId = wrapper.setHop(GROVE_RATE_LIMITS, 1 hours, bytes32(0), keccak256("grove-hop"), MIN_DELAY); + _execute(hopId); + vm.stopPrank(); } // ============================================================================ From 0795abab1d0811ecd3fe677412da27213a955863 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:48:13 +0700 Subject: [PATCH 113/150] Add comment about immediate vs delayed actions --- deploy/PASInit.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index ee38cad..a34e335 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -70,6 +70,8 @@ interface PASMomLike { library PASInit { uint256 constant internal WAD = 10**18; + // It is noted that delayed and immediate operations order is non deterministic. + // The relevant operators of each role are assumed to communicate and track timelock and configurator executions. enum Role { _UNSET, // 0 (unused) DELAYED, // 1 From 1a43da0aae5741ce940747990dba1d5bd093c229 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 30 Jan 2026 08:38:50 -0300 Subject: [PATCH 114/150] Remove comment --- test/Integration.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 9253124..47308e8 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -234,7 +234,6 @@ contract IntegrationTest is DssTest { testControllers[0] = SPARK_CONTROLLER; testControllers[1] = address(0x22); - // Deploy helper and give it auth on beamState vm.startPrank(pauseProxy); PASInit.initExtras(freshPas, hop, maxChange, testCBeams, testRateLimits, testControllers); vm.stopPrank(); From b090cc81c0a599e4750619694c8da5933004d6ce Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:03:43 +0700 Subject: [PATCH 115/150] Add comment about EnumerableSet order --- src/timelock/Timelock.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 77851f1..3745aa5 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -143,6 +143,8 @@ contract Timelock is TimelockController, Pausable { // Operations may still not be executable due to various downstream conditions. // It is assumed that this is not a perfect fetching mechanism and that if needed proposals // can be executed without cron keepers, or canceled in case they are jamming this mechanism. + // Also note that EnumerableSet doesn't maintain insertion order (when an operation is removed + // the last element fills its spot). Proposers and keepers are assumed to be aware. function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) external view returns (bytes32 id) { uint256 length = _operationIds.length(); if (startIndex >= length) return bytes32(0); From e89295a4739cd41891dcbad83309c41b44c4760b Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:40:49 -0300 Subject: [PATCH 116/150] Timelock: Replace helper structure from enumerable to linked list (#5) * Timelock: Replace helper structure from enumerable to linked list * Not allow bytes32(0) as valid value * maxIterations change * nit * Add lastCheckedId * Change getNextExecutableOperationId to return just one id and boolean * Improve edge case * Some renaming * Change getNextExecutableOperationId to use startId * Move getNextExecutableOperationId to a helper contract --------- Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- README.md | 2 +- src/timelock/Bytes32LinkedList.sol | 99 +++++ src/timelock/Timelock.sol | 82 +++-- src/timelock/helpers/TimelockHelper.sol | 60 +++ test/Integration.t.sol | 2 +- test/timelock/Bytes32LinkedList.t.sol | 404 +++++++++++++++++++++ test/timelock/Timelock.t.sol | 380 ++++++------------- test/timelock/TimelockHelper.t.sol | 345 ++++++++++++++++++ test/timelock/TimelockWrapperForeign.t.sol | 2 +- test/timelock/TimelockWrapperMainnet.t.sol | 2 +- 10 files changed, 1068 insertions(+), 310 deletions(-) create mode 100644 src/timelock/Bytes32LinkedList.sol create mode 100644 src/timelock/helpers/TimelockHelper.sol create mode 100644 test/timelock/Bytes32LinkedList.t.sol create mode 100644 test/timelock/TimelockHelper.t.sol diff --git a/README.md b/README.md index e171757..6601600 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The operational interface used by cBeams to modify rate limits and execute contr ### Timelock -Extended OpenZeppelin TimelockController with pausing support, permissionless execution, and operation tracking for keeper integration. Disables self-calls to prevent proposals from modifying admin settings. Provides helper functions (`getNextExecutableOperation`, `getOperationIndex`) for automated execution via keeper jobs. +Extended OpenZeppelin TimelockController with pausing support, permissionless execution, and operation tracking for keeper integration. Disables self-calls to prevent proposals from modifying admin settings. Provides helper functions (as `getNextExecutableOperationId`) for automated execution via keeper jobs. ### TimelockWrapperMainnet diff --git a/src/timelock/Bytes32LinkedList.sol b/src/timelock/Bytes32LinkedList.sol new file mode 100644 index 0000000..b1aace0 --- /dev/null +++ b/src/timelock/Bytes32LinkedList.sol @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +/// @title Bytes32LinkedList +/// @notice A double linked list library for bytes32 values +library Bytes32LinkedList { + struct Node { + bytes32 prev; + bytes32 next; + } + + struct List { + bytes32 first; + bytes32 last; + uint256 count; + mapping(bytes32 => Node) nodes; + mapping(bytes32 => bool) exists; + } + + /// @notice Add a new item to the end of the list + /// @param list The list to add to + /// @param id The bytes32 value to add + /// @return success True if the item was added, false if it already exists + function add(List storage list, bytes32 id) internal returns (bool success) { + require(id != bytes32(0), "id-null"); + + if (list.exists[id]) { + return false; + } + + list.exists[id] = true; + list.count++; + + if (list.first == bytes32(0)) { + // First item in the list + list.first = id; + list.last = id; + } else { + // Append to end + list.nodes[id].prev = list.last; + list.nodes[list.last].next = id; + list.last = id; + } + + return true; + } + + /// @notice Remove an item from the list + /// @param list The list to remove from + /// @param id The bytes32 value to remove + /// @return success True if the item was removed, false if it didn't exist + function remove(List storage list, bytes32 id) internal returns (bool success) { + if (!list.exists[id]) { + return false; + } + + Node storage node = list.nodes[id]; + bytes32 prevId = node.prev; + bytes32 nextId = node.next; + + // Update prev node's next pointer + if (prevId != bytes32(0)) { + list.nodes[prevId].next = nextId; + } else { + // Removing first element + list.first = nextId; + } + + // Update next node's prev pointer + if (nextId != bytes32(0)) { + list.nodes[nextId].prev = prevId; + } else { + // Removing last element + list.last = prevId; + } + + // Clean up + delete list.nodes[id]; + list.exists[id] = false; + list.count--; + + return true; + } +} diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 3745aa5..09ce125 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -18,10 +18,10 @@ pragma solidity ^0.8.24; import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol"; import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; -import { EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { Bytes32LinkedList } from "./Bytes32LinkedList.sol"; contract Timelock is TimelockController, Pausable { - using EnumerableSet for EnumerableSet.Bytes32Set; + using Bytes32LinkedList for Bytes32LinkedList.List; bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); @@ -34,7 +34,7 @@ contract Timelock is TimelockController, Pausable { bytes32 salt; } - EnumerableSet.Bytes32Set internal _operationIds; + Bytes32LinkedList.List internal _operationIds; mapping(bytes32 id => Operation) internal _operations; // Changes from original timelock: @@ -98,9 +98,9 @@ contract Timelock is TimelockController, Pausable { for (uint256 i = 0; i < targets.length; ++i) { require(targets[i] != address(this), "Timelock/self-calls-disabled"); } - + super.scheduleBatch(targets, values, payloads, predecessor, salt, delay); - + // Track operation for keeper jobs bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); _operationIds.add(id); @@ -137,52 +137,58 @@ contract Timelock is TimelockController, Pausable { } // ------------------------------------------------------------------------ - // Keeper job helpers + // Operations getters // ------------------------------------------------------------------------ - // Operations may still not be executable due to various downstream conditions. - // It is assumed that this is not a perfect fetching mechanism and that if needed proposals - // can be executed without cron keepers, or canceled in case they are jamming this mechanism. - // Also note that EnumerableSet doesn't maintain insertion order (when an operation is removed - // the last element fills its spot). Proposers and keepers are assumed to be aware. - function getNextExecutableOperation(uint256 startIndex, uint256 maxIterations) external view returns (bytes32 id) { - uint256 length = _operationIds.length(); - if (startIndex >= length) return bytes32(0); + function getFirstOperationId() external view returns (bytes32) { + return _operationIds.first; + } - // Safe: if maxIterations is 0 (no limit) or exceeds remaining operations, scan to end - uint256 remaining = length - startIndex; - uint256 endIndex = (maxIterations == 0 || maxIterations >= remaining) ? length : startIndex + maxIterations; + function getLastOperationId() external view returns (bytes32) { + return _operationIds.last; + } - for (uint256 i = startIndex; i < endIndex; ++i) { - bytes32 operationId = _operationIds.at(i); + function getOperationsCount() external view returns (uint256) { + return _operationIds.count; + } - // Check if operation is ready - if (!isOperationReady(operationId)) continue; + function getPrevOperationId(bytes32 id) external view returns (bytes32) { + return _operationIds.nodes[id].prev; + } - // Check if predecessor is done, if any - bytes32 predecessor = _operations[operationId].predecessor; - if (predecessor != bytes32(0) && !isOperationDone(predecessor)) continue; + function getNextOperationId(bytes32 id) external view returns (bytes32) { + return _operationIds.nodes[id].next; + } - return operationId; - } + function getOperationExists(bytes32 id) external view returns (bool) { + return _operationIds.exists[id]; + } - return bytes32(0); + function getOperation(bytes32 id) external view returns (Operation memory op) { + return _operations[id]; } - function getOperationIndex(bytes32 id) external view returns (bool exists, uint256 index) { - uint256 position = _operationIds._inner._positions[id]; - if (position == 0) { - return (false, 0); - } - return (true, position - 1); + function getOperationLength(bytes32 id) external view returns (uint256) { + return _operations[id].targets.length; } - function getOperationCount() external view returns (uint256) { - return _operationIds.length(); + function getOperationTarget(bytes32 id, uint256 index) external view returns (address) { + return _operations[id].targets[index]; } - function getOperation(bytes32 id) external view returns (Operation memory op) { - return _operations[id]; + function getOperationValue(bytes32 id, uint256 index) external view returns (uint256) { + return _operations[id].values[index]; + } + + function getOperationPayload(bytes32 id, uint256 index) external view returns (bytes memory) { + return _operations[id].payloads[index]; + } + + function getOperationPredecessor(bytes32 id) external view returns (bytes32) { + return _operations[id].predecessor; } -} + function getOperationSalt(bytes32 id) external view returns (bytes32) { + return _operations[id].salt; + } +} diff --git a/src/timelock/helpers/TimelockHelper.sol b/src/timelock/helpers/TimelockHelper.sol new file mode 100644 index 0000000..03b5cf1 --- /dev/null +++ b/src/timelock/helpers/TimelockHelper.sol @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +interface TimelockLike { + function isOperationReady(bytes32) external view returns (bool); + function isOperationDone(bytes32) external view returns (bool); + function getFirstOperationId() external view returns (bytes32); + function getNextOperationId(bytes32) external view returns (bytes32); + function getOperationExists(bytes32) external view returns (bool); + function getOperationPredecessor(bytes32) external view returns (bytes32); +} + +contract TimelockHelper { + + TimelockLike immutable public timelock; + + constructor( + address timelock_ + ) { + timelock = TimelockLike(timelock_); + } + + // Operations may still not be executable due to various downstream conditions. + // It is assumed that this is not a perfect fetching mechanism and that if needed proposals + // can be executed without cron keepers, or canceled in case they are jamming this mechanism. + // returns - If found: the executable operation. If not found: the next startId to continue from, or bytes32(0) if exhausted. + function getNextExecutableOperationId(bytes32 startId, uint256 maxIterations) external view returns (bool found, bytes32 id) { + require(maxIterations > 0, "Timelock/zero-maxIterations"); + require(startId == bytes32(0) || timelock.getOperationExists(startId), "Timelock/invalid-startId"); + + id = startId == bytes32(0) ? timelock.getFirstOperationId() : startId; + + uint256 i = 0; + while (id != bytes32(0) && i++ < maxIterations) { + if (timelock.isOperationReady(id)) { + bytes32 predecessor = timelock.getOperationPredecessor(id); + if (predecessor == bytes32(0) || timelock.isOperationDone(predecessor)) return (true, id); + } + + id = timelock.getNextOperationId(id); + } + + return (false, id); + } +} diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 47308e8..8d5fe78 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -531,7 +531,7 @@ contract IntegrationTest is DssTest { timelock.cancel(operationId); assertFalse(timelock.isOperationPending(operationId), "operation should be cancelled"); - assertEq(timelock.getOperationCount(), 0, "operation count should be 0"); + assertEq(timelock.getOperationsCount(), 0, "operation count should be 0"); } function testCancellerCanCancelOperation() public { diff --git a/test/timelock/Bytes32LinkedList.t.sol b/test/timelock/Bytes32LinkedList.t.sol new file mode 100644 index 0000000..f1c8647 --- /dev/null +++ b/test/timelock/Bytes32LinkedList.t.sol @@ -0,0 +1,404 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +import { Test } from "forge-std/Test.sol"; +import { Bytes32LinkedList } from "src/timelock/Bytes32LinkedList.sol"; + +contract LinkedListWrapper { + using Bytes32LinkedList for Bytes32LinkedList.List; + + Bytes32LinkedList.List internal list; + + function add(bytes32 id) external returns (bool) { + return list.add(id); + } + + function remove(bytes32 id) external returns (bool) { + return list.remove(id); + } + + function exists(bytes32 id) external view returns (bool) { + return list.exists[id]; + } + + function count() external view returns (uint256) { + return list.count; + } + + function first() external view returns (bytes32) { + return list.first; + } + + function last() external view returns (bytes32) { + return list.last; + } + + function prev(bytes32 id) external view returns (bytes32) { + return list.nodes[id].prev; + } + + function next(bytes32 id) external view returns (bytes32) { + return list.nodes[id].next; + } +} + +contract Bytes32LinkedListTest is Test { + LinkedListWrapper public wrapper; + + bytes32 public constant ID_A = keccak256("A"); + bytes32 public constant ID_B = keccak256("B"); + bytes32 public constant ID_C = keccak256("C"); + bytes32 public constant ID_D = keccak256("D"); + + function setUp() public { + wrapper = new LinkedListWrapper(); + } + + // ============================================================================ + // Empty List Tests + // ============================================================================ + + function testEmptyListState() public view { + assertEq(wrapper.count(), 0); + assertEq(wrapper.first(), bytes32(0)); + assertEq(wrapper.last(), bytes32(0)); + assertFalse(wrapper.exists(ID_A)); + } + + function testRemoveFromEmptyList() public { + bool success = wrapper.remove(ID_A); + assertFalse(success); + assertEq(wrapper.count(), 0); + } + + // ============================================================================ + // Add Tests + // ============================================================================ + + function testAddSingleElement() public { + bool success = wrapper.add(ID_A); + + assertTrue(success); + assertEq(wrapper.count(), 1); + assertEq(wrapper.first(), ID_A); + assertEq(wrapper.last(), ID_A); + assertTrue(wrapper.exists(ID_A)); + assertEq(wrapper.prev(ID_A), bytes32(0)); + assertEq(wrapper.next(ID_A), bytes32(0)); + } + + function testAddTwoElements() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + + assertEq(wrapper.count(), 2); + assertEq(wrapper.first(), ID_A); + assertEq(wrapper.last(), ID_B); + + // A <-> B + assertEq(wrapper.prev(ID_A), bytes32(0)); + assertEq(wrapper.next(ID_A), ID_B); + assertEq(wrapper.prev(ID_B), ID_A); + assertEq(wrapper.next(ID_B), bytes32(0)); + } + + function testAddThreeElements() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + + assertEq(wrapper.count(), 3); + assertEq(wrapper.first(), ID_A); + assertEq(wrapper.last(), ID_C); + + // A <-> B <-> C + assertEq(wrapper.prev(ID_A), bytes32(0)); + assertEq(wrapper.next(ID_A), ID_B); + assertEq(wrapper.prev(ID_B), ID_A); + assertEq(wrapper.next(ID_B), ID_C); + assertEq(wrapper.prev(ID_C), ID_B); + assertEq(wrapper.next(ID_C), bytes32(0)); + } + + function testAddDuplicateReturnsFalse() public { + assertTrue(wrapper.add(ID_A)); + assertFalse(wrapper.add(ID_A)); + + assertEq(wrapper.count(), 1); + } + + function testAddingBytes32ZeroReverts() public { + vm.expectRevert("id-null"); + wrapper.add(bytes32(0)); + } + + // ============================================================================ + // Remove Tests + // ============================================================================ + + function testRemoveSingleElement() public { + wrapper.add(ID_A); + + bool success = wrapper.remove(ID_A); + + assertTrue(success); + assertEq(wrapper.count(), 0); + assertEq(wrapper.first(), bytes32(0)); + assertEq(wrapper.last(), bytes32(0)); + assertFalse(wrapper.exists(ID_A)); + } + + function testRemoveFirstOfTwo() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + + wrapper.remove(ID_A); + + assertEq(wrapper.count(), 1); + assertEq(wrapper.first(), ID_B); + assertEq(wrapper.last(), ID_B); + assertFalse(wrapper.exists(ID_A)); + assertTrue(wrapper.exists(ID_B)); + assertEq(wrapper.prev(ID_B), bytes32(0)); + assertEq(wrapper.next(ID_B), bytes32(0)); + } + + function testRemoveLastOfTwo() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + + wrapper.remove(ID_B); + + assertEq(wrapper.count(), 1); + assertEq(wrapper.first(), ID_A); + assertEq(wrapper.last(), ID_A); + assertTrue(wrapper.exists(ID_A)); + assertFalse(wrapper.exists(ID_B)); + assertEq(wrapper.prev(ID_A), bytes32(0)); + assertEq(wrapper.next(ID_A), bytes32(0)); + } + + function testRemoveMiddleOfThree() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + + wrapper.remove(ID_B); + + assertEq(wrapper.count(), 2); + assertEq(wrapper.first(), ID_A); + assertEq(wrapper.last(), ID_C); + assertTrue(wrapper.exists(ID_A)); + assertFalse(wrapper.exists(ID_B)); + assertTrue(wrapper.exists(ID_C)); + + // A <-> C (B removed) + assertEq(wrapper.prev(ID_A), bytes32(0)); + assertEq(wrapper.next(ID_A), ID_C); + assertEq(wrapper.prev(ID_C), ID_A); + assertEq(wrapper.next(ID_C), bytes32(0)); + } + + function testRemoveFirstOfThree() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + + wrapper.remove(ID_A); + + assertEq(wrapper.count(), 2); + assertEq(wrapper.first(), ID_B); + assertEq(wrapper.last(), ID_C); + + // B <-> C + assertEq(wrapper.prev(ID_B), bytes32(0)); + assertEq(wrapper.next(ID_B), ID_C); + assertEq(wrapper.prev(ID_C), ID_B); + assertEq(wrapper.next(ID_C), bytes32(0)); + } + + function testRemoveLastOfThree() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + + wrapper.remove(ID_C); + + assertEq(wrapper.count(), 2); + assertEq(wrapper.first(), ID_A); + assertEq(wrapper.last(), ID_B); + + // A <-> B + assertEq(wrapper.prev(ID_A), bytes32(0)); + assertEq(wrapper.next(ID_A), ID_B); + assertEq(wrapper.prev(ID_B), ID_A); + assertEq(wrapper.next(ID_B), bytes32(0)); + } + + function testRemoveNonExistentReturnsFalse() public { + wrapper.add(ID_A); + + bool success = wrapper.remove(ID_B); + + assertFalse(success); + assertEq(wrapper.count(), 1); + } + + function testRemoveAlreadyRemovedReturnsFalse() public { + wrapper.add(ID_A); + wrapper.remove(ID_A); + + bool success = wrapper.remove(ID_A); + + assertFalse(success); + assertEq(wrapper.count(), 0); + } + + // ============================================================================ + // Order Preservation Tests + // ============================================================================ + + function testOrderPreservedAfterMiddleRemoval() public { + // Add A, B, C, D + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + wrapper.add(ID_D); + + // Remove B and C + wrapper.remove(ID_B); + wrapper.remove(ID_C); + + // Should have A <-> D + assertEq(wrapper.count(), 2); + assertEq(wrapper.first(), ID_A); + assertEq(wrapper.last(), ID_D); + assertEq(wrapper.next(ID_A), ID_D); + assertEq(wrapper.prev(ID_D), ID_A); + } + + function testCanReaddRemovedElement() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.remove(ID_A); + + // Re-add A, should go to end + assertTrue(wrapper.add(ID_A)); + + assertEq(wrapper.count(), 2); + assertEq(wrapper.first(), ID_B); + assertEq(wrapper.last(), ID_A); + + // B <-> A + assertEq(wrapper.next(ID_B), ID_A); + assertEq(wrapper.prev(ID_A), ID_B); + } + + // ============================================================================ + // Traversal Tests + // ============================================================================ + + function testForwardTraversal() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + + bytes32 current = wrapper.first(); + assertEq(current, ID_A); + + current = wrapper.next(current); + assertEq(current, ID_B); + + current = wrapper.next(current); + assertEq(current, ID_C); + + current = wrapper.next(current); + assertEq(current, bytes32(0)); + } + + function testBackwardTraversal() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + + bytes32 current = wrapper.last(); + assertEq(current, ID_C); + + current = wrapper.prev(current); + assertEq(current, ID_B); + + current = wrapper.prev(current); + assertEq(current, ID_A); + + current = wrapper.prev(current); + assertEq(current, bytes32(0)); + } + + // ============================================================================ + // Edge Cases + // ============================================================================ + + function testRemoveAllElementsOneByOne() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + + wrapper.remove(ID_A); + assertEq(wrapper.count(), 2); + assertEq(wrapper.first(), ID_B); + assertEq(wrapper.last(), ID_C); + + wrapper.remove(ID_B); + assertEq(wrapper.count(), 1); + assertEq(wrapper.first(), ID_C); + assertEq(wrapper.last(), ID_C); + + wrapper.remove(ID_C); + assertEq(wrapper.count(), 0); + assertEq(wrapper.first(), bytes32(0)); + assertEq(wrapper.last(), bytes32(0)); + } + + function testRemoveInReverseOrder() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + + wrapper.remove(ID_C); + assertEq(wrapper.last(), ID_B); + + wrapper.remove(ID_B); + assertEq(wrapper.last(), ID_A); + + wrapper.remove(ID_A); + assertEq(wrapper.count(), 0); + } + + function testNodeCleanupAfterRemoval() public { + wrapper.add(ID_A); + wrapper.add(ID_B); + wrapper.add(ID_C); + + wrapper.remove(ID_B); + + // B's node data should be cleared + assertEq(wrapper.prev(ID_B), bytes32(0)); + assertEq(wrapper.next(ID_B), bytes32(0)); + assertFalse(wrapper.exists(ID_B)); + } +} diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol index a4592a2..8b2882f 100644 --- a/test/timelock/Timelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -46,9 +46,9 @@ contract MockTarget { } contract TimelockTest is Test { - Timelock public timelock; - MockTarget public mockTarget; - MockTarget public mockTarget2; + Timelock public timelock; + MockTarget public mockTarget; + MockTarget public mockTarget2; address public admin; address public proposer; @@ -83,9 +83,9 @@ contract TimelockTest is Test { pauser = address(0x4); other = address(0x5); - timelock = new Timelock(MIN_DELAY, admin); - mockTarget = new MockTarget(); - mockTarget2 = new MockTarget(); + timelock = new Timelock(MIN_DELAY, admin); + mockTarget = new MockTarget(); + mockTarget2 = new MockTarget(); // Grant roles after deployment vm.startPrank(admin); @@ -197,7 +197,7 @@ contract TimelockTest is Test { vm.prank(newProposer); _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - assertEq(timelock.getOperationCount(), 1); + assertEq(timelock.getOperationsCount(), 1); } function testAdminCanRevokeRole() public { @@ -389,7 +389,7 @@ contract TimelockTest is Test { vm.stopPrank(); assertTrue(id1 != id2); - assertEq(timelock.getOperationCount(), 2); + assertEq(timelock.getOperationsCount(), 2); } function testMultipleProposersCanSchedule() public { @@ -402,7 +402,7 @@ contract TimelockTest is Test { vm.prank(proposer2); _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("s2"), MIN_DELAY); - assertEq(timelock.getOperationCount(), 2); + assertEq(timelock.getOperationsCount(), 2); } // ============================================================================ @@ -678,7 +678,7 @@ contract TimelockTest is Test { vm.prank(canceller); timelock.cancel(id); - assertEq(timelock.getOperationCount(), 0); + assertEq(timelock.getOperationsCount(), 0); } function testProposerCanCancelOtherProposersOperation() public { @@ -690,7 +690,7 @@ contract TimelockTest is Test { vm.prank(proposer2); timelock.cancel(id); - assertEq(timelock.getOperationCount(), 0); + assertEq(timelock.getOperationsCount(), 0); } // ============================================================================ @@ -804,7 +804,7 @@ contract TimelockTest is Test { vm.prank(proposer); _schedule(address(mockTarget), 0, data, bytes32(0), SALT, 2 days); - assertEq(timelock.getOperationCount(), 1); + assertEq(timelock.getOperationsCount(), 1); } function testUpdateDelayImmediatelyProposerCannotChangeDelay() public { @@ -891,221 +891,21 @@ contract TimelockTest is Test { assertEq(mockTarget2.lastEthReceived(), 2 ether); } - // ============================================================================ - // Keeper Helper Function Tests - // ============================================================================ - - function testGetNextExecutableOperationEmpty() public view { - assertEq(timelock.getNextExecutableOperation(0, 0), bytes32(0)); - } - - function testGetNextExecutableOperationNotReady() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - assertEq(timelock.getNextExecutableOperation(0, 0), bytes32(0)); - } - - function testGetNextExecutableOperationReady() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.prank(proposer); - bytes32 expectedId = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - assertEq(timelock.getNextExecutableOperation(0, 0), expectedId); - } - - function testGetNextExecutableOperationMultipleReturnsFirst() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - bytes memory data3 = abi.encodeWithSelector(MockTarget.setValue.selector, 3); - - vm.startPrank(proposer); - bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); - - vm.warp(block.timestamp + 1 hours); - _schedule(address(mockTarget), 0, data2, bytes32(0), keccak256("s2"), MIN_DELAY); - - vm.warp(block.timestamp + 1 hours); - _schedule(address(mockTarget), 0, data3, bytes32(0), keccak256("s3"), MIN_DELAY); - vm.stopPrank(); - - vm.warp(block.timestamp + MIN_DELAY); - - assertEq(timelock.getNextExecutableOperation(0, 0), id1); - } - - function testGetNextExecutableOperationChangesAfterExecution() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - - vm.startPrank(proposer); - bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); - bytes32 id2 = _schedule(address(mockTarget), 0, data2, id1, keccak256("s2"), MIN_DELAY); - vm.stopPrank(); - - vm.warp(block.timestamp + MIN_DELAY); - - assertEq(timelock.getNextExecutableOperation(0, 0), id1); - - _execute(address(mockTarget), 0, data1, bytes32(0), keccak256("s1")); - - assertEq(timelock.getNextExecutableOperation(0, 0), id2); - } - - function testGetNextExecutableOperationWithMaxIterations() public { - // Schedule 5 operations, only the 4th one is ready - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.startPrank(proposer); - // Schedule 3 operations with long delay (not ready) - _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s1"), 10 days); - _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), 10 days); - _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s3"), 10 days); - // Schedule 1 operation with short delay (will be ready) - bytes32 readyId = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s4"), MIN_DELAY); - // Schedule 1 more with long delay - _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s5"), 10 days); - vm.stopPrank(); - - vm.warp(block.timestamp + MIN_DELAY); - - // With maxIterations=3, should not find the ready operation (it's at index 3); - assertEq(timelock.getNextExecutableOperation(0, 3), bytes32(0), "Should not find with limit 3"); - - // With maxIterations=4, should find it - assertEq(timelock.getNextExecutableOperation(0, 4), readyId, "Should find with limit 4"); - - // With maxIterations=100, should also find it - assertEq(timelock.getNextExecutableOperation(0, 100), readyId, "Should find with limit 100"); - } - - function testGetNextExecutableOperationZeroMeansNoLimit() public { - // Schedule many operations, put the ready one at the end - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - - vm.startPrank(proposer); - // Schedule 10 operations with long delay - for (uint256 i = 0; i < 10; i++) { - _schedule(address(mockTarget), 0, data, bytes32(0), keccak256(abi.encodePacked("long", i)), 10 days); - } - // Schedule 1 ready operation at the end - bytes32 readyId = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("ready"), MIN_DELAY); - vm.stopPrank(); - - vm.warp(block.timestamp + MIN_DELAY); - - // With maxIterations=0 (no limit), should find it even though it's at index 10 - assertEq(timelock.getNextExecutableOperation(0, 0), readyId, "Zero should mean no limit"); - } - - function testGetNextExecutableOperationStartIndexPastEnd() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - vm.prank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - // length=1, startIndex=5 -> returns bytes32(0) - assertEq(timelock.getNextExecutableOperation(5, 0), bytes32(0)); - } - - function testGetNextExecutableOperationLargeMaxIterations() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - vm.prank(proposer); - bytes32 expectedId = _schedule(address(mockTarget), 0, data, bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // Very large maxIterations should not overflow - assertEq(timelock.getNextExecutableOperation(0, type(uint256).max), expectedId); + function testGetOperationNonExistent() public view { + Timelock.Operation memory op = timelock.getOperation(keccak256("fake")); + assertEq(op.targets.length, 0); + assertEq(op.values.length, 0); + assertEq(op.payloads.length, 0); } - function testGetNextExecutableOperationPagination() public { + function testLinkedListGetters() public { bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); - vm.startPrank(proposer); - _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s0"), 10 days); // not ready - bytes32 id1 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s1"), MIN_DELAY); // ready - _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), 10 days); // not ready - bytes32 id3 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s3"), MIN_DELAY); // ready - vm.stopPrank(); - - vm.warp(block.timestamp + MIN_DELAY); - - // First call finds id1 - assertEq(timelock.getNextExecutableOperation(0, 0), id1); - - // Get the index of id1 - (bool exists, uint256 index) = timelock.getOperationIndex(id1); - assertTrue(exists); - assertEq(index, 1); - - // Execute id1, then continue from same index (swap-and-pop puts last element there) - _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s1")); - - // Continue from same index, finds id3 (which was swapped from the end) - assertEq(timelock.getNextExecutableOperation(index, 0), id3); - - // Get index of id3 - (exists, index) = timelock.getOperationIndex(id3); - assertTrue(exists); - - // Execute id3 - _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s3")); - - // Continue from same index, nothing ready left - assertEq(timelock.getNextExecutableOperation(index, 0), bytes32(0)); - } - - function testGetNextExecutableOperationSkipsPendingPredecessor() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - bytes memory data3 = abi.encodeWithSelector(MockTarget.setValue.selector, 3); - - vm.startPrank(proposer); - // Schedule id1 with long delay (won't be ready) - bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), 10 days); - // Schedule id2 with id1 as predecessor (ready but predecessor not done) - _schedule(address(mockTarget), 0, data2, id1, keccak256("s2"), MIN_DELAY); - // Schedule id3 with no predecessor (ready) - bytes32 id3 = _schedule(address(mockTarget), 0, data3, bytes32(0), keccak256("s3"), MIN_DELAY); - vm.stopPrank(); - - vm.warp(block.timestamp + MIN_DELAY); - - // Should skip id1 (not ready), skip id2 (predecessor not done), return id3 - assertEq(timelock.getNextExecutableOperation(0, 0), id3); - } - - function testGetNextExecutableOperationWithDonePredecessor() public { - bytes memory data1 = abi.encodeWithSelector(MockTarget.setValue.selector, 1); - bytes memory data2 = abi.encodeWithSelector(MockTarget.setValue.selector, 2); - - // Schedule operation A (no predecessor) - vm.prank(proposer); - bytes32 id1 = _schedule(address(mockTarget), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); - - // Schedule operation B with A as predecessor - vm.prank(proposer); - bytes32 id2 = _schedule(address(mockTarget), 0, data2, id1, keccak256("s2"), MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - // Before executing predecessor: should return id1 (id2's predecessor not done) - assertEq(timelock.getNextExecutableOperation(0, 0), id1); - - // Execute id1 (predecessor is now done) - _execute(address(mockTarget), 0, data1, bytes32(0), keccak256("s1")); - - // Now id2's predecessor is done, should return id2 - assertEq(timelock.getNextExecutableOperation(0, 0), id2); - } - - function testGetOperationIndex() public { - bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + // Empty list + assertEq(timelock.getOperationsCount(), 0); + assertEq(timelock.getFirstOperationId(), bytes32(0)); + assertEq(timelock.getLastOperationId(), bytes32(0)); + assertFalse(timelock.getOperationExists(keccak256("fake"))); // Schedule 3 operations vm.startPrank(proposer); @@ -1114,53 +914,48 @@ contract TimelockTest is Test { bytes32 id2 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), MIN_DELAY); vm.stopPrank(); - // Verify initial indices - (bool exists0, uint256 index0) = timelock.getOperationIndex(id0); - (bool exists1, uint256 index1) = timelock.getOperationIndex(id1); - (bool exists2, uint256 index2) = timelock.getOperationIndex(id2); + // Verify count + assertEq(timelock.getOperationsCount(), 3); - assertTrue(exists0); - assertTrue(exists1); - assertTrue(exists2); - assertEq(index0, 0); - assertEq(index1, 1); - assertEq(index2, 2); + // Verify existence + assertTrue(timelock.getOperationExists(id0)); + assertTrue(timelock.getOperationExists(id1)); + assertTrue(timelock.getOperationExists(id2)); + assertFalse(timelock.getOperationExists(keccak256("fake"))); - // Execute id0 (index 0) - id2 should swap into index 0 - vm.warp(block.timestamp + MIN_DELAY); - _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s0")); + // Verify first and last + assertEq(timelock.getFirstOperationId(), id0); + assertEq(timelock.getLastOperationId(), id2); - // id0 no longer exists - (exists0,) = timelock.getOperationIndex(id0); - assertFalse(exists0); + // Verify prev/next chain: id0 <-> id1 <-> id2 + assertEq(timelock.getPrevOperationId(id0), bytes32(0)); + assertEq(timelock.getNextOperationId(id0), id1); + assertEq(timelock.getPrevOperationId(id1), id0); + assertEq(timelock.getNextOperationId(id1), id2); + assertEq(timelock.getPrevOperationId(id2), id1); + assertEq(timelock.getNextOperationId(id2), bytes32(0)); - // id1 unchanged at index 1 - (exists1, index1) = timelock.getOperationIndex(id1); - assertTrue(exists1); - assertEq(index1, 1); + // Remove middle element (id1) via cancel + vm.prank(proposer); + timelock.cancel(id1); - // id2 swapped to index 0 - (exists2, index2) = timelock.getOperationIndex(id2); - assertTrue(exists2); - assertEq(index2, 0); + // Verify count after removal + assertEq(timelock.getOperationsCount(), 2); - // Execute id1 (index 1) - it's now last, so just removed - _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s1")); + // Verify existence after removal + assertTrue(timelock.getOperationExists(id0)); + assertFalse(timelock.getOperationExists(id1)); + assertTrue(timelock.getOperationExists(id2)); - (exists1,) = timelock.getOperationIndex(id1); - assertFalse(exists1); + // Verify first and last unchanged + assertEq(timelock.getFirstOperationId(), id0); + assertEq(timelock.getLastOperationId(), id2); - // id2 still at index 0 - (exists2, index2) = timelock.getOperationIndex(id2); - assertTrue(exists2); - assertEq(index2, 0); - } - - function testGetOperationNonExistent() public view { - Timelock.Operation memory op = timelock.getOperation(keccak256("fake")); - assertEq(op.targets.length, 0); - assertEq(op.values.length, 0); - assertEq(op.payloads.length, 0); + // Verify chain after removal: id0 <-> id2 + assertEq(timelock.getPrevOperationId(id0), bytes32(0)); + assertEq(timelock.getNextOperationId(id0), id2); + assertEq(timelock.getPrevOperationId(id2), id0); + assertEq(timelock.getNextOperationId(id2), bytes32(0)); } function testGetOperationAfterExecution() public { @@ -1193,28 +988,28 @@ contract TimelockTest is Test { } function testOperationCountTracking() public { - assertEq(timelock.getOperationCount(), 0); + assertEq(timelock.getOperationsCount(), 0); bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); vm.startPrank(proposer); bytes32 id1 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s1"), MIN_DELAY); - assertEq(timelock.getOperationCount(), 1); + assertEq(timelock.getOperationsCount(), 1); _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), MIN_DELAY); - assertEq(timelock.getOperationCount(), 2); + assertEq(timelock.getOperationsCount(), 2); _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s3"), MIN_DELAY); - assertEq(timelock.getOperationCount(), 3); + assertEq(timelock.getOperationsCount(), 3); vm.stopPrank(); vm.prank(canceller); timelock.cancel(id1); - assertEq(timelock.getOperationCount(), 2); + assertEq(timelock.getOperationsCount(), 2); vm.warp(block.timestamp + MIN_DELAY); _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s2")); - assertEq(timelock.getOperationCount(), 1); + assertEq(timelock.getOperationsCount(), 1); } function testOperationDataIntegrity() public { @@ -1250,4 +1045,53 @@ contract TimelockTest is Test { assertEq(op.salt, salt); } + function testIndividualGettersMatchCombinedGetter() public { + // Create a batch operation with multiple calls + address[] memory targets = new address[](3); + targets[0] = address(mockTarget); + targets[1] = address(mockTarget2); + targets[2] = address(mockTarget); + + uint256[] memory values = new uint256[](3); + values[0] = 1 ether; + values[1] = 2 ether; + values[2] = 0; + + bytes[] memory payloads = new bytes[](3); + payloads[0] = abi.encodeWithSelector(MockTarget.receiveEther.selector); + payloads[1] = abi.encodeWithSelector(MockTarget.setValue.selector, 100); + payloads[2] = abi.encodeWithSelector(MockTarget.setValue.selector, 200); + + bytes32 predecessor = keccak256("test-predecessor"); + bytes32 salt = keccak256("test-salt"); + + vm.prank(proposer); + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, MIN_DELAY); + + bytes32 id = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + + // Get the full operation using combined getter + Timelock.Operation memory op = timelock.getOperation(id); + + // Verify individual getters match the origina data + assertEq(timelock.getOperationLength(id), 3, "Length mismatch"); + assertEq(timelock.getOperationPredecessor(id), predecessor, "Predecessor mismatch"); + assertEq(timelock.getOperationSalt(id), salt, "Salt mismatch"); + + // Verify individual getters match the combined getter + assertEq(timelock.getOperationLength(id), op.targets.length, "Length mismatch"); + assertEq(timelock.getOperationPredecessor(id), op.predecessor, "Predecessor mismatch"); + assertEq(timelock.getOperationSalt(id), op.salt, "Salt mismatch"); + + // Verify each index matches + for (uint256 i = 0; i < op.targets.length; i++) { + assertEq(timelock.getOperationTarget(id, i), targets[i], "Target mismatch at index"); + assertEq(timelock.getOperationValue(id, i), values[i], "Value mismatch at index"); + assertEq(timelock.getOperationPayload(id, i), payloads[i], "Payload mismatch at index"); + + assertEq(timelock.getOperationTarget(id, i), op.targets[i], "Target mismatch at index"); + assertEq(timelock.getOperationValue(id, i), op.values[i], "Value mismatch at index"); + assertEq(timelock.getOperationPayload(id, i), op.payloads[i], "Payload mismatch at index"); + } + } } diff --git a/test/timelock/TimelockHelper.t.sol b/test/timelock/TimelockHelper.t.sol new file mode 100644 index 0000000..cfce5ed --- /dev/null +++ b/test/timelock/TimelockHelper.t.sol @@ -0,0 +1,345 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +import { Test } from "forge-std/Test.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; +import { TimelockHelper } from "src/timelock/helpers/TimelockHelper.sol"; + +contract TimelockHelperTest is Test { + Timelock public timelock; + TimelockHelper public timelockHelper; + + uint256 public constant MIN_DELAY = 1 days; + bytes32 public constant SALT = keccak256("test-salt"); + + function setUp() public { + timelock = new Timelock(MIN_DELAY, address(this)); + timelockHelper = new TimelockHelper(address(timelock)); + + timelock.grantRole(timelock.PROPOSER_ROLE(), address(this)); + timelock.grantRole(timelock.CANCELLER_ROLE(), address(this)); + } + + function _schedule( + address target, + uint256 value, + bytes memory data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) internal returns (bytes32) { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory values = new uint256[](1); + values[0] = value; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + } + + function _execute( + address target, + uint256 value, + bytes memory data, + bytes32 predecessor, + bytes32 salt + ) internal { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory values = new uint256[](1); + values[0] = value; + bytes[] memory payloads = new bytes[](1); + payloads[0] = data; + timelock.executeBatch{value: value}(targets, values, payloads, predecessor, salt); + } + + function testGetNextExecutableOperationEmpty() public view { + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, bytes32(0)); + assertFalse(found); + } + + function testGetNextExecutableOperationNotReady() public { + bytes memory data = abi.encodeWithSignature("random(uint256)", 42); + + _schedule(address(123), 0, data, bytes32(0), SALT, MIN_DELAY); + + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, bytes32(0)); + assertFalse(found); + } + + function testGetNextExecutableOperationReady() public { + bytes memory data = abi.encodeWithSignature("random(uint256)", 42); + + bytes32 expectedId = _schedule(address(123), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, expectedId); + assertTrue(found); + } + + function testGetNextExecutableOperationMultipleReturnsFirst() public { + bytes memory data1 = abi.encodeWithSignature("random(uint256)", 1); + bytes memory data2 = abi.encodeWithSignature("random(uint256)", 2); + bytes memory data3 = abi.encodeWithSignature("random(uint256)", 3); + + bytes32 id1 = _schedule(address(123), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + + vm.warp(block.timestamp + 1 hours); + _schedule(address(123), 0, data2, bytes32(0), keccak256("s2"), MIN_DELAY); + + vm.warp(block.timestamp + 1 hours); + _schedule(address(123), 0, data3, bytes32(0), keccak256("s3"), MIN_DELAY); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, id1); + assertTrue(found); + } + + function testGetNextExecutableOperationChangesAfterExecution() public { + bytes memory data1 = abi.encodeWithSignature("random(uint256)", 1); + bytes memory data2 = abi.encodeWithSignature("random(uint256)", 2); + + bytes32 id1 = _schedule(address(123), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + bytes32 id2 = _schedule(address(123), 0, data2, id1, keccak256("s2"), MIN_DELAY); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, id1); + assertTrue(found); + + _execute(address(123), 0, data1, bytes32(0), keccak256("s1")); + + (found, id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, id2); + assertTrue(found); + } + + function testGetNextExecutableOperationWithMaxIterations() public { + // Schedule 5 operations, only the 4th one is ready + bytes memory data = abi.encodeWithSignature("random(uint256)", 42); + + // Schedule 3 operations with long delay (not ready) + _schedule(address(123), 0, data, bytes32(0), keccak256("s1"), 10 days); + _schedule(address(123), 0, data, bytes32(0), keccak256("s2"), 10 days); + _schedule(address(123), 0, data, bytes32(0), keccak256("s3"), 10 days); + // Schedule 1 operation with short delay (will be ready) + bytes32 expectedReadyId = _schedule(address(123), 0, data, bytes32(0), keccak256("s4"), MIN_DELAY); + // Schedule 1 more with long delay + _schedule(address(123), 0, data, bytes32(0), keccak256("s5"), 10 days); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + // With maxIterations=3, should not find the ready operation (it's at position 4) + // Returns the next id to continue from (s4) with found=false + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), 3); + assertEq(id, expectedReadyId, "Should return next id to check with limit 3"); + assertFalse(found, "Should not be found with limit 3"); + + // With maxIterations=4, should find it + (found, id) = timelockHelper.getNextExecutableOperationId(bytes32(0), 4); + assertEq(id, expectedReadyId, "Should find with limit 4"); + assertTrue(found, "Should be found with limit 4"); + + // With maxIterations=100, should also find it + (found, id) = timelockHelper.getNextExecutableOperationId(bytes32(0), 100); + assertEq(id, expectedReadyId, "Should find with limit 100"); + assertTrue(found, "Should be found with limit 100"); + } + + function testGetNextExecutableOperationZeroMaxIterationsReverts() public { + vm.expectRevert("Timelock/zero-maxIterations"); + timelockHelper.getNextExecutableOperationId(bytes32(0), 0); + } + + function testGetNextExecutableOperationStartWithNonExistentIdReverts() public { + bytes memory data = abi.encodeWithSignature("random(uint256)", 42); + _schedule(address(123), 0, data, bytes32(0), SALT, MIN_DELAY); + + // Starting with a non-existent ID reverts + vm.expectRevert("Timelock/invalid-startId"); + timelockHelper.getNextExecutableOperationId(keccak256("nonexistent"), type(uint256).max); + } + + function testGetNextExecutableOperationLargeMaxIterations() public { + bytes memory data = abi.encodeWithSignature("random(uint256)", 42); + bytes32 expectedId = _schedule(address(123), 0, data, bytes32(0), SALT, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Very large maxIterations should not overflow + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, expectedId); + assertTrue(found); + } + + function testGetNextExecutableOperationPagination() public { + bytes memory data = abi.encodeWithSignature("random(uint256)", 42); + + _schedule(address(123), 0, data, bytes32(0), keccak256("s0"), 10 days); // not ready + bytes32 id1 = _schedule(address(123), 0, data, bytes32(0), keccak256("s1"), MIN_DELAY); // ready + _schedule(address(123), 0, data, bytes32(0), keccak256("s2"), 10 days); // not ready + bytes32 id3 = _schedule(address(123), 0, data, bytes32(0), keccak256("s3"), MIN_DELAY); // ready + _schedule(address(123), 0, data, bytes32(0), keccak256("s4"), 10 days); // not ready + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + // First call finds id1 + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, id1); + assertTrue(found); + + // Continue from after id1 (use getNextOperationId to get next starting point), finds id3 + bytes32 nextStart = timelock.getNextOperationId(id1); + (found, id) = timelockHelper.getNextExecutableOperationId(nextStart, type(uint256).max); + assertEq(id, id3); + assertTrue(found); + + // Continue from after id3, nothing ready left (list exhausted) + nextStart = timelock.getNextOperationId(id3); + (found, id) = timelockHelper.getNextExecutableOperationId(nextStart, type(uint256).max); + assertEq(id, bytes32(0)); + assertFalse(found); + } + + function testGetNextExecutableOperationSkipsPendingPredecessor() public { + bytes memory data1 = abi.encodeWithSignature("random(uint256)", 1); + bytes memory data2 = abi.encodeWithSignature("random(uint256)", 2); + bytes memory data3 = abi.encodeWithSignature("random(uint256)", 3); + + // Schedule id1 with long delay (won't be ready) + bytes32 id1 = _schedule(address(123), 0, data1, bytes32(0), keccak256("s1"), 10 days); + // Schedule id2 with id1 as predecessor (ready but predecessor not done) + _schedule(address(123), 0, data2, id1, keccak256("s2"), MIN_DELAY); + // Schedule id3 with no predecessor (ready) + bytes32 id3 = _schedule(address(123), 0, data3, bytes32(0), keccak256("s3"), MIN_DELAY); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + + // Should skip id1 (not ready), skip id2 (predecessor not done), return id3 + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, id3); + assertTrue(found); + } + + function testGetNextExecutableOperationWithDonePredecessor() public { + bytes memory data1 = abi.encodeWithSignature("random(uint256)", 1); + bytes memory data2 = abi.encodeWithSignature("random(uint256)", 2); + + // Schedule operation A (no predecessor) + bytes32 id1 = _schedule(address(123), 0, data1, bytes32(0), keccak256("s1"), MIN_DELAY); + + // Schedule operation B with A as predecessor + bytes32 id2 = _schedule(address(123), 0, data2, id1, keccak256("s2"), MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY); + + // Before executing predecessor: should return id1 (id2's predecessor not done) + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, id1); + assertTrue(found); + + // Execute id1 (predecessor is now done) + _execute(address(123), 0, data1, bytes32(0), keccak256("s1")); + + // Now id2's predecessor is done, should return id2 + (found, id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, id2); + assertTrue(found); + } + + function testGetNextExecutableOperationReturnValues() public { + bytes memory data = abi.encodeWithSignature("random(uint256)", 42); + + // Case 1: Empty list - id should be bytes32(0), found should be false + (bool found, bytes32 id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, bytes32(0), "Empty list: id should be 0"); + assertFalse(found, "Empty list: found should be false"); + + // Schedule 4 operations with long delays (none ready) + bytes32 id0 = _schedule(address(123), 0, data, bytes32(0), keccak256("s0"), 10 days); + bytes32 id1 = _schedule(address(123), 0, data, bytes32(0), keccak256("s1"), 10 days); + bytes32 id2 = _schedule(address(123), 0, data, bytes32(0), keccak256("s2"), 10 days); + bytes32 id3 = _schedule(address(123), 0, data, bytes32(0), keccak256("s3"), 10 days); + vm.stopPrank(); + + // Case 2: No operations ready, maxIterations reached - id should be next to check, found false + (found, id) = timelockHelper.getNextExecutableOperationId(bytes32(0), 2); + assertEq(id, id2, "Not ready, limit 2: id should be id2 (next to check)"); + assertFalse(found, "Not ready, limit 2: found should be false"); + + (found, id) = timelockHelper.getNextExecutableOperationId(bytes32(0), 3); + assertEq(id, id3, "Not ready, limit 3: id should be id3 (next to check)"); + assertFalse(found, "Not ready, limit 3: found should be false"); + + // Case 3: No operations ready, list exhausted - id should be bytes32(0), found false + (found, id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, bytes32(0), "Not ready, exhausted: id should be 0"); + assertFalse(found, "Not ready, exhausted: found should be false"); + + // Make operations ready + vm.warp(block.timestamp + 10 days); + + // Case 4: Ready operation found - id should be the ready operation, found true + (found, id) = timelockHelper.getNextExecutableOperationId(bytes32(0), type(uint256).max); + assertEq(id, id0, "Ready found: id should be id0"); + assertTrue(found, "Ready found: found should be true"); + + // Case 5: Ready operation found starting from id1 (startId is inclusive) + (found, id) = timelockHelper.getNextExecutableOperationId(id1, type(uint256).max); + assertEq(id, id1, "Ready from id1: id should be id1"); + assertTrue(found, "Ready from id1: found should be true"); + + // Case 6: Pagination - when not found, returned id is directly the next startId + // Cancel id0 and id1 so they won't be found, schedule new not-ready ops + timelock.cancel(id0); + timelock.cancel(id1); + // Schedule 2 new operations with very long delay (not ready) + bytes32 id4 = _schedule(address(123), 0, data, bytes32(0), keccak256("new0"), 100 days); + bytes32 id5 = _schedule(address(123), 0, data, bytes32(0), keccak256("new1"), 100 days); + vm.stopPrank(); + + // Now list is: id2 (ready), id3 (ready), id4 (not ready), id5 (not ready) + // Search with limit 1 starting from beginning - finds id2 immediately + (found, id) = timelockHelper.getNextExecutableOperationId(bytes32(0), 1); + assertEq(id, id2, "Pagination case 6a: id should be id2"); + assertTrue(found, "Pagination case 6a: found should be true"); + + // Search with limit 1 starting from id4 - checks id4 (not ready), returns id5 (next to check) + (found, id) = timelockHelper.getNextExecutableOperationId(id4, 1); + assertEq(id, timelock.getNextOperationId(id4), "Pagination case 6b: id should be next of id4"); + assertFalse(found, "Pagination case 6b: found should be false"); + + // Continue from id5 with limit 1 - checks id5 (not ready), id5 is last so returns 0 (exhausted) + (found, id) = timelockHelper.getNextExecutableOperationId(id5, 1); + assertEq(id, timelock.getNextOperationId(id5), "Pagination case 6c: id should be next of id5 (0)"); + assertFalse(found, "Pagination case 6c: found should be false"); + } +} diff --git a/test/timelock/TimelockWrapperForeign.t.sol b/test/timelock/TimelockWrapperForeign.t.sol index f08b8fb..250cc43 100644 --- a/test/timelock/TimelockWrapperForeign.t.sol +++ b/test/timelock/TimelockWrapperForeign.t.sol @@ -425,7 +425,7 @@ contract TimelockWrapperForeignTest is DssTest { vm.prank(coreCouncil); bytes32 id = wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); assertEq(id, expectedId); - assertEq(timelock.getOperationCount(), 1); + assertEq(timelock.getOperationsCount(), 1); vm.warp(block.timestamp + MIN_DELAY); timelock.executeBatch(targets, values, payloads, bytes32(0), salt); diff --git a/test/timelock/TimelockWrapperMainnet.t.sol b/test/timelock/TimelockWrapperMainnet.t.sol index e2acabf..40bae86 100644 --- a/test/timelock/TimelockWrapperMainnet.t.sol +++ b/test/timelock/TimelockWrapperMainnet.t.sol @@ -426,7 +426,7 @@ contract TimelockWrapperMainnetTest is DssTest { vm.prank(coreCouncil); bytes32 id = wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); assertEq(id, expectedId); - assertEq(timelock.getOperationCount(), 1); + assertEq(timelock.getOperationsCount(), 1); vm.warp(block.timestamp + MIN_DELAY); timelock.executeBatch(targets, values, payloads, bytes32(0), salt); From 358954b389eac8997e007ad1b2d84442436576ef Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:41:43 +0700 Subject: [PATCH 117/150] Bump Spark mainnet alm controller version to v1.9 --- src/timelock/TimelockWrapperMainnet.sol | 30 +++++++++++-- test/timelock/TimelockWrapperMainnet.t.sol | 52 ++++++++++++++++++---- 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/timelock/TimelockWrapperMainnet.sol b/src/timelock/TimelockWrapperMainnet.sol index 0727d31..028b3f6 100644 --- a/src/timelock/TimelockWrapperMainnet.sol +++ b/src/timelock/TimelockWrapperMainnet.sol @@ -47,17 +47,18 @@ interface BeamStateLike { } interface ControllerLike { - // Shared functions (Spark v1.8.0 & Grove v1.8.0) + // Shared functions (Spark v1.9.0 & Grove v1.8.0) function grantRole(bytes32 role, address account) external; function revokeRole(bytes32 role, address account) external; function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; function setLayerZeroRecipient(uint32 destinationEndpointId, bytes32 layerZeroRecipient) external; function setMaxSlippage(address pool, uint256 maxSlippage) external; function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; - // Spark-only functions (v1.8.0) + // Spark-only functions (v1.9.0) function setOTCBuffer(address exchange, address otcBuffer) external; function setOTCRechargeRate(address exchange, uint256 rechargeRate18) external; function setOTCWhitelistedAsset(address exchange, address asset, bool isWhitelisted) external; + function setUniswapV4TickLimits(bytes32 poolId, int24 tickLowerMin, int24 tickUpperMax, uint24 maxTickSpacing) external; // Grove-only functions (v1.8.0) function setUniswapV3PoolMaxTickDelta(address pool, uint24 maxTickDelta) external; function setUniswapV3AddLiquidityLowerTickBound(address pool, int24 lowerTickBound) external; @@ -73,7 +74,7 @@ struct RateLimitConfig { uint256 slope; } -// Spark Mainnet controller aligned to v1.8.0: https://github.com/sparkdotfi/spark-alm-controller/blob/7be959378fe48117f7a06796f94e240345428982/src/MainnetController.sol +// Spark Mainnet controller aligned to v1.9.0: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol // Grove Mainnet controller aligned to v1.8.0: https://github.com/grove-labs/grove-alm-controller/blob/2c6e3d4297d5f244894d05f3dbbe47bcada34712/src/MainnetController.sol // Notes: @@ -413,6 +414,29 @@ contract TimelockWrapperMainnet { emit ProposalSubmitted(operationId, "setOTCWhitelistedAsset"); } + function setUniswapV4TickLimits( + bytes32 poolId, + int24 tickLowerMin, + int24 tickUpperMax, + uint24 maxTickSpacing, + address controller, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external toll returns (bytes32 operationId) { + bytes memory controllerData = abi.encodeWithSelector( + ControllerLike.setUniswapV4TickLimits.selector, + poolId, + tickLowerMin, + tickUpperMax, + maxTickSpacing + ); + bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + + operationId = _submitProposal(payload, predecessor, salt, delay); + emit ProposalSubmitted(operationId, "setUniswapV4TickLimits"); + } + // Grove-only functions function setUniswapV3PoolMaxTickDelta( diff --git a/test/timelock/TimelockWrapperMainnet.t.sol b/test/timelock/TimelockWrapperMainnet.t.sol index 40bae86..eb8eb2e 100644 --- a/test/timelock/TimelockWrapperMainnet.t.sol +++ b/test/timelock/TimelockWrapperMainnet.t.sol @@ -39,6 +39,8 @@ interface ControllerLike { // otcs returns: (buffer, rechargeRate18, sent18, sentTimestamp, claimed18) function otcs(address) external view returns (address, uint256, uint256, uint256, uint256); function otcWhitelistedAssets(address, address) external view returns (bool); + // uniswapV4TickLimits returns: (tickLowerMin, tickUpperMax, maxTickSpacing) + function uniswapV4TickLimits(bytes32) external view returns (int24, int24, uint24); // Grove-specific getters function centrifugeRecipients(uint16) external view returns (bytes32); // uniswapV3PoolParams returns: (swapMaxTickDelta, addLiquidityLowerTick, addLiquidityUpperTick, twapSecondsAgo) @@ -59,8 +61,8 @@ contract TimelockWrapperMainnetTest is DssTest { event ProposalSubmitted(bytes32 indexed operationId, string functionName); // --- Mainnet Addresses --- - // https://github.com/sparkdotfi/spark-address-registry/blob/0a06d13c3e9d36428a6f916ac25528d82154517b/src/Ethereum.sol - address constant SPARK_CONTROLLER = 0xE52d643B27601D4d2BAB2052f30cf936ed413cec; + // https://github.com/sparkdotfi/spark-address-registry/blob/7ed39b0c31043bed076dc8734be65afab4cabec1/src/Ethereum.sol + address constant SPARK_CONTROLLER = 0xc9ff605003A1b389980f650e1aEFA1ef25C8eE32; address constant SPARK_PROXY = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; // https://github.com/grove-labs/grove-address-registry/blob/dd625925ab44e89eadce0cb4258e32aae2dfa73e/src/Ethereum.sol @@ -238,7 +240,7 @@ contract TimelockWrapperMainnetTest is DssTest { } function testTollModifiers() public { - bytes4[] memory tolledMethods = new bytes4[](22); + bytes4[] memory tolledMethods = new bytes4[](23); tolledMethods[0] = wrapper.start.selector; tolledMethods[1] = wrapper.setHop.selector; tolledMethods[2] = wrapper.setMaxChange.selector; @@ -255,12 +257,13 @@ contract TimelockWrapperMainnetTest is DssTest { tolledMethods[13] = wrapper.setOTCBuffer.selector; tolledMethods[14] = wrapper.setOTCRechargeRate.selector; tolledMethods[15] = wrapper.setOTCWhitelistedAsset.selector; - tolledMethods[16] = wrapper.setMaxExchangeRate.selector; - tolledMethods[17] = wrapper.setUniswapV3PoolMaxTickDelta.selector; - tolledMethods[18] = wrapper.setUniswapV3AddLiquidityLowerTickBound.selector; - tolledMethods[19] = wrapper.setUniswapV3AddLiquidityUpperTickBound.selector; - tolledMethods[20] = wrapper.setUniswapV3TwapSecondsAgo.selector; - tolledMethods[21] = wrapper.setCentrifugeRecipient.selector; + tolledMethods[16] = wrapper.setUniswapV4TickLimits.selector; + tolledMethods[17] = wrapper.setMaxExchangeRate.selector; + tolledMethods[18] = wrapper.setUniswapV3PoolMaxTickDelta.selector; + tolledMethods[19] = wrapper.setUniswapV3AddLiquidityLowerTickBound.selector; + tolledMethods[20] = wrapper.setUniswapV3AddLiquidityUpperTickBound.selector; + tolledMethods[21] = wrapper.setUniswapV3TwapSecondsAgo.selector; + tolledMethods[22] = wrapper.setCentrifugeRecipient.selector; vm.startPrank(address(0xBEEF)); checkModifier(address(wrapper), "TimelockWrapperMainnet/not-whitelisted", tolledMethods); @@ -735,6 +738,37 @@ contract TimelockWrapperMainnetTest is DssTest { assertTrue(ControllerLike(SPARK_CONTROLLER).otcWhitelistedAssets(exchange, usdc)); } + // --- setUniswapV4TickLimits (Spark Only) --- + + function testSetUniswapV4TickLimitsOnSparkController() public { + bytes32 poolId = keccak256("test-pool"); + int24 tickLowerMin = -887220; + int24 tickUpperMax = 887220; + uint24 maxTickSpacing = 200; + bytes32 salt = keccak256("v4tick"); + + bytes32 expectedId = _expectedOperationId( + abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV4TickLimits(bytes32,int24,int24,uint24)", poolId, tickLowerMin, tickUpperMax, maxTickSpacing), SPARK_CONTROLLER), + bytes32(0), + salt + ); + + vm.expectEmit(true, true, true, true, address(wrapper)); + emit ProposalSubmitted(expectedId, "setUniswapV4TickLimits"); + vm.prank(coreCouncil); + bytes32 id = wrapper.setUniswapV4TickLimits(poolId, tickLowerMin, tickUpperMax, maxTickSpacing, SPARK_CONTROLLER, bytes32(0), salt, MIN_DELAY); + assertEq(id, expectedId); + (bytes memory data,) = _getControllerAction(id); + _execute(id); + + vm.prank(cBeam); + configurator.callControllerAction(SPARK_CONTROLLER, data); + (int24 setTickLowerMin, int24 setTickUpperMax, uint24 setMaxTickSpacing) = ControllerLike(SPARK_CONTROLLER).uniswapV4TickLimits(poolId); + assertEq(setTickLowerMin, tickLowerMin); + assertEq(setTickUpperMax, tickUpperMax); + assertEq(setMaxTickSpacing, maxTickSpacing); + } + // --- setMaxExchangeRate (Spark & Grove) --- function _checkSetMaxExchangeRate(address controller, bytes32 salt) internal { From 296651a4336efe477f357fc253f13c7360e4876c Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:44:39 +0700 Subject: [PATCH 118/150] Update README to the relocation of getNextExecutableOperationId --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6601600..37cdd5a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The operational interface used by cBeams to modify rate limits and execute contr ### Timelock -Extended OpenZeppelin TimelockController with pausing support, permissionless execution, and operation tracking for keeper integration. Disables self-calls to prevent proposals from modifying admin settings. Provides helper functions (as `getNextExecutableOperationId`) for automated execution via keeper jobs. +Extended OpenZeppelin TimelockController with pausing support, permissionless execution, and operation tracking for keeper integration. Disables self-calls to prevent proposals from modifying admin settings. ### TimelockWrapperMainnet From 588d053f59c419d84ed99eb473fa264ed16a0b2a Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 5 Feb 2026 16:17:17 -0300 Subject: [PATCH 119/150] Timelock: minor changes --- src/timelock/helpers/TimelockHelper.sol | 8 +++----- test/timelock/TimelockHelper.t.sol | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/timelock/helpers/TimelockHelper.sol b/src/timelock/helpers/TimelockHelper.sol index 03b5cf1..8de9e9c 100644 --- a/src/timelock/helpers/TimelockHelper.sol +++ b/src/timelock/helpers/TimelockHelper.sol @@ -29,9 +29,7 @@ contract TimelockHelper { TimelockLike immutable public timelock; - constructor( - address timelock_ - ) { + constructor(address timelock_) { timelock = TimelockLike(timelock_); } @@ -40,8 +38,8 @@ contract TimelockHelper { // can be executed without cron keepers, or canceled in case they are jamming this mechanism. // returns - If found: the executable operation. If not found: the next startId to continue from, or bytes32(0) if exhausted. function getNextExecutableOperationId(bytes32 startId, uint256 maxIterations) external view returns (bool found, bytes32 id) { - require(maxIterations > 0, "Timelock/zero-maxIterations"); - require(startId == bytes32(0) || timelock.getOperationExists(startId), "Timelock/invalid-startId"); + require(maxIterations > 0, "TimelockHelper/zero-maxIterations"); + require(startId == bytes32(0) || timelock.getOperationExists(startId), "TimelockHelper/invalid-startId"); id = startId == bytes32(0) ? timelock.getFirstOperationId() : startId; diff --git a/test/timelock/TimelockHelper.t.sol b/test/timelock/TimelockHelper.t.sol index cfce5ed..618826f 100644 --- a/test/timelock/TimelockHelper.t.sol +++ b/test/timelock/TimelockHelper.t.sol @@ -173,7 +173,7 @@ contract TimelockHelperTest is Test { } function testGetNextExecutableOperationZeroMaxIterationsReverts() public { - vm.expectRevert("Timelock/zero-maxIterations"); + vm.expectRevert("TimelockHelper/zero-maxIterations"); timelockHelper.getNextExecutableOperationId(bytes32(0), 0); } @@ -182,7 +182,7 @@ contract TimelockHelperTest is Test { _schedule(address(123), 0, data, bytes32(0), SALT, MIN_DELAY); // Starting with a non-existent ID reverts - vm.expectRevert("Timelock/invalid-startId"); + vm.expectRevert("TimelockHelper/invalid-startId"); timelockHelper.getNextExecutableOperationId(keccak256("nonexistent"), type(uint256).max); } From 047c8a59774f3ad54877c51d501d73afd49e7d5f Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 6 Feb 2026 12:40:45 -0300 Subject: [PATCH 120/150] Linked list: return false instead of reverting when adding bytes32(0) item --- src/timelock/Bytes32LinkedList.sol | 4 +--- test/timelock/Bytes32LinkedList.t.sol | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/timelock/Bytes32LinkedList.sol b/src/timelock/Bytes32LinkedList.sol index b1aace0..d317adf 100644 --- a/src/timelock/Bytes32LinkedList.sol +++ b/src/timelock/Bytes32LinkedList.sol @@ -37,9 +37,7 @@ library Bytes32LinkedList { /// @param id The bytes32 value to add /// @return success True if the item was added, false if it already exists function add(List storage list, bytes32 id) internal returns (bool success) { - require(id != bytes32(0), "id-null"); - - if (list.exists[id]) { + if (id == bytes32(0) || list.exists[id]) { return false; } diff --git a/test/timelock/Bytes32LinkedList.t.sol b/test/timelock/Bytes32LinkedList.t.sol index f1c8647..fb0a8ec 100644 --- a/test/timelock/Bytes32LinkedList.t.sol +++ b/test/timelock/Bytes32LinkedList.t.sol @@ -142,9 +142,10 @@ contract Bytes32LinkedListTest is Test { assertEq(wrapper.count(), 1); } - function testAddingBytes32ZeroReverts() public { - vm.expectRevert("id-null"); - wrapper.add(bytes32(0)); + function testAddingBytes32ZeroIsNoOp() public { + assertFalse(wrapper.add(bytes32(0))); + + assertEq(wrapper.count(), 0); } // ============================================================================ From 53f05f440592e31c9ef663bcc3453818c630f2f5 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 6 Feb 2026 12:58:31 -0300 Subject: [PATCH 121/150] Fix comment --- src/timelock/Bytes32LinkedList.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timelock/Bytes32LinkedList.sol b/src/timelock/Bytes32LinkedList.sol index d317adf..fbb2382 100644 --- a/src/timelock/Bytes32LinkedList.sol +++ b/src/timelock/Bytes32LinkedList.sol @@ -35,7 +35,7 @@ library Bytes32LinkedList { /// @notice Add a new item to the end of the list /// @param list The list to add to /// @param id The bytes32 value to add - /// @return success True if the item was added, false if it already exists + /// @return success True if the item was added, false if it is bytes32(0) or already exists function add(List storage list, bytes32 id) internal returns (bool success) { if (id == bytes32(0) || list.exists[id]) { return false; From 98635195faf1184762f9837ced7ffded056d18b5 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 6 Feb 2026 15:09:13 -0300 Subject: [PATCH 122/150] State: Fix some minor inconsistencies --- src/BeamState.sol | 30 +++++++++++++++--------------- test/BeamState.t.sol | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 4f131c0..2f4e235 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -23,18 +23,19 @@ contract BeamState { // Note: Some of the variables defined here are for controlling the actions of the Configurator. // That's why they might not have a direct apparent reason within this contract itself. + // Note: allowed == 0 => false, allowed == 1 => true mapping(address usr => uint256 allowed) public wards; mapping(address usr => bytes32 rolesData) public userRoles; mapping(bytes4 sig => bytes32 rolesData) public actionsRoles; - mapping(address rateLimits_ => uint256 added) public rateLimits; // allowed == 0 => false, allowed == 1 => true - mapping(address controller => uint256 added) public controllers; // allowed == 0 => false, allowed == 1 => true - mapping(address cBeam => uint256 added) public cBeams; // allowed == 0 => false, allowed == 1 => true - mapping(address rateLimits_ => mapping(address cBeam => uint256 allowed)) public rateLimitsCBeams; // allowed == 0 => false, allowed == 1 => true - mapping(address controller => mapping(address cBeam => uint256 allowed)) public controllersCBeams; // allowed == 0 => false, allowed == 1 => true - mapping(bytes32 key => mapping(address rateLimits_ => DefaultRateLimits limit)) public initRateLimits; // rateLimits == address(0) every rateLimits allowed - mapping(bytes32 key => mapping(address controller => bool allowed)) public initControllerActions; // controller == address(0) every controller allowed - mapping(address rateLimits_ => uint256 value) public hop; // rateLimits == address(0) => general backup configuration - mapping(address rateLimits_ => uint256 value) public maxChange; // rateLimits == address(0) => general backup configuration + mapping(address rateLimits_ => uint256 allowed) public rateLimits; + mapping(address controller => uint256 allowed) public controllers; + mapping(address cBeam => uint256 allowed) public cBeams; + mapping(address rateLimits_ => mapping(address cBeam => uint256 allowed)) public rateLimitsCBeams; + mapping(address controller => mapping(address cBeam => uint256 allowed)) public controllersCBeams; + mapping(bytes32 key => mapping(address rateLimits_ => DefaultRateLimits limit)) public initRateLimits; // rateLimits == address(0) => general fallback configuration + mapping(bytes32 key => mapping(address controller => uint256 allowed)) public initControllerActions; // controller == address(0) => general fallback configuration + mapping(address rateLimits_ => uint256 value) public hop; // rateLimits == address(0) => general fallback configuration + mapping(address rateLimits_ => uint256 value) public maxChange; // rateLimits == address(0) => general fallback configuration bool public stopped; @@ -52,7 +53,7 @@ contract BeamState { event Rely(address indexed usr); event Deny(address indexed usr); event SetUserRole(address indexed who, uint8 indexed role, bool enabled); - event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); + event SetRoleAction(uint8 indexed role, bytes4 indexed sig, bool enabled); event Stop(); event Start(); event SetHop(address indexed rateLimits_, uint256 value); @@ -121,8 +122,8 @@ contract BeamState { } function isControllerActionEnabled(bytes32 key, address controller) external view returns (bool ok) { - ok = initControllerActions[key][address(0)] || // address(0) enabled for every controller - initControllerActions[key][controller]; + ok = initControllerActions[key][address(0)] == 1 || // address(0) enabled for every controller + initControllerActions[key][controller] == 1; } // --- Admin functions --- @@ -254,13 +255,12 @@ contract BeamState { } function addInitControllerActions(bytes calldata data, address controller) external roleAuth returns (bytes32 key) { - key = keccak256(data); - initControllerActions[key][controller] = true; + initControllerActions[key = keccak256(data)][controller] = 1; emit AddInitControllerActions(key, controller); } function delInitControllerActions(bytes32 key, address controller) external roleAuth { - delete initControllerActions[key][controller]; + initControllerActions[key][controller] = 0; emit DelInitControllerActions(key, controller); } } diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index c6862ca..8573b9e 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -31,7 +31,7 @@ contract BeamStateTest is DssTest { address constant TARGET2 = address(0xA2); event SetUserRole(address indexed who, uint8 indexed role, bool enabled); - event SetRoleAction(uint8 indexed role, bytes4 sig, bool enabled); + event SetRoleAction(uint8 indexed role, bytes4 indexed sig, bool enabled); event Stop(); event Start(); event SetHop(address indexed rateLimits, uint256 value); From 3142e72d590b645f08515e20dd5c6c0fce4b8f88 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:15:46 -0300 Subject: [PATCH 123/150] nit change Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- src/timelock/Bytes32LinkedList.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timelock/Bytes32LinkedList.sol b/src/timelock/Bytes32LinkedList.sol index fbb2382..0ec055b 100644 --- a/src/timelock/Bytes32LinkedList.sol +++ b/src/timelock/Bytes32LinkedList.sol @@ -17,7 +17,7 @@ pragma solidity ^0.8.24; /// @title Bytes32LinkedList -/// @notice A double linked list library for bytes32 values +/// @notice A double linked list library for non-zero bytes32 values library Bytes32LinkedList { struct Node { bytes32 prev; From 3ef03a174114cea255c0fd408b30718468c6582b Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 6 Feb 2026 22:16:24 -0300 Subject: [PATCH 124/150] Change order params of a couple of functions of wrappers --- src/timelock/TimelockWrapperForeign.sol | 4 ++-- src/timelock/TimelockWrapperMainnet.sol | 4 ++-- test/timelock/TimelockWrapperForeign.t.sol | 4 ++-- test/timelock/TimelockWrapperMainnet.t.sol | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/timelock/TimelockWrapperForeign.sol b/src/timelock/TimelockWrapperForeign.sol index 50c6a90..6ec4bc0 100644 --- a/src/timelock/TimelockWrapperForeign.sol +++ b/src/timelock/TimelockWrapperForeign.sol @@ -234,9 +234,9 @@ contract TimelockWrapperForeign { // Role bytes32 can be computed off-chain and passed as parameter, e.g keccak256("RELAYER"), keccak256("FREEZER") function grantRole( - address controller, bytes32 role, address account, + address controller, bytes32 predecessor, bytes32 salt, uint256 delay @@ -253,9 +253,9 @@ contract TimelockWrapperForeign { } function revokeRole( - address controller, bytes32 role, address account, + address controller, bytes32 predecessor, bytes32 salt, uint256 delay diff --git a/src/timelock/TimelockWrapperMainnet.sol b/src/timelock/TimelockWrapperMainnet.sol index 028b3f6..5873b07 100644 --- a/src/timelock/TimelockWrapperMainnet.sol +++ b/src/timelock/TimelockWrapperMainnet.sol @@ -238,9 +238,9 @@ contract TimelockWrapperMainnet { // Role bytes32 can be computed off-chain and passed as parameter, e.g keccak256("RELAYER"), keccak256("FREEZER") function grantRole( - address controller, bytes32 role, address account, + address controller, bytes32 predecessor, bytes32 salt, uint256 delay @@ -257,9 +257,9 @@ contract TimelockWrapperMainnet { } function revokeRole( - address controller, bytes32 role, address account, + address controller, bytes32 predecessor, bytes32 salt, uint256 delay diff --git a/test/timelock/TimelockWrapperForeign.t.sol b/test/timelock/TimelockWrapperForeign.t.sol index 250cc43..e5fc986 100644 --- a/test/timelock/TimelockWrapperForeign.t.sol +++ b/test/timelock/TimelockWrapperForeign.t.sol @@ -479,7 +479,7 @@ contract TimelockWrapperForeignTest is DssTest { vm.expectEmit(true, true, true, true, address(wrapper)); emit ProposalSubmitted(expectedId, "grantRole"); vm.prank(coreCouncil); - bytes32 id = wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + bytes32 id = wrapper.grantRole(role, account, controller, bytes32(0), salt, MIN_DELAY); assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); @@ -515,7 +515,7 @@ contract TimelockWrapperForeignTest is DssTest { vm.expectEmit(true, true, true, true, address(wrapper)); emit ProposalSubmitted(expectedId, "revokeRole"); vm.prank(coreCouncil); - bytes32 id = wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + bytes32 id = wrapper.revokeRole(role, account, controller, bytes32(0), salt, MIN_DELAY); assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); diff --git a/test/timelock/TimelockWrapperMainnet.t.sol b/test/timelock/TimelockWrapperMainnet.t.sol index eb8eb2e..65232fc 100644 --- a/test/timelock/TimelockWrapperMainnet.t.sol +++ b/test/timelock/TimelockWrapperMainnet.t.sol @@ -483,7 +483,7 @@ contract TimelockWrapperMainnetTest is DssTest { vm.expectEmit(true, true, true, true, address(wrapper)); emit ProposalSubmitted(expectedId, "grantRole"); vm.prank(coreCouncil); - bytes32 id = wrapper.grantRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + bytes32 id = wrapper.grantRole(role, account, controller, bytes32(0), salt, MIN_DELAY); assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); @@ -519,7 +519,7 @@ contract TimelockWrapperMainnetTest is DssTest { vm.expectEmit(true, true, true, true, address(wrapper)); emit ProposalSubmitted(expectedId, "revokeRole"); vm.prank(coreCouncil); - bytes32 id = wrapper.revokeRole(controller, role, account, bytes32(0), salt, MIN_DELAY); + bytes32 id = wrapper.revokeRole(role, account, controller, bytes32(0), salt, MIN_DELAY); assertEq(id, expectedId); (bytes memory data,) = _getControllerAction(id); _execute(id); From 581a448aac4a2a8b90da4e19aeb6b886052f3682 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Sat, 7 Feb 2026 15:55:43 +0700 Subject: [PATCH 125/150] Add SECURITY.md (#6) * Add SECURITY.md * Minor changes * Fix typo * Note about moms * Apply suggestion from @telome Co-authored-by: telome <130504305+telome@users.noreply.github.com> * Apply suggestion from @telome Co-authored-by: telome <130504305+telome@users.noreply.github.com> * Add suggested changes to SECURITY.md * Minor change --------- Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- SECURITY.md | 22 ++++++++++++++++++++++ src/BeamState.sol | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ec42ddd --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,22 @@ +# Notes and Trust Assumptions + +- The following entities are trusted: + - **Timelock**: Fully trusted. + - **Core Council through direct actions**: Very highly trusted. + - **cBEAMs**: Mostly trusted (a trusted team's multisig, malicious activity is assumed to be of very low likelihood). + +- It is assumed proposers or cancelers would not cancel proposals maliciously (including Core Council). +- It is assumed that before adding calldata, rate limits, or whitelisting targets, security related scenarios are thoroughly considered. +- As part of the above considerations, possible misalignment of cBEAMs is expected to be taken into consideration (although unlikely). For example: + 1. Omission - not executing a call when expected + 2. Double execution - executing the same call twice + 3. Stale execution - executing an old call after a newer one superseded it + 4. Reordering - executing calls in the wrong order +- It is also expected to be considered whether cBEAMs can block withdrawals in case of emergencies (for example, when needed, infinity rate limit would be configured). +- It is assumed the Core Council would not block withdrawals or emergency mechanisms on purpose. +- It is expected to be considered whether a default rate limits / calldata can be applied simultaneously on many controllers, thus amplifying potential harm. For example, the damage of depositing into a new vault without applying slippage protection might be amplified if it can be done for all whitelisted controllers. In general it is assumed that as the system scales more protections are added for such scenarios. +- It is assumed that cBEAMs and Core Council are synced in their operations, including timing their actions against Timelock executions. +- It is assumed the cancelers and Core Council are available on short notice to remove proposals and configurations if needed. This includes removing old calldata/rate-limits in a timely manner. +- It is assumed pausers would not temporarily DoS the timelock on purpose. +- As with other Sky contracts, a mom mechanism is not expected to be deployed on remote chains. It is expected to be sufficiently replaced with highly secured and trusted multisigs. + diff --git a/src/BeamState.sol b/src/BeamState.sol index 2f4e235..1eea73d 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -218,7 +218,7 @@ contract BeamState { } // Note: Once cBEAMS are whitelisted they can be assigned to controllers/rate-limits (potentially without delay), - // so theoretically they can be positioned to interfere with each other. This is known and assumed to be monitored + // so theoretically they can be positioned to interfere with each other. This is known and assumed to be monitored. function setCBeamForRateLimits(address rateLimits_, address cBeam) external roleAuth { require(rateLimits[rateLimits_] == 1, "BeamState/not-existing-rateLimits"); From 1ee943179963731d70392b905cf03363e8926074 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:28:37 +0700 Subject: [PATCH 126/150] Add defensive requires on linked list calls --- src/timelock/Timelock.sol | 6 +++--- test/timelock/Timelock.t.sol | 31 ++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index 09ce125..a75cd88 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -103,7 +103,7 @@ contract Timelock is TimelockController, Pausable { // Track operation for keeper jobs bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); - _operationIds.add(id); + require(_operationIds.add(id), "Timelock/add-failed"); _operations[id] = Operation(targets, values, payloads, predecessor, salt); } @@ -111,7 +111,7 @@ contract Timelock is TimelockController, Pausable { // If needed, the admin can atomically cancel any proposal right after unpausing. function cancel(bytes32 id) public virtual override whenNotPaused { super.cancel(id); - _operationIds.remove(id); + require(_operationIds.remove(id), "Timelock/remove-failed"); delete _operations[id]; } @@ -132,7 +132,7 @@ contract Timelock is TimelockController, Pausable { ) public payable virtual override whenNotPaused { super.executeBatch(targets, values, payloads, predecessor, salt); bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); - _operationIds.remove(id); + require(_operationIds.remove(id), "Timelock/remove-failed"); delete _operations[id]; } diff --git a/test/timelock/Timelock.t.sol b/test/timelock/Timelock.t.sol index 8b2882f..7f5b9c1 100644 --- a/test/timelock/Timelock.t.sol +++ b/test/timelock/Timelock.t.sol @@ -16,7 +16,7 @@ pragma solidity ^0.8.24; -import { Test } from "forge-std/Test.sol"; +import { Test, stdStorage, StdStorage } from "forge-std/Test.sol"; import { Timelock } from "src/timelock/Timelock.sol"; import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol"; import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; @@ -46,6 +46,8 @@ contract MockTarget { } contract TimelockTest is Test { + using stdStorage for StdStorage; + Timelock public timelock; MockTarget public mockTarget; MockTarget public mockTarget2; @@ -1045,6 +1047,33 @@ contract TimelockTest is Test { assertEq(op.salt, salt); } + function testLinkedListRequiresOnCorruptedState() public { + bytes memory data = abi.encodeWithSelector(MockTarget.setValue.selector, 42); + + // Test add-failed: corrupt exists[id] to true before scheduling + bytes32 id1 = _hashOperation(address(mockTarget), 0, data, bytes32(0), keccak256("s1")); + stdstore.target(address(timelock)).sig("getOperationExists(bytes32)").with_key(id1).checked_write(true); + vm.prank(proposer); + vm.expectRevert("Timelock/add-failed"); + _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s1"), MIN_DELAY); + + // Test remove-failed on cancel: corrupt exists[id] to false after scheduling + vm.prank(proposer); + bytes32 id2 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s2"), MIN_DELAY); + stdstore.target(address(timelock)).sig("getOperationExists(bytes32)").with_key(id2).checked_write(false); + vm.prank(proposer); + vm.expectRevert("Timelock/remove-failed"); + timelock.cancel(id2); + + // Test remove-failed on execute: corrupt exists[id] to false after scheduling + vm.prank(proposer); + bytes32 id3 = _schedule(address(mockTarget), 0, data, bytes32(0), keccak256("s3"), MIN_DELAY); + vm.warp(block.timestamp + MIN_DELAY); + stdstore.target(address(timelock)).sig("getOperationExists(bytes32)").with_key(id3).checked_write(false); + vm.expectRevert("Timelock/remove-failed"); + _execute(address(mockTarget), 0, data, bytes32(0), keccak256("s3")); + } + function testIndividualGettersMatchCombinedGetter() public { // Create a batch operation with multiple calls address[] memory targets = new address[](3); From 652f3694463d65d761e90e959a44885d7c9dbf04 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:34:47 +0700 Subject: [PATCH 127/150] Minor import path change --- test/BeamState.t.sol | 2 +- test/Configurator.t.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 8573b9e..9dc6cba 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -17,7 +17,7 @@ pragma solidity ^0.8.24; import "dss-test/DssTest.sol"; -import { BeamState } from "../src/BeamState.sol"; +import { BeamState } from "src/BeamState.sol"; contract BeamStateTest is DssTest { diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 9afe934..f0ee12c 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -17,8 +17,8 @@ pragma solidity ^0.8.24; import "dss-test/DssTest.sol"; -import { Configurator, BeamStateLike, RateLimitsLike } from "../src/Configurator.sol"; -import { BeamState } from "../src/BeamState.sol"; +import { Configurator, BeamStateLike, RateLimitsLike } from "src/Configurator.sol"; +import { BeamState } from "src/BeamState.sol"; // Mock TARGET contract implementing RateLimits interface contract MockTarget { From 43326ee1b7602922f2268b26900639d66874f99a Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:41:46 +0700 Subject: [PATCH 128/150] Remove comment about cancelers --- src/timelock/Timelock.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/timelock/Timelock.sol b/src/timelock/Timelock.sol index a75cd88..540c9cc 100644 --- a/src/timelock/Timelock.sol +++ b/src/timelock/Timelock.sol @@ -45,7 +45,6 @@ contract Timelock is TimelockController, Pausable { // - Do not allow proposals to change the min delay // Notes: - // - By default all proposers can also cancel any proposal, this should be taken into account to make sure that they are trusted and that specific cancellations do not cause big harm. // - Cancellers can cancel any proposal, not only ones they created. Same assumptions as above apply. constructor( From 899394929ef4921dffca010bee9f83d4ee89bc72 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:45:04 +0700 Subject: [PATCH 129/150] Update src/timelock/Bytes32LinkedList.sol Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- src/timelock/Bytes32LinkedList.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timelock/Bytes32LinkedList.sol b/src/timelock/Bytes32LinkedList.sol index 0ec055b..58c5179 100644 --- a/src/timelock/Bytes32LinkedList.sol +++ b/src/timelock/Bytes32LinkedList.sol @@ -17,7 +17,7 @@ pragma solidity ^0.8.24; /// @title Bytes32LinkedList -/// @notice A double linked list library for non-zero bytes32 values +/// @notice A doubly linked list library for non-zero bytes32 values library Bytes32LinkedList { struct Node { bytes32 prev; From e95eda40782e64d1ec93abf884e3cb79b336dce8 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Sun, 8 Feb 2026 10:27:41 -0300 Subject: [PATCH 130/150] Remove unused constant in PASInit --- deploy/PASInit.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index a34e335..9e59d3b 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -68,7 +68,6 @@ interface PASMomLike { } library PASInit { - uint256 constant internal WAD = 10**18; // It is noted that delayed and immediate operations order is non deterministic. // The relevant operators of each role are assumed to communicate and track timelock and configurator executions. From 4fab3aacacb0cb04302f57b14ac67cdd0e4cf9ac Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Sun, 8 Feb 2026 10:35:17 -0300 Subject: [PATCH 131/150] Remove some misleading comments --- test/Configurator.t.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index f0ee12c..09b7903 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -850,7 +850,6 @@ contract ConfiguratorTest is DssTest { vm.warp(block.timestamp + 86_400); // Any increase requires hop - // Stop the BeamState beamState.stop(); // Try to set rate limit - should revert @@ -858,7 +857,6 @@ contract ConfiguratorTest is DssTest { vm.expectRevert("Configurator/stopped"); configurator.setRateLimit(address(target1), key, 600 * WAD, 6 * WAD); - // Restart the BeamState beamState.start(); // Now it should work @@ -875,7 +873,6 @@ contract ConfiguratorTest is DssTest { bytes memory data = abi.encodeWithSignature("controllerFunction(uint256)", 123); beamState.addInitControllerActions(data, address(0)); // Global whitelist - // Stop the BeamState beamState.stop(); // Try to call controller action - should revert @@ -883,7 +880,6 @@ contract ConfiguratorTest is DssTest { vm.expectRevert("Configurator/stopped"); configurator.callControllerAction(address(target1), data); - // Restart the BeamState beamState.start(); // Now it should work From 207ef9798dea328297243db474f82a266884020f Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 13 Feb 2026 17:57:41 -0300 Subject: [PATCH 132/150] Wrappers: use abi.encodeCall --- src/timelock/TimelockWrapperForeign.sol | 138 ++++++++----------- src/timelock/TimelockWrapperMainnet.sol | 173 ++++++++++-------------- 2 files changed, 134 insertions(+), 177 deletions(-) diff --git a/src/timelock/TimelockWrapperForeign.sol b/src/timelock/TimelockWrapperForeign.sol index 6ec4bc0..d5b96b9 100644 --- a/src/timelock/TimelockWrapperForeign.sol +++ b/src/timelock/TimelockWrapperForeign.sol @@ -146,37 +146,37 @@ contract TimelockWrapperForeign { } function start(bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.start.selector); + bytes memory payload = abi.encodeCall(BeamStateLike.start, ()); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "start"); } function setHop(address rateLimits, uint256 hop, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.setHop.selector, rateLimits, hop); + bytes memory payload = abi.encodeCall(BeamStateLike.setHop, (rateLimits, hop)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setHop"); } function setMaxChange(address rateLimits, uint256 maxChange, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.setMaxChange.selector, rateLimits, maxChange); + bytes memory payload = abi.encodeCall(BeamStateLike.setMaxChange, (rateLimits, maxChange)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxChange"); } function addRateLimits(address rateLimits, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addRateLimits.selector, rateLimits); + bytes memory payload = abi.encodeCall(BeamStateLike.addRateLimits, (rateLimits)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "addRateLimits"); } function addController(address controller, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addController.selector, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addController, (controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "addController"); } function addCBeam(address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addCBeam.selector, cBeam); + bytes memory payload = abi.encodeCall(BeamStateLike.addCBeam, (cBeam)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "addCBeam"); } @@ -187,12 +187,9 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector( - BeamStateLike.addInitRateLimits.selector, - config.key, - config.rateLimits, - config.maxAmount, - config.slope + bytes memory payload = abi.encodeCall( + BeamStateLike.addInitRateLimits, + (config.key, config.rateLimits, config.maxAmount, config.slope) ); operationId = _submitProposal(payload, predecessor, salt, delay); @@ -213,12 +210,9 @@ contract TimelockWrapperForeign { for (uint256 i = 0; i < len; ++i) { targets[i] = address(beamState); - payloads[i] = abi.encodeWithSelector( - BeamStateLike.addInitRateLimits.selector, - configs[i].key, - configs[i].rateLimits, - configs[i].maxAmount, - configs[i].slope + payloads[i] = abi.encodeCall( + BeamStateLike.addInitRateLimits, + (configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope) ); } @@ -241,12 +235,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.grantRole.selector, - role, - account + bytes memory controllerData = abi.encodeCall( + ControllerLike.grantRole, + (role, account) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "grantRole"); @@ -260,12 +253,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.revokeRole.selector, - role, - account + bytes memory controllerData = abi.encodeCall( + ControllerLike.revokeRole, + (role, account) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "revokeRole"); @@ -279,12 +271,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setMintRecipient.selector, - destinationDomain, - mintRecipient + bytes memory controllerData = abi.encodeCall( + ControllerLike.setMintRecipient, + (destinationDomain, mintRecipient) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMintRecipient"); @@ -298,12 +289,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setLayerZeroRecipient.selector, - destinationEndpointId, - layerZeroRecipient + bytes memory controllerData = abi.encodeCall( + ControllerLike.setLayerZeroRecipient, + (destinationEndpointId, layerZeroRecipient) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setLayerZeroRecipient"); @@ -317,12 +307,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setMaxSlippage.selector, - pool, - maxSlippage + bytes memory controllerData = abi.encodeCall( + ControllerLike.setMaxSlippage, + (pool, maxSlippage) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxSlippage"); @@ -337,13 +326,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setMaxExchangeRate.selector, - token, - shares, - maxExpectedAssets + bytes memory controllerData = abi.encodeCall( + ControllerLike.setMaxExchangeRate, + (token, shares, maxExpectedAssets) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxExchangeRate"); @@ -359,12 +346,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3PoolMaxTickDelta.selector, - pool, - maxTickDelta + bytes memory controllerData = abi.encodeCall( + ControllerLike.setUniswapV3PoolMaxTickDelta, + (pool, maxTickDelta) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3PoolMaxTickDelta"); @@ -378,12 +364,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3AddLiquidityLowerTickBound.selector, - pool, - lowerTickBound + bytes memory controllerData = abi.encodeCall( + ControllerLike.setUniswapV3AddLiquidityLowerTickBound, + (pool, lowerTickBound) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityLowerTickBound"); @@ -397,12 +382,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3AddLiquidityUpperTickBound.selector, - pool, - upperTickBound + bytes memory controllerData = abi.encodeCall( + ControllerLike.setUniswapV3AddLiquidityUpperTickBound, + (pool, upperTickBound) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityUpperTickBound"); @@ -416,12 +400,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3TwapSecondsAgo.selector, - pool, - twapSecondsAgo + bytes memory controllerData = abi.encodeCall( + ControllerLike.setUniswapV3TwapSecondsAgo, + (pool, twapSecondsAgo) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3TwapSecondsAgo"); @@ -435,12 +418,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setCentrifugeRecipient.selector, - centrifugeId, - recipient + bytes memory controllerData = abi.encodeCall( + ControllerLike.setCentrifugeRecipient, + (centrifugeId, recipient) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setCentrifugeRecipient"); @@ -453,11 +435,11 @@ contract TimelockWrapperForeign { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setMerklDistributor.selector, - merklDistributor + bytes memory controllerData = abi.encodeCall( + ControllerLike.setMerklDistributor, + (merklDistributor) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMerklDistributor"); diff --git a/src/timelock/TimelockWrapperMainnet.sol b/src/timelock/TimelockWrapperMainnet.sol index 5873b07..460fcfa 100644 --- a/src/timelock/TimelockWrapperMainnet.sol +++ b/src/timelock/TimelockWrapperMainnet.sol @@ -150,37 +150,37 @@ contract TimelockWrapperMainnet { } function start(bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.start.selector); + bytes memory payload = abi.encodeCall(BeamStateLike.start, ()); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "start"); } function setHop(address rateLimits, uint256 hop, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.setHop.selector, rateLimits, hop); + bytes memory payload = abi.encodeCall(BeamStateLike.setHop, (rateLimits, hop)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setHop"); } function setMaxChange(address rateLimits, uint256 maxChange, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.setMaxChange.selector, rateLimits, maxChange); + bytes memory payload = abi.encodeCall(BeamStateLike.setMaxChange, (rateLimits, maxChange)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxChange"); } function addRateLimits(address rateLimits, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addRateLimits.selector, rateLimits); + bytes memory payload = abi.encodeCall(BeamStateLike.addRateLimits, (rateLimits)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "addRateLimits"); } function addController(address controller, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addController.selector, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addController, (controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "addController"); } function addCBeam(address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addCBeam.selector, cBeam); + bytes memory payload = abi.encodeCall(BeamStateLike.addCBeam, (cBeam)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "addCBeam"); } @@ -191,12 +191,9 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeWithSelector( - BeamStateLike.addInitRateLimits.selector, - config.key, - config.rateLimits, - config.maxAmount, - config.slope + bytes memory payload = abi.encodeCall( + BeamStateLike.addInitRateLimits, + (config.key, config.rateLimits, config.maxAmount, config.slope) ); operationId = _submitProposal(payload, predecessor, salt, delay); @@ -217,12 +214,9 @@ contract TimelockWrapperMainnet { for (uint256 i = 0; i < len; ++i) { targets[i] = address(beamState); - payloads[i] = abi.encodeWithSelector( - BeamStateLike.addInitRateLimits.selector, - configs[i].key, - configs[i].rateLimits, - configs[i].maxAmount, - configs[i].slope + payloads[i] = abi.encodeCall( + BeamStateLike.addInitRateLimits, + (configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope) ); } @@ -245,12 +239,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.grantRole.selector, - role, - account + bytes memory controllerData = abi.encodeCall( + ControllerLike.grantRole, + (role, account) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "grantRole"); @@ -264,12 +257,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.revokeRole.selector, - role, - account + bytes memory controllerData = abi.encodeCall( + ControllerLike.revokeRole, + (role, account) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "revokeRole"); @@ -283,12 +275,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setMintRecipient.selector, - destinationDomain, - mintRecipient + bytes memory controllerData = abi.encodeCall( + ControllerLike.setMintRecipient, + (destinationDomain, mintRecipient) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMintRecipient"); @@ -302,12 +293,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setLayerZeroRecipient.selector, - destinationEndpointId, - layerZeroRecipient + bytes memory controllerData = abi.encodeCall( + ControllerLike.setLayerZeroRecipient, + (destinationEndpointId, layerZeroRecipient) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setLayerZeroRecipient"); @@ -321,12 +311,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setMaxSlippage.selector, - pool, - maxSlippage + bytes memory controllerData = abi.encodeCall( + ControllerLike.setMaxSlippage, + (pool, maxSlippage) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxSlippage"); @@ -341,13 +330,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setMaxExchangeRate.selector, - token, - shares, - maxExpectedAssets + bytes memory controllerData = abi.encodeCall( + ControllerLike.setMaxExchangeRate, + (token, shares, maxExpectedAssets) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setMaxExchangeRate"); @@ -363,17 +350,16 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setOTCBuffer.selector, - exchange, - otcBuffer + bytes memory controllerData = abi.encodeCall( + ControllerLike.setOTCBuffer, + (exchange, otcBuffer) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCBuffer"); } - + function setOTCRechargeRate( address exchange, uint256 rechargeRate18, @@ -382,17 +368,16 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setOTCRechargeRate.selector, - exchange, - rechargeRate18 + bytes memory controllerData = abi.encodeCall( + ControllerLike.setOTCRechargeRate, + (exchange, rechargeRate18) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCRechargeRate"); } - + function setOTCWhitelistedAsset( address exchange, address asset, @@ -402,13 +387,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setOTCWhitelistedAsset.selector, - exchange, - asset, - isWhitelisted + bytes memory controllerData = abi.encodeCall( + ControllerLike.setOTCWhitelistedAsset, + (exchange, asset, isWhitelisted) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setOTCWhitelistedAsset"); @@ -424,14 +407,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV4TickLimits.selector, - poolId, - tickLowerMin, - tickUpperMax, - maxTickSpacing + bytes memory controllerData = abi.encodeCall( + ControllerLike.setUniswapV4TickLimits, + (poolId, tickLowerMin, tickUpperMax, maxTickSpacing) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV4TickLimits"); @@ -447,12 +427,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3PoolMaxTickDelta.selector, - pool, - maxTickDelta + bytes memory controllerData = abi.encodeCall( + ControllerLike.setUniswapV3PoolMaxTickDelta, + (pool, maxTickDelta) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3PoolMaxTickDelta"); @@ -466,12 +445,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3AddLiquidityLowerTickBound.selector, - pool, - lowerTickBound + bytes memory controllerData = abi.encodeCall( + ControllerLike.setUniswapV3AddLiquidityLowerTickBound, + (pool, lowerTickBound) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityLowerTickBound"); @@ -485,12 +463,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3AddLiquidityUpperTickBound.selector, - pool, - upperTickBound + bytes memory controllerData = abi.encodeCall( + ControllerLike.setUniswapV3AddLiquidityUpperTickBound, + (pool, upperTickBound) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityUpperTickBound"); @@ -504,12 +481,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setUniswapV3TwapSecondsAgo.selector, - pool, - twapSecondsAgo + bytes memory controllerData = abi.encodeCall( + ControllerLike.setUniswapV3TwapSecondsAgo, + (pool, twapSecondsAgo) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setUniswapV3TwapSecondsAgo"); @@ -523,12 +499,11 @@ contract TimelockWrapperMainnet { bytes32 salt, uint256 delay ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeWithSelector( - ControllerLike.setCentrifugeRecipient.selector, - centrifugeId, - recipient + bytes memory controllerData = abi.encodeCall( + ControllerLike.setCentrifugeRecipient, + (centrifugeId, recipient) ); - bytes memory payload = abi.encodeWithSelector(BeamStateLike.addInitControllerActions.selector, controllerData, controller); + bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); operationId = _submitProposal(payload, predecessor, salt, delay); emit ProposalSubmitted(operationId, "setCentrifugeRecipient"); From bd5505f7e65b802b797d07928119579884e7ebd2 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 13 Feb 2026 18:20:17 -0300 Subject: [PATCH 133/150] Allow to decrease from unlimited if the state is updated --- src/Configurator.sol | 17 +++++++++++------ test/Configurator.t.sol | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/Configurator.sol b/src/Configurator.sol index 0b30e7d..c28ccf7 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -84,10 +84,6 @@ contract Configurator { // --- Internal functions --- - function _max(uint256 x, uint256 y) internal pure returns (uint256 z) { - z = x > y ? x : y; - } - function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { z = x < y ? x : y; } @@ -105,8 +101,17 @@ contract Configurator { uint256 maxChange = beamState.getMaxChange(rateLimits); // Ceiling is the max of (current * maxChange) and default - require(maxAmount <= _max(current.maxAmount * maxChange / WAD, defMaxAmount), "Configurator/exceeds-max-amount"); - require(slope <= _max(current.slope * maxChange / WAD, defSlope), "Configurator/exceeds-max-slope"); + require( + maxAmount <= defMaxAmount || + maxAmount <= current.maxAmount || // avoid overflow when current maxAmount is type(uint256).max + maxAmount <= current.maxAmount * maxChange / WAD, + "Configurator/exceeds-max-amount" + ); + require( + slope <= defSlope || + slope <= current.slope * maxChange / WAD, + "Configurator/exceeds-max-slope" + ); // Any increase requires hop if (maxAmount > current.maxAmount || slope > current.slope) { diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 09b7903..ff17f5e 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -387,6 +387,24 @@ contract ConfiguratorTest is DssTest { configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); } + // --- Overflow Protection Tests --- + + function testSetRateLimitNoOverflowWhenCurrentMaxAmountIsMax() public { + bytes32 key = keccak256("overflow-key"); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + // Current maxAmount is type(uint256).max — would overflow in `current.maxAmount * maxChange / WAD` + _setupRateLimitData(target1, key, type(uint256).max, 10 * WAD, type(uint256).max, block.timestamp); + + // Decrease to within defaults — should not overflow + vm.prank(CBEAM1); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); + + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); + assertEq(data.maxAmount, 1_000 * WAD, "maxAmount should decrease from type(uint256).max"); + assertEq(data.slope, 10 * WAD, "slope should be set correctly"); + } + // --- LastAmount Capping Tests --- function testLastAmountCappedAtMaxAmount() public { From 610ba4ed7ecb924184c30d8319b211571b0b3523 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Sun, 15 Feb 2026 09:07:04 +0700 Subject: [PATCH 134/150] Comments about checking in general if no specific setting --- src/BeamState.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/BeamState.sol b/src/BeamState.sol index 1eea73d..f7884c8 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -106,10 +106,12 @@ contract BeamState { } function getHop(address rateLimits_) external view returns (uint256 hop_) { + // If not set for specific rateLimits, check in general hop_ = hop[rateLimits_]; hop_ = hop_ != 0 ? hop_ : hop[address(0)]; } function getMaxChange(address rateLimits_) external view returns (uint256 maxChange_) { + // If not set for specific rateLimits, check in general maxChange_ = maxChange[rateLimits_]; maxChange_ = maxChange_ != 0 ? maxChange_ : maxChange[address(0)]; } From 26f064560024b0e8878c391069c72467d22a7425 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:53:17 +0700 Subject: [PATCH 135/150] Add comment about enforcing maximal rate limits --- src/Configurator.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Configurator.sol b/src/Configurator.sol index c28ccf7..1734703 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -93,6 +93,7 @@ contract Configurator { function setRateLimit(address rateLimits, bytes32 key, uint256 maxAmount, uint256 slope) external notStopped authRateLimits(rateLimits) { (uint256 defMaxAmount, uint256 defSlope) = beamState.getInitRateLimits(key, rateLimits); if (defMaxAmount == type(uint256).max && defSlope == 0) { + // If maximal rate limits are defined in beamState the cBEAM has to use those require(maxAmount == type(uint256).max && slope == 0, "Configurator/unlimited-incorrect-params"); RateLimitsLike(rateLimits).setUnlimitedRateLimitData(key); emit SetRateLimit(rateLimits, key, type(uint256).max, 0); From 2af76dda4d09bfb6ebf8a2344624d5e0a8d14da1 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:19:39 -0300 Subject: [PATCH 136/150] Fix in test Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- test/Configurator.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index ff17f5e..20fe286 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -392,11 +392,11 @@ contract ConfiguratorTest is DssTest { function testSetRateLimitNoOverflowWhenCurrentMaxAmountIsMax() public { bytes32 key = keccak256("overflow-key"); _setupCBeam(address(target1), CBEAM1); - _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 10 * WAD); + _setupDefaultRateLimits(key, address(target1), 500 * WAD, 10 * WAD); // Current maxAmount is type(uint256).max — would overflow in `current.maxAmount * maxChange / WAD` _setupRateLimitData(target1, key, type(uint256).max, 10 * WAD, type(uint256).max, block.timestamp); - // Decrease to within defaults — should not overflow + // Decrease to finite value above defaults — should not overflow vm.prank(CBEAM1); configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); From 8ea679abf5ba48ab101f0464b4f56f60ab8b70c3 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 16 Feb 2026 10:26:44 -0300 Subject: [PATCH 137/150] Add also overflow protection for slope --- src/Configurator.sol | 1 + test/Configurator.t.sol | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Configurator.sol b/src/Configurator.sol index 1734703..6ee37e5 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -110,6 +110,7 @@ contract Configurator { ); require( slope <= defSlope || + slope <= current.slope || // avoid overflow for an hypothetical case where current slope is type(uint256).max slope <= current.slope * maxChange / WAD, "Configurator/exceeds-max-slope" ); diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index 20fe286..f87d0f1 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -405,6 +405,22 @@ contract ConfiguratorTest is DssTest { assertEq(data.slope, 10 * WAD, "slope should be set correctly"); } + function testSetRateLimitNoOverflowWhenCurrentSlopeIsMax() public { + bytes32 key = keccak256("overflow-slope-key"); + _setupCBeam(address(target1), CBEAM1); + _setupDefaultRateLimits(key, address(target1), 1_000 * WAD, 500 * WAD); + // Current slope is type(uint256).max — would overflow in `current.slope * maxChange / WAD` + _setupRateLimitData(target1, key, 1_000 * WAD, type(uint256).max, 1_000 * WAD, block.timestamp); + + // Decrease slope to finite value above defaults — should not overflow + vm.prank(CBEAM1); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 1_000 * WAD); + + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); + assertEq(data.maxAmount, 1_000 * WAD, "maxAmount should be set correctly"); + assertEq(data.slope, 1_000 * WAD, "slope should decrease from type(uint256).max"); + } + // --- LastAmount Capping Tests --- function testLastAmountCappedAtMaxAmount() public { From 94be5f06dd3be38e950d5d74b6a789017a8cfa82 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 16 Feb 2026 15:04:42 -0300 Subject: [PATCH 138/150] Minor changes --- deploy/PASInit.sol | 2 ++ src/BeamState.sol | 2 +- test/BeamState.t.sol | 9 +++++++++ test/Integration.t.sol | 10 ++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 9e59d3b..e41effe 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -141,6 +141,8 @@ library PASInit { address[] memory rateLimits, address[] memory controllers ) internal { + require(hop > 0, "PASInit/hop-is-zero"); + BeamStateLike beamState = BeamStateLike(pasInstance.beamState); beamState.setHop(address(0), hop); diff --git a/src/BeamState.sol b/src/BeamState.sol index f7884c8..0e0c9fb 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -178,7 +178,7 @@ contract BeamState { } function setMaxChange(address rateLimits_, uint256 value) external roleAuth { - require(value >= WAD, "BeamState/maxChange-below-1x"); + require(value == 0 || value >= WAD, "BeamState/maxChange-below-1x"); maxChange[rateLimits_] = value; emit SetMaxChange(rateLimits_, value); } diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 9dc6cba..4c8a9c6 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -316,6 +316,15 @@ contract BeamStateTest is DssTest { assertEq(beamState.maxChange(TARGET1), WAD, "maxChange should be WAD"); } + function testSetMaxChangeZero() public { + beamState.setMaxChange(TARGET1, 2 * WAD); + assertEq(beamState.maxChange(TARGET1), 2 * WAD, "maxChange should be set"); + + // Setting to 0 clears the specific override + beamState.setMaxChange(TARGET1, 0); + assertEq(beamState.maxChange(TARGET1), 0, "maxChange should be cleared"); + } + function testSetMaxChangeBelowWad() public { vm.expectRevert("BeamState/maxChange-below-1x"); beamState.setMaxChange(TARGET1, WAD - 1); diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 8d5fe78..25a5bb5 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -255,6 +255,16 @@ contract IntegrationTest is DssTest { assertEq(freshBeamState.controllers(testControllers[1]), 1, "second controller should be added"); } + 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)); + } + + function testInitExtrasRevertsWhenHopIsZero() public { + vm.expectRevert("PASInit/hop-is-zero"); + this.initExtras(); + } + // ============================================================================ // CoreCouncil Direct Actions (IMMEDIATE Role) Tests // ============================================================================ From 027ea6d2d210ca9f9b6f26065aeb98b46fbcd6dc Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 17 Feb 2026 08:24:48 -0300 Subject: [PATCH 139/150] Minor changes --- src/BeamState.sol | 2 +- src/Configurator.sol | 6 ++++-- test/BeamState.t.sol | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/BeamState.sol b/src/BeamState.sol index 0e0c9fb..146d7d8 100644 --- a/src/BeamState.sol +++ b/src/BeamState.sol @@ -178,7 +178,7 @@ contract BeamState { } function setMaxChange(address rateLimits_, uint256 value) external roleAuth { - require(value == 0 || value >= WAD, "BeamState/maxChange-below-1x"); + require(value == 0 || value >= WAD, "BeamState/invalid-maxChange"); maxChange[rateLimits_] = value; emit SetMaxChange(rateLimits_, value); } diff --git a/src/Configurator.sol b/src/Configurator.sol index 6ee37e5..7224dd2 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -102,15 +102,17 @@ contract Configurator { uint256 maxChange = beamState.getMaxChange(rateLimits); // Ceiling is the max of (current * maxChange) and default + // Avoids overflow when current maxAmount is type(uint256).max and guarantees decreases are always possible even when maxChange is 0 require( maxAmount <= defMaxAmount || - maxAmount <= current.maxAmount || // avoid overflow when current maxAmount is type(uint256).max + maxAmount <= current.maxAmount || maxAmount <= current.maxAmount * maxChange / WAD, "Configurator/exceeds-max-amount" ); + // Avoids overflow for a hypothetical case where current slope is type(uint256).max require( slope <= defSlope || - slope <= current.slope || // avoid overflow for an hypothetical case where current slope is type(uint256).max + slope <= current.slope || slope <= current.slope * maxChange / WAD, "Configurator/exceeds-max-slope" ); diff --git a/test/BeamState.t.sol b/test/BeamState.t.sol index 4c8a9c6..841f04b 100644 --- a/test/BeamState.t.sol +++ b/test/BeamState.t.sol @@ -325,8 +325,8 @@ contract BeamStateTest is DssTest { assertEq(beamState.maxChange(TARGET1), 0, "maxChange should be cleared"); } - function testSetMaxChangeBelowWad() public { - vm.expectRevert("BeamState/maxChange-below-1x"); + function testSetMaxChangeInvalidValue() public { + vm.expectRevert("BeamState/invalid-maxChange"); beamState.setMaxChange(TARGET1, WAD - 1); } From 189efd732c914e4640028b6a6be53d78e338601e Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:58:34 +0700 Subject: [PATCH 140/150] Fix README heading --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37cdd5a..e891fc8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PAS - Permissioned Action System +# PAS - Parallelized Allocation System PAS is a governance framework for managing rate-limited operations through authorized actors called cBeams. It provides timelocked proposal execution, configurable rate limits, and role-based access control for interacting with external controllers. From edef24baafa0b0452eeeab54f40d4a8e9f26d336 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Mon, 23 Feb 2026 07:44:45 -0300 Subject: [PATCH 141/150] Add audit reports (#9) --- .../20260218-ChainSecurity_Sky_PAS_audit.pdf | Bin 0 -> 1188261 bytes audit/20260219-cantina-report-sky-pas.pdf | Bin 0 -> 511093 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 audit/20260218-ChainSecurity_Sky_PAS_audit.pdf create mode 100644 audit/20260219-cantina-report-sky-pas.pdf diff --git a/audit/20260218-ChainSecurity_Sky_PAS_audit.pdf b/audit/20260218-ChainSecurity_Sky_PAS_audit.pdf new file mode 100644 index 0000000000000000000000000000000000000000..df1105728096544d469d74f41944f8dd2bafae9e GIT binary patch literal 1188261 zcmcG0XOt9G*DfI0gbE0VI@LMnoO5-~sXC|XuFe^76ik4UlrTt8PzfR`CNP00ND#zV z6cE#kihxQmA%YV0Ru7sx zH(aYWTGnK$3xq3hBiVv8U1*Q4Of}su!CU;~9tY7-6?LVV|EIy8EW_xs>@KFKood}tLm#~8 zZ}hqI2mJ<}>x!kgS1zTgPUs4Eu^6a4=nr4aXK;jqE)kE-K&6eMWAmjJ+(7@9P`&72 zREFLO8|B`q(Iu>KCD%f_c)~8G8*WzHjWpcs;$7VH-{KgmjdIt}uFC=i9jOAG?v<(8 zYQV|z&i_H@VI;$*4iLT7t_?$YP32Uu$UB0q#=R*GR6I_VKq*FZz@F>2KRvY%Jl_Tui( z>!opr_s-03W6i#qaeVeX=BM|FzXaRWtslwftERn1pT6bb6O&w`AGRJ1yzs|e7oM0Y z_~_(UhZcOq&;IsvXrw9g(Dl;;=b`pXuYCXYvcR?hBfr2*KXPE+d*Y*B`Rd9|E`E7K()ILA{O}1prs--VX z_6)lH&FLTIZ~i&CXZVGKDMM?x82hg`8{3Y3`ckv_(E9iMz^OBv*Z;nL(D_k)g*dDG z_IT~WmzzfX{;%Jj|HMA;z)9KX&po?Bw`P|1#L~fnvt8Q~!xt@?<()p+Qu?#McJIGO zY&iE=*SNKzk|t{Ce$cmS_P z{f%!9nEmvu1+71(SM-mrxCwU_Xp{=q;(k9&{o$yFhFv+2gkkUh{qvdh=We0Tn7*QV z+aBQ(z_Rnm+M~qoTdr<4BvT(0_gQqq?0H8##>Q0V8=}`9+r-Y?wfX)p&oIB~cdPuv zuYLw}JGP%Zy7fmb!SwT!&hNGlRCeEeCV9NyYgZY%OULzROzS`P%>38N?kA$R7$50g zHhb-sg~;fMZy5V8qknGlUHvU!W}RIqG!^DdC;mIJ&ohH8f9926b%S0iX$yOMzDNz9 zb%DKN>SW4>``65#-*aO6wn+zmFKsRksXeuI=z*cDg2lDJ*_PbDeZhU7K5+hr6K7M^ zxkHX6h)&ju&^!6taOb8-HY6W%`BU2a?2q_v`DXB4E9ECfT|0QS#l7NC_tyKbnMlN( zL+-ut{b{#8FKG1kXq-3crNuW$mYy3#I1>n(T4&E%K4dR*{x;~F9rhb|u}J0g z`&G}^=fCWkHH-7w%=NcTY}^1nG-Yhx=BfUC)7`~8qO5~kG0ctS!SWYgEC7S=88idF z#qh>wUC%zfjX<8QJp5qjEa9EEoKD-Hhwr_0->||M4xYZ^`27c88OlHKjAzf!15IcC z9I|EX9?6Ue@3>~2eKT4AVr!lGo%wIV#ciWSWOrJ-J?0?oG z9f(QEc!E!^e)6Sh#qhTa$dyT}x)nH0Iw?1@>ltyti+aIPja- z+k(j-5M|ZD@FPgiC$}C7E_zljFwMSk=TzHQs!tbierYZHDKg>>h?g;&i7%g`mqM@L z+SPFrx4bQI3(w9FUh86f`Ouv!n(zkUoOz9b)V@0ZS%bxjnkEVM2!Al?%B5{at%!CHjF!W_o>if?}bClUv8ZLS~%m0 zuV+HL53h@;e?PiuH8|;|JML zE_N>_>SBUuzh0!&n-zP3P$zMFy(q5gAg)g4MpOHL&GJkyJfYRe(jFw)3zIrSJP0~# zZ?f;Sqr)#Re?{W&G<5_<(Sk{oGzxli`!ZBZ@9DMk?~&7XCoijy-~RF!y7SO_)}xLhji_c8b|bZO*7r&>^F5X zVVrJ&VfYPXf5yA1FZci^tsj2%{r9Cl8FF%%bNgnOPJiUU%;J!r*6@G*a`AF}@tck7 zX8a*WmiC$ORPONf{?kmUr*8OUVgAVXJF;hgS?l>D#isUbu^oJHf_3+)b=N$^y7`_L zs*mXV;-T^)WX=z3#{TdFwEtV=kGDVj>b6Oq3-TS^=X}2qPV8Q)p1BnIs&wKx=EjkI zPUAw4-n#a!`My5u$^%tL4nF7jsjJV)0e#ke`Pi?wO)bUdZvEyO;?*-woG3N>K3-%{ zzZaf+{J!dYZ%-AUS+wiNbAIVlH!JVOnnrM8{gJ0uJ!R3o|Nb@CE!ds8aAw$Fhc=1x z|Ju2Q_SoZ_ZvJJ;_deJ6Jwql>LpDs2-5Z?E$PAr)_s0kC9wi_S8a-)AaX8YB_nA0F z?0nrcXh&7vM~l4*S5bENn|M4tj6HP6TW=z3=Dg5+nA82|cV}Lz1<)7x8EnI4UF^;Y`V5~^)>f^s-aUiTUdR+yf;7izMqGU zSf+kv_Q)02FVgHEc1vjR9a^rv!l>47~QtOnT!nVe&9~`~^ z>=T1N?BWf)(0AhG8y5E4o4oeZKCj;M*^AP#gTZ%peXW}?;;}(DZ~Ee(`Sp>C>t1{3 z=7YEV+=n(L{?5F?svU!``p6I*e9JF7&Fmnq?+rk44rWEa)%wM=uUm1;8?%ga>W8ly z_`s9Ng`-ApAARTnXy(C`Ke<0;W_`Rcz0hyH`CCDD828>k?pvAq_@$?U#OIXbh6rwW zbQXQ9Kk;xP{NjR_uKF~2LOS-kcQ=jxW^8TJ>!Tu^3jg{3uU-Gi%nkP(-*W9i!Y>ov zq>;84#rv)Y9_hlBaNc2K27fpB!sHv? z;koX8+R?&H9d^goNQ(T@L!YYOG>s+L=M6Va#og@2kr%DI*J~yweQ5U=f*10!zuqLz|ASJY+%+FFKRdtg{D+^h`l=rf|9HtWrch@1It6%9KIuiQsl^AROq95P)X7Ouhh@V{Z{12<>A1PKGcvik9{q)*k zZn=H!2RB9MGQvXjYmwJH<24QoGsuj;;QHxFh3oM(kFR(ed^>#idz(J&`aCg1{pjYW zpPc^W+sUQLyEv6cFX5m7Z;=HiSJG=97?bK;Hd-OzOrUD{i|_r|1eRyIQrs&ksItAcAviP zH0B)P9D9)bfe8<6yzb1Wt#PCclP7+B!}0GAzW%{4GY<_S-bs9EuW8j+OP~+PA6)yv z@~f}8`RAMW(ML5tsvmrPY-0{1!nW33oPsvB#e#?kEapM}R&+Iv1dEn5AZ-yTFk-?2T zvi8L1jaQDW-nIMr+40R#opRePx8M5Ju)n6=^v>!d*5^;f-xzg(HtwO31v4deCSW>Z^oIpNAdSV zSFbI+{ACV=AAisF{te%rI=uLBW!J4cf8BX*5`H}iiiUbkk-{^uiK zykQ?7-Ms$bZ*LucxZG7Xe)jV&!>(EIEniipf4R@~gzLq(63E)0zTK{QL*qKQk^VvW zy3)4Nh|&)SvTN5AzF2^Xee_#u*EiF@ap3v4m} z|NFqv^}hxerJ&enA3pzebj^|XPcA$8+J?Z1xBSU)J#l^h;mpJ9I8VR4(fmRI{CL%| z702!!^YG$tHy*q5wI7yz{_Yo_y!}=A?!A3yUA1kH;BJ=yH~uj7!mRzbZCLt9{KTaE z(N}&dZ#-ICd*P8oYaV)Qz{Ky)M$a+7JiUJ6k%#_Ta?4dS2A=(q@z$Mx#r`cnI`PX3 z9}QZ4eN6d&x99?LA${V|GU8ANW$81@H5Weq{gdqKji*jL@kwI$+6QwFPkmj>{A>On zFF3Xg*u8o;)v(TR3|s^5p83bj1Kls3+V=CVnG5zFn({XJ&sWdP|LEDrCyhV1F_+r8 z_2=S8#}_HhUq1bo`0W1E3c`qaB~j-GJ*?wJ4h zEamvShMYe+fl$tV`_-Arzn=RKsNKQHmtm<#eTiYZ$oTyYuCD;Pz3}>Se}~i7^*2BG z-}b(e+x&-+3|c{N$VIi?k*t)H4V%3KeH+b|7D+a`xLouJr5bf`Gu_D7TGd7uihO%1 zQET#wK7Z3#UX&BND6hBbtK@sRMfOGN@E@-5A`u$MXIe-TrSv#rZddfmU0q3>s+;K! zUn>@(WTbjru6IzEfGy}^YLhi1oX;VxE)j>-f$kT3(s^8QFSnWNaF)H&Un2kLLbM-U zz~Xgr*j%)7V6j+eb%l=OLKSb>n+Uu+qyd*ZC!@F|VS8}-jEk%2|_zNFbKEMvO;F@cmD?NASo|iU#uqi=%n)LKDq^FTBjbr0)opI!+L(iR=M224e z_MU&;c;Djf+q*Vx8g}r|Z8wg1`iJnMg$o`H9zOo=rapaa`-B7j!_|7N^0M#zU-ixT zuVr3z_U6i#u3@^L?%H{|>B-r_XO5J+a^Bo_;gZZ~dr!*CEILJbkI; z(TNLQ^*r_Gjjz!$hYgF6+vZzed|g+bz3%bZCZOld$Z&1-FG$LodFRXJ9vW6hE&O?O z_`17(@$Gz0_lE6&mL3`^diVw3wqd*OZ%^u5;ni;ZPc>c1qF+({_m0ByGa8G~Y0>9i!o2qv+G=Q5(ye^8(uMkRughJk@_XHebERWk z$v%hc#`EB>V2aBTPhZoB?oteWw2Rnu>joz1C2in zHd`n_FF?=-k$@vamC)PD6N^}Ac)1ugJo)aOgO03-%zgBvGqw}_V`ZNaD>Uwf)15A|VcY7-JuBSa(0fjZLOim% zpRdR4UG$96_YC3V)dN5733(qqW{f@q{u-f&0%|;o>ab{$X%5 zcqo6Lb%lusJ@uh?kzsFs>ia7oUijM&Ba4Z@=KIon^y?NrvcnxR?1BfZ>3JI&vE~u} zx<~f!?EYMJ3w+ZB+h050UbpDad4J`vF3&HoTQY8k{d4?={PjP~pSbSv>vpImx(AL7 zzIA1~@tFNXlUTp%=*-7fHXBPBAL`%GEjT(ova;BURz9??$G>*;x&!m?Y(0M44sEyY z;Uh!j^K+*ji|w#<>t8r>?Z%bkPcBt_2>zja{OF9kR#s0%C!Ifi(=ET8Iq{z^ahYH0 z@KOJNT;kFNSr^;f>%4A{@nL&`=^P2{kd28hdKWJ z+PjuLd`kW0UTHVA`aOT++y$rZTe^$eZFU@H%jZ6D>Yg8W$=6%P9~K-~_Gsh&=lAm8 zFbBSWIkId!t{H5k5EGl98`j{>sZ!{d*~piHf{&-tyOn9LQ|Ru<6UDoD|ZZ_=EHC zw|mAevaLm)djE-|Jlj)yhM%?kxnd`08evcW(T+pNb2Fd(h-WtJ89auu1tDEKXEX1? zf$#M>_Sd76E}V14k6|~izWKMF+x9J4ZwegKELnL|d&PBomVWLE=e28A&tJD<FJqkOqq2<@O`(fnsDQSZ=9uRdNz5gZWS`zx6GNDrn`ZB-{e)b;r@4=$WS~$ z?mu8bVtCJe#>7xvU;HMfvQN*XGspkaKdwZv|KQBOFTUxTYAN$Sx&1~t)_;KI|K;`@ zg?0bH=1cCNt(I%mN(VZkN#Y78{ck8NP%Hl@ckuZF zA^J)H(_>3EizuM%&0!sM_qW8RauQ7}|Mg0@bJLHa-Y$Xgzkmubg`xX$K`pi{U_gLD z;=t1K{6-dLVO=qQRM~7@ohRAd|C#c1k{N+X$CrzQzxzX83q5A25Haj*-NMP` zs$v|o(;pCugE*E5P}_2aF2=}&N*0>2(<9BH3rY=Moiel2JtYjqWa8|skw?BXt8tXndN1%#1 zUMmf*@6>eU@sY&TV7`kzNV#_JWS*&J=PjT82ppxA&Rg9NEXlL>#~N9oZS?1(BuQPZY{nNu0-r%4d1@u45&EbGKTs1!n@~Uxi*O`qDXWq-%WicsT?1k=wp|1`Z2x!* zDVqpTj4-T}iE=5>f_NRO3MOI;#{jIK+ziBW%orO4qYR>! z=!nZN3WO~Nh!MI~sSmO#Fpc4a!r2lwz*5I$qP(ZrpqNwArrnmU=)7g7o7y=uRILu5 zj-HfSh_Y0G7tF*OH6^3clr+4t7F9`&Q;8BEY|q3@9vd5U#C80JUEYG^F*zmkk2w*b zy27F)L{WPZtD;yAU!N?JSh7-n_X%bgjG1tV=EjT|loQk(QJ+O<0l01gSquuvQmN zSDS@2oM{A8)SQn;wuKW>y-P@MHaj7`lT1olqsdBBN2@$#jVK|yNgk0FbqEX~4bniI zPaMs(fN%oj!+{R!HcI`dl>m}bh5-@_EI4am6(L4l(NN%mBC!VR3uQUEAgNN7 zTCF0VLRKLEW0QoCw~i!g;*^GGaYV5^D-cQs0052xU^Gj#lZ_FkA1Q_#L`Oue^{`s9Kz0Ygx7@H|EV zm-DEYq!#94I0t~FP!|-CG1U;ZoFde%6f~BU?a`(`L(9jhG(#y%CJ=d{Kq{moxC1$l zAc2SwLC}sfG;F-2gkkWd{@Dvo>xi>SJb5LYR8S(Vv^)}26|J>=C1h=ekf4|j!5CT! zLa~4$s`VIMQVKO0yto%EGld8RO&^b46;;9sr%9(tNSP6aB3GXALGH%aVkZSk;ye>X+9aAUp8~RaM4(Z zwB>4T7I1+Itq6)=O!+iStYOG=n2CJVo$~S-4I)3~apK*jEFD*}`?VZy7`E0+RT+}! zkt^bGRHLuhq?Zmd#w1yhGL;ns*-BWL2k=_Op8RUrtyto;tq3SHVT9EFQzkOa{Bn*;KxIEoqaeFB)Z!k`hvP$VxjiAJbFd2ccim2Iom!|{i zPJ^AsnRj zr&`2>P)Fcg1VaExR)K^Bwb*Dh#G!IL6=t(?4W-s9*3>!lFqL9Jm||QJ!RNuE-KGsk zjdnSugAzL_sjO+V;(4X13G&K`hKq^lt#K$AamwjAPCZgr5hP|P6Y#4TL{3^|M-7Ho z0s;Z<#q@x|=&TM8(G4Pb7e0bW+o_BB3Nf`JIaqVMM8H7nFvQ zN`hD&(Qc(K^5-qE< zav4W%c~uN;&J{@-1k4J7lB{c6PQ(OS%tTxeK|CH)%@W4IN^U@;(}B%OjE*!eW?ukH z6k%zwuE-2!z_P)Y#3=$o*u>+j83qFjrpuVwVo6qx#Tm3lFe^zZI7(#*!kIZbIgZUU zsObUn#VueZEFqcV7sk!OvJ2tqAuC6SH$>{iGG8H})G(%e%&Lkr)0QM7+{EL<)fP=h z6q;0aUPpr$jh5NU(_^+!HlcGUDoh{O(h{0{%n~0(VOgv(kH>0F0eJyYQsEq)kr#2x z!~(ktMU^~#5uwx)K#}XSd2x0tlh&(MfX_1^@fJd>hLXw2 zN38>AN{1R$Ubfy02kfqh05)1RRuah zmy9n<>dR1$0gRWOX%^&iM0u6VnHU)!dY~fla8|2`vVZLsY5VR3iD!;SwiSO!ZEQ zm5{R}wWL5RBoJFEh*;S;n?2J?)OdP+DW;Fbt9g$)lXUpCn1+r`56JbfSIP+zN@kP2 zfTwwMT1ya-5inj&7=}clx*=Ia=j{f>NDFWAl|AMxS;oN>$h9Eb5RHgs5F_rAt6LHz zozrFbN)l+WmMbJTqo5VEp$^1C%TFPlQO~*>Wssn3$`fL*jGvWJSRfCfBwR_SN}3Hu z$!u4WV$TuEYC}TD6sd(JEkv#;nRQIIgZ;a`U{hr(6D&!+Iu|MyN(@o0nC03GVTCB^ ztMx`fNEvcrsUm|o5puzCnZfGDGY~$H5LG*ZT#J-KtHpc8A;1+G>I$#~!eyz+2u)vh zT>&8kg-mCXq32U5Qk!pNBK3qi`KSDjR~26d9h^0JB>;v$1iZw z4WcNTjst;mFk7w^5=D|v+oqDN30+cHFDdvna>PsLvtcENTZAZJQflV}q)duTKvZOCj$7iI2Ds{FNcs%2ID17AKa#Ys1YlBUlN?jW|OpC9nIe9K9_n4@en0RUGoB z*adv2f(bLDWH@Lo>Kt}6x7o&a(oH94R5TW`)e!b^$$6EEF6N{OF%zR~@Rki(Z33@H z;~9$lYU~_`S!Uv3DO@ZF6m?N13A?H=HDe+(FVGc2LI5Z7%-59UO-di{` zVxXwu@XFLtjU#RI_}Nly!{N`>BJEk$vnfR<%-aw$+2B^8ZwP7~IV zp}19N&Sd2BB95EITa4yTeFab@1yGDnXu~d+s|gXC#s=e3)Ry@}MLfG6%>|1_ZzRve zM)J0lJMU8nJh3QE#^qJ*ap+Ry$5;b}I2{FnPEK3qv>SY2Smb6SQWr&1iIwF3R3uBL zK*@+mfd`pb4ZTf9l4wvfcB=1gfb23#dJj*IsStn?L(W!Z8Ah!-%t>o1E-TQcpkqUPlW-7>a1f#g>B7P7y^9D!D1XI)r3Re52VHEZVCoOIc7;s!?NehR7jr zLDmr!z|_!+!Yrc2&7nFP#1iID0waQgbW66PW)ovxD@`wvN}_5IsS0gq!D#U$ ziS|fa231^axj&Ed=~aB2ODm^Bu?pDm<$F&Br)T2wymo^qh!=@bKo|4Y2pnS!N6k~q zh8Q_(bJsB_MWm6XC(3?wOBF(bf?feG9lJTYZNQEQ#hrq!}l}vZc zTZJTGMhRn^h8z*fnyDOK$(P>=BWG}znmVib!P~Qg44zJ=!yU%7Lw@7q=L8V#8_FVVUpu@FpE)?V_v&hr=-!9 zR*8~sLD^!+r=Z8d3>S(J4M`a0q#3$F7-Pz7IavSfb5Xc9#HH9-}_#89!MOrvgMl5` zYn@p#jyi$Hlr<Cbae>)vVT4AMoD3uB%imTab$e;{J(SRaqa1C5L zn8jCl8l>*5fhk{|Y!5iw=7L>K5^>?UUWElYe4odr27Po;T;?IDGJ%{!2Z?1$mWPrA zgn|Yh!PV87%bP_5p~I5lN>TP8r=h7hEJaobh`bHATA;3>ggDU!x7*lS-0N#AFeV*6 zqiI<IS8Z<~jAf|M72aOJDl@Iyx=oOKY zoQ!eHNq;7iX~a9bD!>*8QIbL-hlrnwv&6A~?4g4X$SBh(5vByRnlUCMQ(XzGwjyVd z^AVPdDlF(w2#Y0@y|AEalC?4Qj1Q;hLfyr2vG?KUthQ)v!k$O~FY0=i;2O&~O8FGhWKvdNXo7fzW)*Zp) zs3`u4S8<({B93GVj}pqZmJWTAtIJg#op3q2;T3fOmC|YH1JvBQIHM`# zYN&mSbIN!S3|CA+CoQVaW!&wkU+AlH-3Bce$=JTO=ZR(_BTjNC>qIeo=wbP!|Qpe;h^)q;1Jk!<)%xBrJmv0VO6jTx=W`n1_BVDWk3Oh|5J1snoOiC#6O;7Sp~>GG;5H5@Qh zEo!Qm&o>bBJU*^Q4B548I-1S|h%!}bCo`@Xz8y<(3>lt6n$2pGRHqb8>r$gl#YYIa zlB?RbC}|XjG#-=+yl62=vuf*wBK0C4>Mh~j#hN8s4SOBhfIDmglkR%dLO_`wiv+?Z z@%a=;kjleuUAiPthBf8x3>L3t*Nl)2&sXTURVe4DX|J%^V5uH;a%vR>MGRU^A!I|8 z^-4H}XLZVOql1Hwv@`;9kOU{i>PT6}vIUK~SR8MuimAS!qXn6J9fO~hVskkS50EuU zIz7LDXA-r_Qp6C(<#Qr>cfl6V_)uEIitUAdk-U}3YWT7`gUyfA5qeFN&$s2<2wO{l zdoK(ud`{kx<>^gAf6;BOn^4?k)u@Tkz!#cAcm@EYJG5CNJSeO9LqpAy8-HyONTza{8H`6V{yBEKZAC4&VaD^4urN^M1! zLnK%w4yc^YwnZgCO^LQ4axg7xNQHQ$7G`IQ*ibZ0O>;FSt_lE1kiYJ(v&ak+HXZ>u zWR;|3Z&+)WfnYeomS6%LUMbWT1|$TaEv6MxEF{Nw1e_^Cm9H{XQbArVLausE<(jDk zRpkLuBN)iJDqegwtE2{s=#`lwY0U(}29S&68)aN-y>0R^5f2LutQeBXN5v3AY_rpY zp0uv$EMqCiWq9LG1n5>7kqI(<7F@7QHKK)V$ScVzjTME==P@VjLT^I^83~}qRx=D2lHV=98vk$8DBS~OH%9iWvLV#E>LUgX4+xGH2L?X%nh?q=5Om>^TutV3DnfY1_2kU26o+ES7vSrRj0t$_8c zo1iiv4XVdrwfg*gCl$h$QL-|o$+E&KiUtrzZFO(jkrN{sibCO!V3e71C`pfyl=zf` z6Q&{@2AI3#5@ft179o%m)(9g_M~NSlN~@M3&m~jjPzxMlww(}YiHIe1kqKdD^Efvv zXW_?e(R@kCqEq88YKCXXDimrF=yg&Aa!(ocd4q?=5YpSFR8GW<6LdgGkt}D)QbD&0 zj|;KO=4P$jq@?gRttVBFv%JoX0#mI7h~2Vqfh+c44K0JilH$3tW>3AXuzJ%)YOLxd zqTFWM8le!lU|8mCr+W#LI_UTF8gjWdAdFGb`lJws4B1xNKtb;;^->UTGN1_(GDhs# zu%mJ*5aA;1y1I~I>unwi7cjM49u_rQZeNDCm;gfzwIXUIEQ%5?oqn+M&JMGrj^YRc zxi%y=n?oT_S`m_o&|5sM03c}XVSx*Mvqm9NlFIFN-K)&C>r?NySRA zzEIS9V|H~>!3#+PX}?eyN;L`TB#)-n@Fh`xE*+^xKnsNMy@a?|qO9UOZY>w7_3|JT z30NRh6H&koEDHcZ1m%Xs^CQ3nN+9a(inyzR4#X!kjh$(_;c1gfe0~IqU zOUOwk6NO)P2O^BDhp3-!Hm#orN17;%G+MD_yz+0iJ$jWThC?@m662ccyc#J?)k!=cc=HA*t+0+$1 zS#mh1`k~nCtGs~r$|ZkH=Ud`T>}ugfwoiWr8p!wwU=42%L}ej!HDW# zk7>BVASne(l2(9`BIPjou$a%*2({jBJ=vD?A>vw_CnU#fvU0R;#~W=xD*&e~d@rcRP-*8eFh$;FtXDU55CLG!NU=w8$7$M9iR6Skh#p+E`Y~w17;zlBDLx;ZR#(x`fOVDZ8#DOQ8~H^CE!3 zYJ?iaTwHgl!l4&L6(UL~khsmf-Hz5e7F;B+B{Q$cxnT!en8IsN$Py%>Z|eXoq%8^J zV;+*7PzFs|3V@Vp8kA*>&?$Obw#{gOTzfk1E;#~z6fnB*J~kLl^=6@1S_=^~Su+J+ z*TLwkm2{98iYEkQ%|#>zA_-Eb(B=Oxy54JBRjg|py%sQlDCR5(3WAukWD^1^h@hT) zSHHW?xvJJ%?cWF0TGdH4r;gFXlLp#-myafUtm5Jaw7B%V^a~^>ni^B$=es8DR;Q6M z+lg-l^)@Cw)Uz~<0>wso*~fer_!KSgv)2yX3F1K4kc{+8u#NAr&+l@r=DD@<`@1Bb zsfb$<6Rp!7%1E-9Rjtc>eETyWP5PO5+L=FuaQWtbP)ALDSc$4avC*S7 zscn*`U0UGlN`=U7PwD!)r9X0F!agU`$i6F=CeDBnXe%MR-JZ8OFR6!~xqG34u1SV& zB->qs{%w;@mFgcSgU57JUSrF=%+Vg<9q9DW`lc9OdH4OIMjXD%oG?QP^ZO z(71ft*4&wJ9b$@}8|@pc){hNRdiQRdE7pyu6~i@=#iMm+{U$8+*{iUXJhd$~ZjJBt zndkVC1-#legjH(KpUQb>iKH8So4>K7qLinjEr5j}b~s1<;h*!YZ4IDtv@R*%Hr#iE z;Kczp{hS#^bRXyE7b|W&^;g->_dB=<8o3ocfAc}vbWLyd5=O*??wA_Ra##LkC>ZCF#oP+Q0!whf1$kR3nOZdZ0cbGQwNL>kmrLo zvnr@JC279tUL2dGH)KpYN$-o4tZ@`U58Q)qx){FJfIN?lB;#QgBAM2Eby2vBe5O zCe8Qzoi-QN-wx^CM2VT@JG^2u?`4F-i7Uy%_~q2!Pn}6xi(0!TZ3t)_9eR1nuWvKZY^xCMkBj7 zua#4_^uTHtFT$s#SxqlxluWWR+>JdgBG9f=9r^1`aX2xy??-B%^4EO7%hyKo7g?#e zf|#6_a_%bF1~F2PTygbR4T}}-H2d8qcP?`Uxd%bn2IWc;OtpzH9F}IF;g}`}rKEOi zK#c+-f`_s4d4mmeKU0FXSO4v+P1%8h`qsV#5THJJ ziT5*dxBVnj)Z!*+d|Q~Hyp7EtyupPTg4`X?!}tO%Bk~sdY^gh}z)}aWT_q!t`xraV zm!|Q_QKjnwv-)SDLK^3!-yZFX^ttKg9&nvs{TTxMT6@@RQBN1}*~|Kal@_k(pp#(h zyUMML*JiD-*0>zL0uq@=o;9?94BFW1z*?HskkYnouI}HCVqv}J^;$nwYR8TdGaEYMX^6v9WrSJdC&cJLhK_;z^pu@%5ap2lf~j0?ik8MQV!=g?|n-Of=h;dln{!88_lV5-OtGwtG_mA`h5 z&HHQk1A7hX(K%;>{P(d*EW#rOa8kuj7o3N-6bB$qTCJA$VADH;l;`5;{xoTRnws|! zc8Ax_HMULJ!HxC{Q&KP-7mog#m<-itC|6FshG{gZD?R$0h2!n+c33uU_RbiQ9_*xv zIcY0j;jdC1qlMnWB6jm28v6XrX^Q%d83Nf^9`%|TQ zIX(5xYn_ZhT&yNK7pdgI>;_1N8eFrmS8;{ui>LKj)H=4Tb z-VM=%r7JEtY0gPXn;O}H?ebCgvdWqJVo}q|@EqxQC0|oWVLZQYBShBv{_PH+O#db= z(&0i=RYFC~!K1R-m4f*%G*wK!K*47=FcC%KXMEV*_kA=oTYK@-BQ~5SyPp2s6KKs& z)Y*MQMOg1_0x?dmQ>A`wag$}TyX|&6Z@xL%(et-BZC7NcUPQ7p&SS+@Kv`e$~(k>qVhXX(_CBB14+k2h1zPGBrW8+nOT#VuE7_pn( z@mzCYkJ_?|7N3(M@G!x|dDgSb21XT*qCG$uz6)EVlOI#yZ?vA#c(@+-gjZHg_M5ZN zBUj5anbTW)`K#AO63;1giex+4$ZJRZv%_$?ZS#@*!75hb-HH5o)1Xu0z>=xP%YWwa zES;|J+QA$+$zG*BGA=qMXUNY^-MRc=m$V*d$95uByo8EW)<#z;3EDvp1>9gWw8MKg zm>G5+wJUbMUGM6-@@bTbV!xLi4_{uE+RxIoR?EfktqVe&b*k{Y!e_PmwxPF!hAn21 z0;-e`azm%f)vxsA98XMQpx$j4|55L5iw4ut_q30;B2y}l&YP%=xK%fSf-VNw^|dYU zqyF_!1H%z{FBFkc^sTyzLi;BFKFpbLn%H~Svkt}bA2j3J7R~@n;>~*F8Hpnqr}6m5NfW!~{ME8ltt*Rr!HbXP6$=3<<`v{sBYsd zg#~bg+_h6&)nDehB#Y+0@xbK<7%!Z9X?%q5UGUZ8%gOg!2V%Hg?iL^^)<_{yq0fX{Q&S|DO8~SR%={Mxkj> zK+ibqn^TzDVbIfu<0jK~+4o_!;@Or0kaI@9-zK_BY3^=7PN97}jld9o^;()gWQ=lk1P` zob(vs9{)g}7~bZ_{JN_%jr4O{uk=lW1Nd)EsfW~~mh0c$>M&DKEwyNVzPDWN)SC)O zD{*SauNykODa(V#BxGYBUN|w6i3A>#sC9eid*ON^O*Db9sWqs7?Qw=FHHo0Q$jXR6 zf*O)Zd)3)~?5C^jTag@()o!~cV&+G^hshkBis1)0NlpN4a52Rl(<98cUdtlh2+)s` zdh{=>AP|&ZVv=owyl;bj<2x;WP@j~s=SRGkdnGgNHJ0rS=cV)h2XH93&2rsehWUF} zc@~=cb*;C7^MU(RWipl6d3IZXS#q#!=$*BJ$DcDTRozua-n`t56Kh@X2lu|h26lY) zoJ^k`te!^2sxRPs#KSjS}*hgsBw*dh2LJk(-=kN!Ucgq{PT*K!Z;!0C1Fz79jsG*W@mwT~y zeijvG8h8WSQae2epS9|dZp!uU`q&vK+Wv}F$e%^7p0?)Rc|3inqf1dmz|ck&Mw^Ch zIrGv6)aOy#7c+gQGS-`=VpdK(m@hQA7BckmXXE{qt5qh^bg?)a)DBEVCvkC@UF*)4 zHVh)*9@}6Ft+^s=R$@sTvb|Kc1Nk-maK=-&(bu-;40AjDFxD3i zQ{a4iuVD26@t<2~HdS)@B$B(%2rmAB z4KZBu(r9_}I7 zc;lrr^WGa7>%~&8*?m~0V<8Wyd4HUQn>H8;S7b(fXI9(>27_mE8!T8}+{wgzch-Lm zi(0o7pTqJ%?SK-CHSIoP(D0w_F-(ltt-V&{Gi)z|gYhN3bG(_QH=|IXo=fHk6q3e*_ z*CQTmd(zRuRt6A%>MEaK$Id-DsPz~N2b1&Okqqu)spZVhMx6Mox{HSO^j=-{tzMft z9p9aPRIDS-lCm)>wHZr=li>qtJqnN8nk08WN}vybl-k~6C$)4Y*}b^Vb`*ey^>$9o zPuUg7F26@^>LimsqoI7Wcy8$FLLs>{;m8F~&6nhRr(}X^FWnmJ__DHJjpMt>ojPVA z?UZ7PL_2u!B{RP&q)y2~o97OtRnOiAEy@O!E#AgZkC{`JSj#6g&i}!XTeVZes_@7( z2RtZe-i6XLk($2WzY{=Wch>=FyK!f#~4KUSX%=`C?P^hwEeY@@g|(hs?1}(%<=RIyCMj0l{2(A=c1#A)6ad z(|ZNk+W#m{GfA1!YMZgHlHafL*zgvEFAcd#=;#Rq_f<(unEC_sme?+uuF1$PUvKKy zxRX6{RoR<2S#OT^7bBPSkJkRaJ90+7#NAH2ZiO3~t^AavUFvn57N&b9wc~rjG?8#R z^Z6@%HBLclSvBXD{Jf*a&q>XZMR|=U(hq!kh`%ooo`5|@=-c_d>#hCaWow&GV=>*1 zs&K>S)_l{?Fw{ex-i1)vBwI_L7FpA+58d2;K&aE?&eKoEi<;LQOY>s|P*__`ZhtR= zm&8S86`(Q71GZ3PW@}4IOawnA;2Qi71{W8$pvw9Ahz~pSMLO4+Kj~R%Rv{NIsUPU? z-8rAnjbTdNzMH`)cPp=F%K!-5Yirs$jqT$9-PYZzaw0O;m~>>3F)9`2OqI-X(U!}n zJ{_Kt{`c!<%4jVM*F1hR@?L#Q*|E-JLk=qA3O5!C3x{1HHcM-!BQ?SX*rKJYQp{D} zVj-ST`fX&@V(=VM=QlLp>Nb(tWggpsrBP@7v71CI=kE{h=_6E5lF?9+K1bscv+vasYHE)?ErJZ2Lh$UZl|oGdAYbs<}bz9aSd zCYV=H9B+?b-qt8^t5jh4FGj?N8*DRl0`Q8gUEI55_?{VCUUI&eK27q?$Mvf1jCnC~ z>A8io)V}JFkWb>}m}%bT<&|Dt_O@f78%>@%F*hZHXWhe3#Yas?t3(51NsiAO z(|lmF-Q_?X7r$qu z*D{ci>9#r64`a79uV%iBSL_9jzhbTYNcl8@c8s-KJUd!6l{nXOm0 zZ(u`f*h=I4#S+r&2}y8>O2Qpjmu`^u6$ohi>I4b!gIN zC!LSR8mLgx9=~acs!!I=?;rZBGTY?Rc%?dZxN~qvgvGIBj>XmC<_tsTQ#~)S6;gt1 zl+Q)4pYM_JB@2yWf3*L$a)6UH3PglW75rQ>RpTLsP(O%;F;u{xzpl$%9dQZ?>(+AZvVwHJWW1 z4rfR=YMs`?a1tNnL`Kn+z|EH`|(AmGa0*owDmEs+8)`iE=6 zP`0BrCnvZkW_9Ty!$@IO6o(JR8I7h+%T%q+!Im^f zu(Z3_RgJa-2yzrHl$_4x>YvdAz@9jA{(e+@nUr33YP)IGL^mViXB5e#CY49)ho`An z%i5kvU`wAW8L|fYF5S*v57XMVwoP}xpSPUt0Um$2qxg3<=LFre-7ng5U!Jf28(q*+f-s%gpYH{{^ zcOSryiyG^^TgW2W43p_aw$81~P3lh6nrh@=)J3s-P9~GZb92h|t>xP~7uHMq zn1~99%3{~OvTe{h)Fg}DG|uVG z@OQebzb3?RnwW&SSA&3WKFxVvbPIOIu`*0R=|T73>GBs=`Y^X;Wwj{AANTfy*~Dquqz7@6yIK|sS(V}}T9 zemY1DQ(3D64(Cs~Z;s{ZJ2~+HP)mhzK`rhlocy+_fmLQHBYOt5y%%?@)hwxO#e&7p zHq*+Q_A{s=ftFr4mXD9^G{0>LR4XCfR-n>HEUshDXUo^_j-K=VHYqn=OH_*6d^o#_ zVrsGvxKesHr5-Z*3Y0P#hoefi1NgN?Ca5-;6nL4l;W*QOC)ZAD2%j?E8|~X98V9@y zOSz4Bve8wFuG!M{) zcr;g?+-N#aE7zNwYiVbLe(c1a|K3wgN;IhHY|fmZ3}m}P$$Tis&Ma#&MC~}Bs@H%3 z$r<~RV9DEZEa)WRydsC&9Lzq@ndZ$BRp;x$xLP&-InGUXPhGQ182BeAHz(J+pX$Ga zp>oO9_E>VsoU5y{C~;S9(Ouwsu+%pn<6Q~J7N_yLAN!|<;0x93{DLCM$NsUMB{WaR zhDt`wPNhm)bPN1qY?fxFTb7AmJrs@yQR&hg+`Lrq3be37xfO1-BjIF}7Qcf);^amX zU*d4b3-&QCtSHf~6kxT4p1gfd2I1FwSdRLRes-fbl;EJ;pZ^xzbL@3ZDl5!eQSU)< z>q&D-S0|xHtW2#3j?a4a(otE#z$2j&3sV$-mMyZ< zj<&2?Sb4Q+@p|efkT1YSq~^^em-O?&nl02t&grWb7RX%nmlF@Dlg7kqolxH#)Cnj9 z28Lgn7tmI9w|RX5B}X#jUSK?k#&>4%=C_OM9d6mAwhEq4+a7bzo2n|Y<%I4AR%+@^ zt?jbD@Il_N6i#Ws!)&zJPB!K88Ei@R*2o{udRQ7>pOx9d*Qtk==6_ECa;1W$CLS)} z7!xaVaf1iyy;Asd3b6ALD!niDcfa~P zRROtZYN|ymky*yeJm+K8D1VNSWAjU5!II^#zX+HS3-r=~2r0stoid_}OGZ9miuJsY zajPBJL6(P1-ZGseNHcDD+)2Eeu~_27(ROk_zN_)I1X^FTwoQGpt}QFe-Ugkv!_tx##@=$WuAr zelIs%C%J9IKKNnYFg=c~^Y(SDsP{VfbN4>U9PW=6oF%-iE-J#?e|8i}U83x|i{DNv zxCi8yoA5W{?y!BHd2jSYQwwT=!O(i++EkU_^Udfu?b1Cm=bxo6dpN`AT}3NqC=na- z{xgsz^ZO)uPNpN!|82)eW=6++x3Iu}(OD9K1fKL|p7JxcZo)wJV#xga2?EFEvj&4V zj@pKcrbu~O54BmG%NjiFoBCTAjgPr<9n(zGRdpI3{EH z8DviN!7m3%OpEute@=<_eIB>(f;q0`lanVY5balSHg6sdgDh(6E27h7uO{s6dOj7`vL61=N`C&#k?e#xJq$N8E;X8l8l`ED})CY4Rh$ueD&K-MzYpD@7` zCse51+4m^ExMzCpnba+>?CijQkV+=^@a7Y*liONNS0lHXIJgm(4L7H$eXajwlHVS( zeApv~IV96znFiz1rwHe6hy4DHG*mk~zsyeF?eN5#^|U!cKlKdQ`rF|$p=+_?NklK^ zo{LKDrHXp5gRGw#nY8IsP*B~6^HIkhgt65(&DG%U6_5BFnjpAIegc8cH%wyAzEhHJ zrBhQZTz3Kg+8^mBI>(aQrByd{gqV84oauSSC6z(ceJA$H$rHQBhpUES`(V^I)o@%i z*;kE)Fa9NiASk)$5=*{`w5(x{+m~8>Dx6}aLBkkqYL)!)^!vr{i1f~TkiY~wf19WTa`h4P1L2w$AeJ&BMlhde<15fB61+<-V6chzsF_0&Po3B zGz~K_L?K}MmI6V?tC7XUV=1S+d+*li{dSQZ4+#%4LKe7vPD0}tZ`KZ+Zubon{*!Xa z^m`}m`<)l`cLYvJP+5k3FQb3vQ6yZ_Z^t}T;IbH#I~ys@c5_W$ip<~S#h9!Vi~6eCRlCg-m7hTNzWn+2I$y{+7vJr zlyFtUy{#(%Iwu#>H67e}I8Y${J+foOBYM1a`?&x=fMX*Mk}4PpHw8<{LbV^1je1Ln zSME9s>gegp{+N3vKr#lFpj@z+eB+wrAhSq4xf1hchX69#UUs7&oknt>clw7c9d3r8 zWiGcTIS0vFkK8VShfA%}DciQ4TDwE{QdKf@pxAG%6pX+hC0$t>#Wjl01ZBqc47GyCHe+9CbTUmNfz<*O!l|Hd= zfjZtBNUcVI6+$7pI`*b~&@si)<7eriW;$}8!! z;;#R|!pPjLVUHu|3NMkZ1SR}~x-Ed;A(ryfw~K93aqccu@J=sDs2|Xq@s(uL<_;63@ zeYsEFuJik-X-B3t@y<5#xW1zTM>}k*t@g)sSO!Rr{~UTM*-wuMu=G4Zr;}akSjrNP z|NIuHEK?!7We%lkODxKTK{&dQ^F3?Hhiq_*qztS({zoqGuJduV=pD|T?8LtnEiRm@ zhhBhQtT0xmT#nwI^3_9lxR5M+V4yQ_M{!U)b}=-`DbBh;jKrjSFa?6CCfl#Qq)#pS zf-L#=!r!-#Pt}X=(n3%QC9|91#$6)v8tg=)r-g!{Q28|rnl+h{L}-eM8ukM+;IFyD z8oAPwsHF~x^=>}ANl=Fj;FwuRTr2k1r+hkAlg1l0ximhe5lGXhMh`X2q`qq%lToqT z+@TfjtQX`9Y43?e@9C4HczT4d(pFdXgpT081_;n3;xd+X7D02OlOxH z_iZ-*y3V8oMM8ZYgt%5n(N z8@-$08XK}vFSma=KYKx~x~Lhfm2Dko&K#tp4>iq4gQ`_tVLhhRqX$kpBq?$)A6FxDRw z0So_ozNU5$JQ{p^V6MjaUct0Sd_qq1lKmoQsMpTFj#az4bCp=nO@@NZ`%Wd;)RtA_ zvEXGymkzIH<+g;3M$IVZq`sYpw1r2%Uecb}G+MQvtpNGq07@++jfTxdGoG#B4Bsd&H47v z+!yDlZaR<*xN0qkG*sWoTB_$>|0zkGZL@~Q5o8<;eI&TibAx#^K6T;*Sd@_c3rAKBh=FCzl>2UxiW}j6?hW@q9 zBZDAnKIf%-N2FIL_f6(41u;9v`JtWbLo1-C@Et%?pt`e7IKCZ)raXUSK$XxDzk=6K zFR8^glR^PvAFRT-|Fl6s(t7fr7=}3PX=ClP@)SV*sNP`&!9*<6$>u@e5uNor{F6A| z3-+`;ubbXB=id1mB$B8?$2eX`!&F&3wI}o40&QyTO7*=NjSsP`PK=P@3`NlXc$a^q zVW!Ka`E{mOQ$Yc<$-RI+aE@iN+kG!8Sr1vW znb?#d@8hr57sGgScEh-65Q~G*61!Lm*{@`u+yY#N{mmrX?6>5Py!C9)CZ(ZGA14s= zxf)1z_jSP|2u~M^3@S5np1?*6teMjpe*J6ik4up`jc1ScvZFpy>e}x= z$@S7$=ZLuglVB`eQjuq({~gPV(~)`jmvwFh6}<`DXzlf#r(ik76!=4ke$nG%nXds; z^ELko>6zo_v0E9YPUo4enhC|2vB1?gott=6wwG7F=`?)K)EXmnick0Nd#g^4hiBwz z_P=dIyT9s_nME(}oul&JhBGb?A(pN^Pv+i3o|ZmpZn~)%&x;VS?iI zX2^iQV!q$%*Lj5@J7bi(k(9I6Uo&lXSoe>&mkXBq0V{kl5K!ElTC$6fgx{4rN6 zEHE!OFnMOXAWC(&VpU9sYWudk$q$}t^36tS$>N1F6K4jrLxII9Ud+LImiHlZw_U8Y z&duZKMXq7mO3wTC^<20Tx;rdpU(;ddxVi4X*pP;TgEfBf$0>_B*?uRgj#7ga#1a26 zg0Oio2C#L=nRZ(F9b36Y1z<4KuOGlWwQF^M0Pl6ZUHrOj);DP`{%m~Cr6Vs87V?9k z=Gs`q^=FQdpLQ@o2s-K_15Ro9F@rWQLhjTSg5Lj&AN!S0GrDCU}9H@A^|8Qzz zX`cz;W{3hT6gTEGjKuzhV_K^sKTt9=t93}P(Xtzxzc<0#9S~u#G&lI_5bkrGwhvj> z>-4Lu6Arcr7=30kRLJF)DLMdX>u_7BByEF@LBazjS6#|so_*Qp(3i98Y|4Wk zV~%pq-uC(xj11}o*ID*+rj~4uL=4g3|q;`(MJ_+|)@&7xVyTs&H!&swsW zvbgDJ0Qsg3cd~pFZa@BxTi)e$bolaXf|hZ2lTn%15-gCy85X74ySk#h-N1W`#NeC# zixkRHdYsB+rRLpUpONHhTk#UevGY54!1zt?I6L`*!DCc|pf;vZIHVGcxN-wD#Kp%a)oTCaFZ-lgc`h~t98CvZw@Ya!)Y69w9~3GD z!!=ZyR;g-3PZfi}4wPt00?21ihgRGWs^v84Nr!uqh?~RAi zU3kBDo6ew7fwuW!Q&T(G{3=fW1C5k2Bw8{QKt-ehn0#J6Mt$AI-e{c2O{VM2AiBqH z^_~41Bcqr4Mji4A`X9($B>8?jdD5B_NT~fE;$;kgh_PnI7lQV+gGCGb%JTtM=U4qR z;->*PKsteeP4)u{M5@8Q@F1eW$ZY&?8U4$n25a z7wv&XB)b69;dS^~L_TWe2IF0A{`|Yxf&`PtB01ffA8hUXit2iQN0#MDBdWuQKR!RB zv$P+9BzKVIXAhQ-wF zIlbMez`BtcUFXj_^CfNf0=HK*IV^S6^d;-JN}5lFUQ^A(kUOILo;uadzcHbsnY#UqVXkKJp=U1+WC%6?=ELBr@8G+0}HSR#b5RC`x0qqApb5_Kw3_`M&j! z{n6ONOrd9qGplpyeWw@9&3IEZcx-#OQ^g`~bcUarr+V`6BYbcsZ64kQ(F3pp-eP|4 z_Ckg<7LtMk2Zp~ha0jc7w=7_&R4w|RhHzdP@e$_2>be~0Padm zTLIsG!xs`+3x<%xTONtoqYI&H%h-qk&E1X5^bZu(@3mX=!Qg!n^K4!H&}MQQl*qRW zpbcXm*%eY9ncGL1P~C6-NVgH=G&zW~_AH4P21V*IB422t`=dG-w4(ZG&mR2NDP_+= zi+S_ezZmKd6#*Bh>cRzTu0b%fnt{`Fd-sVpea*t(DPB{zWAZzP)mWr>=wjNPzir2+ zTq3G(jQ8iQF)2E!_H7%%`Wf04ql!z9AgM8fJ2F9 zge1b;er}TOq^fZEg4*AUz%8-z$x4mf6g#s|tmeQdV&TfqKG|azT4{L;#OZ}`k#uH9HhKR9rq*etsvyE+b3&o28hsow?HO(gRdH6lxo;R@Ej`>e3lp` zp?g!!KWiA)r_~u4pkW-CB=AKj_x>H#e(0Ea*P=cuJmQPIi^Z5D{8sNaOMUSQ>TzGR z;k|*>1r}qz6^ExDN{8ykTY8uD_!&NvQ8&}L)+S1; z20`l|NzrY4KNAqGkR$N*=#n6TOi<_?L1K1J@4UN!bDti|N7sr~Dh=;q_7RVS-zPSO zfQJqMl(^+c75pk?W`SV*kKimeolaKiXZKwkX8tORAH46=!ALOMQQ@bmdlgpN-aq4Y zBW;G)iL@rCi9C9o$EsqsSc4=ib*XWC>}|J#&Tt)9uqvr{qgXE<@cAjYyuvwkO~2-W z-h2zu@}vv9)wFplEz*op&SThu@c@IFYxkt$@NfUTa8Ic6Xzit|R`ZB4Rhah&jv1B_Q?*9f+Ba{{R0EFstwAVOO~} zBB6|DOxkLdh*BS3Ah8dXRseyZsJTW7z54cz)0e<~|!eqZ29O*-3&G7c1=ks}PAqI8GO-G-lUOXLcr-?w`X6Wh5 zuCR;_FCz5kf)-<=JlD>gjTYyl>D{uKfH~llI(BcgvQx*GJv`{d@J9Fj_Ik$XM9evP z^1-_i9Z&9Y7s;v z_O&uF_Md$9c=2wkS=C5An4Byvk7C8P0F&Tl&j5^Rumqje+I{Q9Jyzkz;c|GLtlrku zP2A#38MFY?Q;%-wb`wjuT0g=Qs@Ce-gLFJ5eE&k>O5avF$jeOptZP{qRU~t`I8+YY zY?!H5=|l9+Y2`obT9T~FWx2Wn<()`3Q8`A6iMuVf`>#s!yMg{^h5I2Nepk2)0ME@N zctyAuyx|HT@1SM-olE!XzH^MJ3%E~$lCyB zrOPR_ko2>u1GAZ`!vuVMN0IYYzj}%jpbY)&r!eFxE7(svkTM0M!hE|%#)-Fndd@HjaK$A+90YIPz$RCf+K^TsFg*6J?J!|HzZ*RQJdF>K_1VyBMXOJH^ zZFNX>Oa&_T`uUUoGaM97O@#_KUrPZ5Y(1SH-|Lnq>A|U#-#{+A zvGC-3KG`NOod(0qLBX6)9XzaZqDuX}muA#o<4NFR{RLmDsU+xG!wVo|)I3))DXGu+LtmC`w5btF%l<;)735$%GcFL<JRMpoE1jf%Jv`A0GN%;fe#_n_Ig~DuG_DD z;Nz06v)t^hx#=1%nY2S3q9}uP67^@Lc28Cg-TdBLG{tGtm^tiT>DHQsaC(Xs+_jQ$ z%7~NbiS3Wvc?xVsi5K%njXZB_-HtdK%WBn15KDE>dJp+zL{|>gFiK3zUh*@&=H%>s zh$7FpP6hJI4_6ynoBRiR>$fVYXm*FZJXQ!_ulb^dAHL-f!phkGqPxx6DxS!lV8eJq zJQ@fOKv)M)du7Twh}ZwR=B|U+_Xdi?e*_9GC)}A+i-GNh@gv~9(-}CZTa(8K{D_Y`~trM?zG-A(D z;GZDx!+U^s9!^}ZJg1IIzj!B@Z8kh_Vtwtlo&mC%$8%43M)(J2XvKlz#zsc^lfZ%x zYC^y6iQ;Pg%VlPS`5sojoBUb%Heb5q$RJM@tL~H5=XSl7k*|v&#T@K^l=r_&ZL2Gc zcaIBe-l_w)zG84t<0tsgp4@#A$7+T6X#;PQIq!#}2ptH9)@tjbI8CQXn@v?o@3>2! zqD?v)#4m@X4I$uhOum^EroKjXyqsIzEJ&q`@3%+n-2aO8N&%SL;n;;Jb5t=`$Ne`e@Yf;*vwG%# z>qjd2Xj?Ed|A4_{iFC9*DVUWlHqwaUJ;_|%{YOgD^q+M4tZ{PC6tdMpfjNfBfsq~L z{j9ztdSW@2>G-`R1RkUhD|y!MNf5fJhYWC3c7hv&k{RHLH2IZB@hiJf>+&rs$6UJb zblHgvs3@FgUy!Y^Kn$iT2sL6Cb&u1m@-wmnw&YyUAWBTE`V+r{y8n^h?aLkOXLCHZ zqFedH`E}IW(EyUW^nm)e^E~(g`${$je939mJbg%s)BuiubvJa8xS$H=z^Fy z?@rq_FEML3U#GTkRg7vFWH!<%-LP-N^k=D`FGGRIwvlFLxb9EH{31}3G5_CRZh&s? z5G2s3uA84f2>~=IVF9lK?>!V)*@4uZR_fyl`dxv4oD4f=@g*iQSAAC!d=9XkjUqIr z<+WXb9Wo4MYYy`4<#S)SQ^jfHo+v2*gr76<$#RzTwD#)x46Nl!?(Or9q)D)P(0H%k zGK}U|&@g+VXBN1w^A!fg8|}UJ*^Rx~w4XLqG+Z{%ngB1@%!<8)pC$6c?eIx}Ar_wL z$iC}JEq=MDp6lXVPhB%qaQy3+8}I3s&eux!S8sOq{O=O^$j@TZzupI`^kI`ef(dzD zTt}ailvcQxNk`MAIAJm&kN&br{xt$s?_-cJ1NZ1ZH7-FPz#8Y$Q4Gt7*Hh@XC>fq> zSQdMhJ4`Q}M@6H+b$Bk5;cdnle zdApOTScM@vVe1<_#&6%3!e=^$o}h`mTSh#=KW6gxN;gxXLa$Sg(-nCUwWCc*fXG8i zbSUW&)tW_i7-;$1T+M8W6Vl&`8WhiP2B~l@5Qj#EKG2g1118_yP1p^kOV2`XO(tFz z!;Q3epWijPXY}D!=g-rg+C&)u!2X$Bi}Q0P(@V|Szka#p$bSsrfDWraE!^WW)1P&V zm~_QE=)%x1L;jJ5USRk{ULZ(HU4C-9`#|#mKq$UE^r|%VRMpGgVBf33tCH=anY>Ka z-nk6FSA8pHgtjC#havek+Fk9vhDbU(0!(Sn`Urb9R`98=m6*Ouz=Lhw;}@gh(Cz4q zl+MGV(#ye{CcB$-Mz@7lIo256p1(rv#zo!lEl$v zx;{XJ%r2C~n~vF!%Kh4H84xiKPvG7xsg!;qbz&0_%^_V}lY=<&$J7_Ary-8yID#Kz z1rHH|AY-|4%nk^{D)h&MwgrP|XZ}9~G`A4#qzAHY(Cg78aY_KwN@B^7fijr?Oo1wUkupr^lNGfK548~V%}4gKkVI~jC=>FYC+6mxnv7rMAJ=YPT?68DSY-R#|5K4HNdv6o$F zRCf#e6%16}tMVPc+G1r4x|a_)lejy@g}Yyr{&)yK4BU^0^eSYVVy^s$Fd0-oC~xDd z)C!C!Bw)2Z_Qn2NfTsx)qNH|9cgdM$wACoSde{ai{bZ@re9pvp2brpP+%vgxXa`rn zx^ny&;?x&qHKu>ir;{h-?h8Aib@yH*T^67*lT@yqFx+1e0Xj#*)p`exU5j4x>fDt)zP&X1==3C-(i}q&u_iP!cGH9*2E2truA2g{;qFY zIN3(OYtD28k7{7>v_agJig)ci8|fe|jnVZYkuGm`)W|!$72ENsJe;)0xDHQGJP5xz z&N6GZ+Kq?X@eYiBnjHV0-2VT^*PAY@s%2Z-*CL8oV67;KV2!9)Au5H66(E8N>ivHm zoO7+U^Str>I1&3qWJYc$Fh(z0d)lbM>EKr~8(X%~Lr>TGqLlL27u|d;bMr1>rS4p6 zD&H8h@t!EXWC88(9|yOeqVxh>cF`#AR2X3OqpS?c40sjXyzBqESfjaf?6}<)%-6tF zk~@CXH!%(d-%s?$CbFN@->>p(Bq8h)lI+et`eBqqzagDUk#;s*N3HUm!#JUQ(ckvR=ExU3w^e>?S_^wq6_}1s`*BO z1pWF8j#u@fB{FBBwIlZ$%qHIvwxFw^UyXH-5;HLEa3%f(md!=@3_JXvu?a%vK59P# z#-Gx6TM79KQU%KX;K{w-q?_8!T=3SJ5O`l%4gRB9kOE%hdkw@D$Vrj!@+WlNz_V#) zqNTij1abH1<}|ar`k_n?(oKBTPu$tUG9#60x52ivt}ARK3+cJpTDpHMY>HrM)FX|b z8=kgjMot~mMt|Wa*6(YzJEt!j`Dfktyf?O`$(p#t*cfbZ5AhnvWQa(ZQn5xJBuHJi z*r$qvV&IvMdL%mNRkPpCY%$WjwxJH=t?Y|Ft+sV(>3-kR*@T|_^E87-L8S=k@-u0$ zO2<7{we@oYY#4C7F{mehVixTHl!Uce++{PyO@; z_RP)pkta^vjE}<}Cp{lwpqRN_Uo~<#@g`PuORXPw>PT%ts2H+mY7cCbuRTD%dTCUk z4x&d`MYtrzAG;~$Dxj%?aOJLobDzJ@OO<8|xGjiOF95@?e|Hmg`aNJvHtt(G zJu+9qcHVoAy={+Rb$Y}lwd4tEN3{hbSoSHWx7}0mvT_cry6X-FJ~I5kTVx>}xoOxy ztnm^Aj~3r%R3$&yWf5n=8GI`TqBO=_x-rnr`*hbEZO&e#$aRiaI^JwOmd^?GdzHlB z7?aaerqQQ9X3zj8VQuze{x4Uk$R{z;|whoqKLd5IlQL(b16DlZ2Rt2%%mIc!O zEn(kHuRqAg#7-i{?gNRX`l!yiqNfN<4E`?PjZ7m=oJJSYLn^cE3nbUnWRgLKuWmRf zy2P4;Y&Z$4%e5VRj8J&+JwG{K3|6n@e^xLE%{C}KNg*o~`)9rMvn%}Xq8jAA##G)j ze|Lq|GD`8;_2-C(66R;MH4*@8B~O9wHi`h&CA9(3$%Yl@oN(ltv4S>>2@olGRt`~h zPwHd+xteJSb)Bhk`3Y}S-%px$*PnO0BYuj$K6*6rrCFB8!NAvg)w|I#e_{j)x6nzf zBdBje+4`y2sd=c~`p`gG0_?Xb5}Kvf*m2JZBH4K;dO*SYvzh-8vGo1K{h(2bdIv22A5M!1=fwB44ZhDs zr2cq-9(8@;N^D8-n=cOj8^ERA(wTZMK{Sg9SmtB!TfKBM)*yEWm`z-E;Xo-tAKR2? z@tu`49`;WpyBVVGMjx@*h^$Y(H(^0p;dG)e_yJMdn=V@%?18 zLvP7UXPc`%ldZDAMGE`$nQk}Ah18TZ;SL9CZLsFp+EHcZE#g%GsJMdz7CE3JKo=;^ zt$1`2pd{ScKf~wvdBTg&g}A%8!g_Gkp9gQ4nMZ$hAj!mQH|~&G*{%p5__D^qmz6(k zE0k1&@;b}H+87@hr3%-PEx|MZmDEpsM++As5&)7WEWbybPw+5$+qk%zj9`^;3)D^y%;ajBa|sMj&`y2QR~RGy@VFLRCuM(762RALg+KEJWZsi=$zRhx z$UlNYQ-yrPqy*AXH0bb(-KEZc0P{Tz8ouIN^ZQ3%dliM=Jq6*S6O}{!Tul@{rc-<@ zHc0UyetV*#k|Es_c4&9j<|-RPL*(Q$BAweooLnP%mMGNwOfPPPJ^`_X%FR#r#8@f_ z{!rN>1}vx`qj_c{{4CJz(2sFm0<=gy*ZH4O`g)i&`*7Z$0Q?HVT=jl%!K9=tCcgu8 z|0~P|`@_W~2Dw%`RJDY`i$JJ+l*5Ff&D-YcN@Vka0PFJ3aL;%BR<2eVh{Z_^0g9$J z6Bo56)jIifue~kx*vBTPZd`n#)!goictBPFQraw&oY))2)+_mLYP$raR!iP#oH=nY zs+6mtGJW@{?glt<$)(RIpe!PV=_x)vF6{2lS5#VZ>2SDJ(jk@(=$ZL!FqZ9r9xJwh zMdQE<*!RP*0{ef>D>b|uA-`urkesx6EyUQ)KBh#-09s&um)KAzrqHQ@h+tLl#l}M% z$L0EFr-~K`^baSb+C|TJF%=!Z%F)-;{|;j*A@;n{L&=KAs{(uesJ0H*b??4OW`;yC z;k-LGb3888Y~}c)c|m9EY7%pRJR5_Gmnjlj!I`Mi*Kd2TGtov^F*ioGqMGO<9icGJ)a%57iPJGL3M{v z@3uV_st{=Dqg*uCJQ#vq?fCl-Ks;6cuE40NgFJ7?++H1Nwp#*1O`uHl7SUzFJDJz? zIricSCGqw_mt1V%pzlk6Ces?$fgUhM|9zT~h;-1#TV5bmFr2S}?e*9eU43=_k@hrN z+$+4-oHad&0sSo}{j3p!Ru4Zs=)diHpeBP?^}ok_fa!*bY!{OC9`B=z-?41q{gs{y zoG#&}jt9oqD^(z~W5nm7V16NHgsRowJmecuQRDU)!_ZC+z zJJFT0TJ**6D{YCq^E1$j6rTz05_CE^&JEJxnRtz|4&f5A;-aXI(9wxs;OQXPDn$KZq zO}ZMD&fe)^%ZFzy>smQJS@op6+Ubb#9=iZ}IhiO9sm&Fdpko6lMqtKw>otlipB-Qs z>GKcB%gqO$UR;L+t6+M#3d!AJ%N~A~zZDp>&{8=RK;Tg;&y(yKpO0iA^PdU~au1`! zV&HkZ)xvw>t1Ow1r5$V~G0>_hcqbezIc1Hl!+Vl@2TKJl$o`O;n+U|1)2Oc2$RnHsIKH)Z=mommyt$v4(%9>?yA;Bwx6 zQ>+&E4R|U$WZT{kt&I9cZ^s%M>?BTUsZoC2;~?-k1C0L9X~T>s)30wnyB&%AY(J<( zl8?1^te9lp2UX_pPC2XgYf1tUnqbncc^Gks!Jn;S?1@SB6I*0l7w7Et4axY=Y5bk#`)9$Oek6o7SjT3{Mb!yn$b*CG@VVvs4;m;dLMy6bx zgyh!hT<+fS1o#2onoyE60u0_S`A0gm#QiwZ(`G-h&Xni9)V0t^W-C^@GMY)uPt${c z;NTD1aEo+Npwt2wjN6xBVp1&44-Kd7W@@Wysvp_y|JYyK!kEv2FbM0%?pWbbkHfH; z?{(OI5hM)g3|&YxbDF0bBP6`~va_hV+7D&y`2XIy{s>YIB&yhT$IFWTck_>#2zN*? z4ic}vb;h_I{Cc#tvd(-*yXpRAm=nfL+O7eQ6-ZO1O#Z+|YnMQP0TMLo(W?491#2S2ayPfUUmNo6zGdmzNF63;zGR=p^mE8RmjrM^Q zd=Fcic4;}|f1WD_G?!<$c?DC>u3+896^JO4a`E4DGT|vy!rC8nsS;5E`!KLG4I!W;|Ux#wJwD`M^)?~Ms*87JCk%FsAGV@ zP+MgBZ*G6qd;FmIs!CJJfu4eRFIWpdz=Bhp9w)izD;y_9As)4q z*luP+q53(SRj3Guf7g1!FR#|qek&2KovnPLb_*&vFYJwi$`lKY({4^3v_qz-y4F zAFwnzn9LZtN#RG?*Q1xGze=2Q9e-R;g_F+tVSo#zSs`Bq=jFX$C&%JJxePaHcOGnL zA&xsH6f%<~3|uUT&b50`rjV;YoURx|d<@tosK=cY7j9EZAG{6u1lkk|3m~G^mxbCU ztjutG_bP|6-4Kh?*oE!6C@nh{Q29)tD`Yre^kUMwEaXQr8yhl%dqTQnAoq3fHadPj z0?mA%g;5~I^tFRloZLDSp6S;?ah9F} zvt|4I>v6jH@p|cB;?w0e30Err@W+4YZ=CtKUij@A3^jRR2U=D&mlvkVcHwd$ZdV)N zNpm@$JgQXSG>0H?=SCMh&{3~T`(Do zFbBmjnv-k#oly)_!KS#I&3cYp>kij0dNb*N$^>gW8 z1A3SVQ6P{Q0I?=XuwysJW7K{e1WE&7l~(HkC)0zMXvLRG!^ESW?{YXy98%9+!TCJe zmloWvYoqB-IB?j#cW#Wi4#N@~Xhy2>!k4Yqynk__bdoiLWFNUiICiOReO(5md?god z*8{1W!Cs|8j`oD6&H6{tXC6c<@$*K$OB*0`gL6DV&MT)_+B(@kkIH8u`~p0LfdI4n zr(6RvNyu=jrssiA1)Mnl;DxSuv*Jd%AKc&LQ~hiHfd0X!4I(~$_@fP{xyMH19#1HJ z#(d1Cr`Y9lgzH~hGx;Q&z{pnN*0{B9half}NSO4TpfMI(P^Tlqwfu{v2TgVraQJeL*5r%>PJZoiP;;P zLfy4NB|8K{|_C4+XQLBD3ri@&;Ofs>DoD)1qoP%`jZ_sJ5UE-<^p){#= z_Zas}p6N}>LZ`o{vitVhP2J929Nyr=u<(i<=6Yw4`fS{RrXEhrkPV^=8-B2l@5J$= z4m)JjiH1EOsXXG#A8pH6*VS`1-uTgCC7YYatZG#Oi-1~gaRm7e&CN>vwT4G-8YeEU z3Y?|SZg39yKv7;8;W+Jgbwst{FF{zfJ>ZC-z`@g+NvsAp^QC0i{w#LP$EmtDjJ+a$ z&d#9Sq@Rxh^z@bM!a(K}fizSTn0k#i3n^nA&LZ!$2Lq@ZRhmJawyn?P#bCbJ1HodF zUea!M?X+{ptP1OdQ6zj{uQ@{ZciVCc@*Ry!d8fzyomzG`$*&qDFQsl3uJWAf;ZuUa zT{<5uhaUfDqFaq!!XjN=E*<%+PYP(sa(9(G{CleE#YMOS3?@)#e!q^5gP(cpDrunX zxT!{cv0ip&iv%l9Rg$Y9U(}J1u-jri)7qc-t4DGvi&`a$d%cBgBa8v2Z=t~Ry3ROi zY)0$-b=g-Gsv>ErwZd)d`6zgv30n#~kc4zqC%+wiv?VdzdlA7L0be-7a!1MxkjAe} z1kv``8`u*+zxq9G{Z2?{V?b!H!A>4xP3`Vu+by2@f!-y)AFgq|E6^uIuFxzecnnlm z*Q6pq{#af+`kNJcxozGnvr67v=-iq~VI|&<9AYn`^Nvs%LeRl@l>3~A=T~|6*c`^J zqR!(ls~*b7rqaT@?tswX=Oov43rAoKg zTK4~ThkxDhfmN(ROAWgBkKW~%S#^d0im3zvaL4UEQPd=(Cq%01Bsr$U&rMOWhJ^Do zEeH6ipaON##kK|0B`ISLmEP3rW*F<#yiFH}8*@^*OzKPpsw5TUN+#x>SDm-LStT~W zWsXjdtub(BL+_$X%|Yw<$4;Iyl@20rA1giAWuFKl-(FanzOH`CwAiuH4@P8%{}uCC z(@9_k{_T^1C=O`{#2D~+s&fgk;<@uSi~ogYUi8Eco!RviZB?TaBaKX7%#~(OFpyug zSbchMCmIB1Ox(u7MxaiePz;c zW!#-=s#zK<$5dVT%6h(P&O}-ASBpU@5DNlCyz|A9upO-h`ypR6llQ zKk28W42B@-ELU!6s&(5dx$X(%@SsIv{HbN80S8~kJsIo1j?6dvz<8Y z=fi3e)+_yIJ1RY>P&?llridPhw`32LFI_-D1mZ2M*mWtnPNWPVtsSIR8%crTI8p5@ zsIB)GOVpaxj>sNrz2Q-nmVB0eiX(uLvkF?ziWv66G_zSwWX&0gywM(zz8<=8C z((yzHR=U5i{IC2c@&Pgv3uB$#ivD{7<6a|G{44+Itg{azlX2ZGhRI){`JU1LeLi}~Y##j5UM=nqbD zwdw-)usXk}(+4CwQ>9a=u@1H}rFNqmId{dqO?=l-)<;Zv(dTgl0z5cuYY{q40#9i9 zT|+_aplCavn0l|0xg7!=q|?KBFLJ9?)2V~}l{fuhD;2G~JyFSqTo9t1P8?p;8HKY0FZ4soEOTGOX@r-+oFTgTB} z%NuXI@{T~KGf*go|W6`_O3mVyj_CGRyJSOeBZd#}F>IUl9dbfV`a2tqu+X>ApKUHwdwh+}c0)|A=+q z$pxyk95VZPeLd!=#UR_Fnmp0>kfesPcVyGMU5?FALt#HzfT8QN;yN2KRT;!;5aS#|+Dm6EEbauV z{%S|%OSUUQ_Z1(71s?$RMfh6>Gx0C2r6TCzbG2W~u@NR7Ojkt1xW!ao(6MMRxSjKBtw4A#hJb4DwX8BNd?9vQ&g9=G99JbEjFqHF$#`pUK-||-JvCVW_8}Mn`4`()zPn-~ z({w>BtQ(kLPC&0PW(m%$QXZ+^E-vQk!0j)jheQiJIB9Ej`}UWz#<*RWiWNdj9+JDg z-kcZl)4r@ukVeF_#Y>D zFa=2U!1lVs(4WYoAR!?DMyUbZWNT1Y(^=WKQkSAK`uK!ecoNngZ$hk(}sVpLn`@~ zPgW7I!Djlj$adFj>3(+M_Y%Ebw$G+Eq;XZ3IG8xw1tQ#G&8s}_LXZ4Ob{AY0t3Spq zIV^hu0o5b)$<$igniOhhi$EUs6!Rv{!9|{sAGd4l*(mkOQBIyeWUg6K2mP;=uPE_~;AYg^;E zW((+Yfw-Hs(=InCA;_X)UW&zG`AVtfL>^!Lv@cO%`F36{z5zYBk$Co=`Wi+7HR4}w z&g+x^-y1a8Dldc{U;JOELfSDJDTZF=p$QP19G*VCoS_?qvHBwO2*Em3vc9j?o#DWN z9>&Ga>s6XE30`D!b1X}MM4$_-!xLcX)%l|>RT-gh95l|BjDN@;V>Ss4NEie2Q4(R+Qz(o3#^Ow6xox1v)%ZcJ{83*UKVqbCjeYf$AK%;1a> z>coY*IX7js2q08q==|@_QN)4dm;Smd=GiR48TShy=9d08F(s_%tZc$y97e^H{ozsQ zPKr^0j!20-z3Lvb<6V0 zJ?q7;uk3>#1{MiLTa4Wqs35B6zZ%OgXXl_Dk?j30CTWR{wm}?h^T8)aefM%F5E_%{ z7LU8>S=E@VlG7KIL&tGnbyMaAPuDblu z24ZJWsW!wTrh2Ent)B)o2D?)o4@Rqt;58i2hrQPNGWI?lL&((YP?j$Pfp_l?efJoa zSbg^jb9s9iM2hLI#sXQ>AfOV~e}}BGt#lU1Ru2}rr`~3dbeW=E z=-}aB!cB|Z4Le`!vCY&=yt&93S>4>mM}nbOlI+eTJ=%%`l)btBAa>3wkft@Cbe<4Q zk~lT8vGP`ZPKq0Okj2{F%5IkihLU67kDK1HBSk95bL%i+Db%RKA(Cta%H2Jocmpwz)a|7MCx}v zJcf*gUW1Ou^U2ux%0NK2Ic}1>DAM@u657gm192j;6P(8M3jPUfzW*Zv`HuO791pI$ zm!AeQ;a%NQH=NF8<4-hfGC`10>^nN(5i{0&f6wjf(Mw_QfPeMJXWzQGX?5eDbKv+X zHx$&W-|FN;y`Ij-2n3q3&B0l!G6634Gx>X;Vi`=?h?#Ti2#pRzcijN)`T_I< zSY)nTaw`JSGjX4UJAzH_VCWd3>^IvcFVT37w*YRV>T&0Mk=4HKPV<#!ZWh%JJtuh| zNLfK6@@sF1@?(e%Z`(my#5%Jsw2Ub*wHTAhrIC`2 zMW#%9Yn86;GG#qORGv`TX+mMMJo{Q$Dm({NX_8~vrKX-??Aaa zJi~OnJ%#N+K!^!=Bww@1o#w(}i4c#hM{$@^uD6}>rbzj-_zIMCpr*r^@oX(d`8{N$ zKvvt@t*)GGZG#y*qP#K+*Sk09_p0p&E_=_5f(+r$DbGKxgNn$ZT^lX7N*maq$HZs% z=>$P}wNvO`w$I0`rU5ae-(0Rzr%lm+0WTeBr9bsKJru&lNF_EY5MW&Ym|XrPBm*eP z@m!$BSR_qF`*|2gwM#>R^wj2he)Kv%Pm9#-o`&30);FtdQ+Sdz^hhv34WCy_4I4&aXf&RD5npKP<4 zPLE*0bxwxTmX1^%PM48+jWLmZfw8BzF757UFHb?Bnsb~jXoaSta@Kh-55tE+u041&Q#tUEUYNn)o_zu& zGM63e zTE`=ox^Oizqfh1MDoRibuX)kBpJRdRd3!^hcc8S(tTF{edRGPVp8nafU#`9wE7_&r zUtM~Ae|1JoR={Rc*k;D;>Fh|>rP8n7ujwzfPI>35sRdf-4PqdzVPk@-f(QEcaVtG0 zB3MuCZUBUCZ#cT^!t5PV&;m|798am;x_R8gaO2z)8pOx5`yTlEKvdwz`|ok_I=Lv1 zOsHeY`3&mBCgc1fF;(SaDK=3$1PQW|5D5`{sE0*di6hgK)=dMF{Z(;0;1;z9AubW`c>krTOY*kFeg2`@dR}O%Ina0s#tr{TD6KB`(yGc+Z*-U+)jcV!>8Nh zU?@NVm&WxV zY}YemDzaXeD{yvI$QA;%^?%N;XuZ9bbz=|*l2U>sAZ5+g77%0zL)~Oz>GbwP4YuD< zhz#X%pk%I9$!dTwjls*XrQqg)0QDKbjhe?wB?na&XKaqlG!BBb=}yL@J-|MZ7mK}+ zI1cHQ-RnN@RE^wOius7aKjT&&pHpy&iA+Yya~L3L&;+}X7znaEyD%wqhp zATbgbvy9eQUFuVDQ`}z47IumDPLL4k*iB;{J3tul&l(V&p2slN?7;yBP`HXm_AnWs z3h6TONq`U$$*#p*T+O7dQJ&t`W3Q_?Prt$Ta2hhfRBXA*;wRb#zz8}zaIOCLmaUum zBlxYerf>wo7##F?d1uzsGCRr19ADVU3yit%*x6~-DQ9W2cWWD+dcBIw$7PuwzL?}b zC*_i{X<23DD6$(>(EYaVlXXxT_PgB)RdIKPcIDhAM78hkg%4vVPIIz!6s80C@T2;X zkF8eF`)C-K?PmzkpV*CUF;(^e)01N12Rp#Si%;TwwVYiGJ~nDj2@Z|H55}scpiK*E zH;W0ow!^C119bw#ZbJ3^(Z4!p2y2VmQheru_?L)4^&G{5K+FlyZt?>VsnUSOyDJD} zsW3Gy-ZN+2HDh09Hha3;x7Ag1S226XJM+V}nSP8{VRqiLT3q(}7Z%Bz&#c=qtU;X= z*Skjg>5{h^L_ZbBDAzccZqjbMkyhbh{xfRml?LbjV=vx`v)<%}&3C#Ib(Zta*XOv2 z9vYXdxv>iDp?ZOcn-Vehg^3sa=NJAQ*$3GBf@h?GU9)_Cs>L8R4@juvbhSAE#RirN za>iBotoJJ`k#l;fm3Ub<^4-34u!v26WL!98`gYO`zAsugGtcqDa@yRr zD;ylFOO8^TFqF7>NL-fZwyv=0V$6UsC5;S$ErOq&+ooqHV*MvTgXX#Wj~FOcApHLP z%0-AyvYa&zsiE9x?tl5(DSkeL$_^cYjH`7Gk0FM?Q-xpYAuzXlZU)F>6g2>UlXv=U zeqw$Q6FrB==kG*m3VcrW+=X|Gn1dd~VXrNoUZgpQ3G5{&sfv|@dO8|ZyNx^co^Eq? z>MXUDTRj0*NPNtET92CS1Mk6?r~P6D9Y-F`iAJ=UICZ>Kboh<@dU^0 z(m2KO%k1Bu6r_bPd0Opz+gKMUHiyEt(`ly94U--6N5iuK)w`hWYF>LNEYr{LqcAVu zAn#!!2x4k=1~GJ%PxeY1mQJeuTReWoMD|fD0c&aYVg|*kd|m82QR+DuRl)5iM%f4~ zaep3FtfQRUP30cR9?}WdT#_nr-$&d(?O3LPa*Gd@n$Gg&|EY_99+fo$LokN6Z+FQI z7Sn$o73~6&0{#-(&4hF(1@04L&dqxaxu{zVfV)PI&M=DS$FCx_UC(S`ki}~T^P=fp!U=3l%VE-=sz_7H02bZAI`>utG zU`eGWib@YFn@b|vc`gsdUDav)6g{XOqhM4v{%rFdgBl&I_y}5$XOOZII?NvZ?`e@h z@nh1lrbkL9(kn=#@nU2v4)ld%jm`@mTkdbJj$Rh$%O^}uNo?bi#mK$dSS7D3;zTk9 zMZIH(Tf@6TBjxMv%Qg?)eRn(s1n)R?%FfM02hm0`$37FCrw|7vNFGn?JHEPk{M8q0h!KJ!#g~2TYS5yJ!0#w88fcz)d+*Z=;~wk#avHyxh_g)OwW2?VnxnJRzO(mOkX$cnGROt(d~#Dlr(5Ds1X-4Hk{mHdEa#- z_3M1tk50DKd-=-UwHgubf#wX8|IAb1I22=ZrMr9oR=v?b8fP|-5nX%ZTuZfP7vSU0yJwu2K$0<`4-dt$bQ~ad)MFU zwNimpdJ)}r^**>}Dy2e*x3TYsw3SV7NARXyRt%rknO+erFEfox;P}iZDqYKK)e#P< zk;063kX}MvEfUZ*sk>*-zxT{>i!ZMDOLKZ+1~be(#rI7c=4Hx7x4YB80f`4XOBgj} zUlWqRi^_KrT^WzI!T;4F$p$XYD(+m`9>#+7`_!w|0UUr3Ge2a@kka6v!BSpsuhi{( z>jlC(H(Y4WK5iXYc3Kv)qm0iHT9fiP{49+Uf#xLhxK)k9tFYP54n5t<7YfvQcIelt zG_ae-Z!dENy?KAJNw!xRE$T?fv~4Gz57C6Enyl2c6?&BY7)28b8nn94`tpRN@-sBy zt}dtnvI?cRgV(pcdO(J35&{_CgJ*kZBf5X!OTQo9!Ttn;uL@Kit(cW7>oDIU>Bvu! zo$Vk$#P2`FNsTMvdRBQuI1z|mi>Cz!EMJCybMgDzuTHP?2UlU4B)A_H1`1Zkys_1q z8E-75@_Kk~dgrBA<-nM>kd}U90{;=CDFx-11a^E8m(%-pYebLc{EF|b<i6o7azT#TzPY^P9c(;1FWrA9>aiAS9zd2llu5 z$?Z&U3N@G$ZH$rU%s4eCMs~g2F4S`^`rM|qj-ze1<<)nLSs96=Wt*)^k)*?Pvoc)XG8(;g3nsfqY05 z{dqrG9?~AId6M$y#X)z(;=Z&+PZzNFWc1DF8FkF%vPo$NK5_RbsLgBYV0SKxu~qKI z${Ht?YSBE?PV^50;D%V)`790iXy(25qGlV6OT8#WeG;eCHxG+Gh`nufK9eidYt;FX zE!pYK++I76qf{!CBhnPv#aguO|ENDo5#-t2M5-a3c@S1#^WM<;i`&07^4q5u-Vv`b?QebfTNU;-etJHPI$!_-kOifmZXein}t$v?Ib)9~U8>mM>f7 zOd=Q7W?d=0NGM(*BjuC!5hpV{wwI{v2JZ#IKxP-$p3tg7mNx2 zDNx=`;rAw%yLjfBl^X#oW)Ilk5g*0MK0w>q zgA74wKw3L~)SZz{co4YuE>Ypa4gj_1UyoOkqL(hH;yK%NA&w#fKW?snwK8yD{jkt?~=_0*BstR2&lN zet6*Gu~9w>o-=55!0@(Gcs2=j5YknBc9K^jsqXF6jph2Mn_;(#zq4e-%Fxcx&$NgV zpd%XIEgO=?N75aERJAt+{=$vcfR7EIez}ov5q*4Xofe%@LRx(nskX@=lg zK+bURzj1aJ-P#n1sZP2RWck#7+SuYg`mcXU%HeR#oN_ZLGAzJ>=1}+&eHgqh?@Kq{ z(OQMkzM;S2X+r3pt*Tm>DHpQ%`AAXI=?I9ZPmcKD8AX(rsHB^*i}GJ zgR=0-4hEePGAzTtRcy{8-dGTfn$%a~)n*eGUOk74r$vyB?%Qcd;C*oxwA*skwfGSL zIZpj=IUaZkrb)RF9ky&Apy2atmu(hM_4ghr3PF6#D8sl`M-kCWBia2_Gad0E;%ava zBW-qCI-lV^K1{zgZkpnx1(<$_=5m=!L5V&?|FbMGLI1JuMV)P`A*;vih3*$Fa*XMh zR%UIF5eclEeL_-=qP^8Rus;%8w%lyDEfe1maSgz``*_5SMiCo2(qwWz6p}HZ{`U0s zqkm1C)nY%e5)NWknk*Ig)O=kE=`dRAu`>Htm5BxLtz!aOy9?YcY4f3|BYSSV-hF>q zl6TNPMvxas>t3VgWuxCzKC-<2LlOA>J_TSxFb()4Uc!W(f0vuI9G`c2X&Bv3bD8OW zK9=JaCXtuutfZDwAIc*#tIIRrRWkeTtU;&w?k8r=_uDRKAG`#X~vX~+025Ge&NUud=!_gXCOFM6AXTM?4$G3iM+vym8lN;k!M z>ilqVFBWcp2N=0XoGw4vW%ei|^HkSK4<-x7&}N0vEJ9Z2{eAT@EkDH*qUb6GCV9dC za%Y1y0DTa{A$tzgcYUw1@j|SnzJU0#Ns%I zjPCI;f3fgoZ`;mOH0nSN)6jweYWuBbcO)*DCs$D zu4?dl;)^gj0A;rP2{r+_{Mgf!{$Q4R$^SiJ9TR&@mIo-CPxH?DVa1JjwD!B;IbFVo zOTK%}f2)9{c!kQ>n%Cy3dKou{%UUw|n!QGFkTxUE=BhVS(u%vou-z>7PyIoLqU*^1 zQABF8xaOf}$mWqp^ z0?Jl2Ixu-iyVCR21psjSV%J;T(!o#NS#zT_4TG^Nc-QdYwS_=Y=2{#5>5@k#U-1~Jtj;zounFzV)q%k}l8UA+8OeL@^H z4O;}|VAS5CtCe-jdDj7%W8)QtfA)KdGHZ^O)Z}GpeiX`c&;{*w)F%%44r<$d(5?@x z?APtNt>HD={dC|3((PpiHXQK+hgXY$&5~!nf{p#4u1m;^(=7su&?ty4?6@F9L|f$e zpNc0kIx5tskj}fksG&_UN?56#t7mF)`&CZwiFH+NQQ`Qb*?nE_hLB_5e>ZrsL#;H- zn98F5Pm#VdvG{Da??VKyGl}-C83?vllcLbQr>|#K^q7p>sZV9DPff1d4x}*8(ZqfT z3Oxk1D%bUB$|Ji1s0WQ3XiML!++2B7(MlB0`GyuRwunkR&hQ$+Eq2s!^vmyPBop!T z8}=0ulI6>l(mkkJK)?c?a_|S;rl1m zuB4WMDk;3^qnCJUVj*6%__>0t3uv`Rb?_8$I6|A~39HO?rL~sD)NuVYU<(%`&#p4A zB1;kUG1Q<-l@wU2{m-{Ib_%fqRN$!t^U;iAo^^+@>|N~8-)1;?M+>IUFkwG(c%vKZ zo0S!e7=slu(S%{O9oR)z>#FQ`n*cPYh2zTVx&4JSa-N;p4Y;AJb-w9U%;U89;(DdA zzc$MJncplg88Sh&lZb(=!Giu`BF=cR2L(u=cxY6{gnqb@9n>%mlLG3twKM;sXA&H5 zsdncrr=T=P-q%HwpQ%vwO-mfRF1YzwlX`xoRg^01IG&%)Dl3S7&q#pFpU*4n_RruW z7Au{a>sN~i;ydnc+}-c|$9|o%^}~m+?R(owSO7r5cC*0%MF#MQp8-Dz!g%68>ZacT z|CAF?vrH33WvWL8}#nB1$#)JWDZkMa_kQ8#qLAdT`-DJt6)@Hn+2eh2^7v0yyt zUiy}=FN!9)IAftGf^Ju}3Fylv(QWpSX*1O|k)fOLqXF)P9=|%*LG!$P&f4tR0f|LA z1mNuh2)-3n=PQ)43@3uktFIpMQuAMBXs=epn$TIjNA+vTJCUUXBlIXrk6TsrUN-Dh zp9Q%zY;()I#^+6?gVSW)zVK+BSRdWk4ZmBaQ2ejdW9KM$Fux`+8oh(b7Ay&~iPrysy`#CvZsRzAC z(l`o{y)dHv;h>O9WrwLQ-5l-cx@AT(@C1^*W$jj)ZNWtyoWxq2B;91UONP&OXC7jG zbK4w9xpkIoMQZCw1Nyy1?#ETS##Vcfo5~4#isi40AYG?gN+jFi%bZyo%Kn=;7;IOS z$h*LWHN($BhuwYppH$)0NdN-~Ivh!aN%XW+1E3W4WW`w2pgN>u^1#$;;vF8CidgSq z1<3{Nlv4x`*zy*E z(@<>L`eU`|d~keyTt+#aNQ$c(-m)K=ORXag0JU%&@fAxpRW`f#07#ufpu(-mYw%!- zYDT;P(I)wkowu|+CJh8_hRx@XwE~^@iRUnLfa&Z2PCSCi5k z1n1&L&r(bHUaWq7wDOn3xtSbR_3^i`R@!TlUd(VNmUj-0#W`<<3SN0LNAjPTXmEUG zj2ztjuIXyXsHh9^RP;rv93lzt%36V5k625Go90#$i?@gW^OQcP}@i$^;eFyMGQ3h)@)KPM9r);i0Z9fNhUn8HTyQ(I2LcuIKKd<_0xSBR%uoJ zd8#~Sr-d(D`IHhY z=38EH=1LD9rs!VXi}41XZ%*fa&mvGw{yezzG+A@mNJZSYsf=ZEhwAfH!2C*`qvN>=7OBevgq9VtnCxHjJ&~i9gKQ_ za88gviyky-+X%F8fwH>=@>^Kpu*s017TAr%anDybW9ab-1hOrSl1!@UBma539Eid$ zAH7{}(jdLYJUD@1kZ^Xd+XjZqaHQfkHx7eerttOokR2N> zFX`2f@1cGQDgkinW%y6eB*@vwRgq_jW55tz^|sm1$DQPf_sB8*QPf@fVe6s6J^24SE+Y)Ta>8w`01$UkXnvI!rU zH$WMF<;|ZJJUTkQDp01bUZF<%JO%FuyU1O%@8r({Ndkj(d;D~vEub}31bYh8n7P}q zwN9~}=QOaw9$xE7>~Y4J3`LBRid<7Xsd#od3%bcecthDVZF%JaZ(#&SZ(4z)H#>_` z$9E4IH5^oYV$4SAR1tW>kpm7fTJX~7Qp-ei!3 zxp~KVzur9c+oitX`z%&j=f_;jiO#Jbm$dB8GvOQS3750v8P-c_9$GP8Pj~C;hseJL zahxx@VXKX^_P%8v1P_LEuyesoXQMnlxb)N!5d-R5Sw|s+InRm=c{JLBdy;Fj-W%}= zBJp?<$27Z~M}za^b!oj|Qe2V-H50!#;kq`W%o{{6s?%Qyl)A|NQ+b+Lj|5Uvwvx7n z!ql*O4uoy?Bt~q1>uAWhMogW>nTGAymy3pfuYodoxe|@WhT0%2^dsc^CCyrnE+4Ki4kS1A(V!!*hXy824DHxak zaTN{`ohXO2x9|BLfv}RS=)mn;Cg!lX)GhRuEnZOC(XBEg3iEO55^1F4q;lCs3;K0s z)VB1jbeZ(N`MDoihP*G=B@V*iq{1p)DIPd$XF*v_+wUJ9jU7wxzCx(Tbe}^fGl$~$ zy_4K!)U7NEVcK- z-#1Y$#GAncj>taeNJOj%JFoU?ez^4smLabW_v#*yz}T2y2ldKHp%DN1HpP5VfDOdS zCLj*>VzWr-e3Gx1af2E;7Ra~&&Y~;TA%Sm2~y6o29x-n=ISP{^YD^` zT5md^+!j^`UXOl}|EdPR1KVub+(!n}(lwsnMNc!2_4;08)6hUr<)>3BH|WAFa%&73 z#L_lOZQm{$2KM2av(YwR)M zOh&fnXd6v1?Pk97yRB+aL~PQ-;erI7m`CsXy=zQ@t>5+7xXwaRS)Z&U8!Hxl{_r0Y zFjJ^@In5U2(-rQK2g!2_Llevl)Uee^E3M9CF|Kra{&^o7kN=gZguPr;%>B2J zx&DA6!=^T?08Cmp)~laXdvkod+*}CD`PE{1j*G=4>S-{)w79r`e z!!p{Yk_A&4ort{Th1VLpUKRnvgn1DXR&(coxZkWJ5c}AeqINRK(fGhl0k*c<$Ykdu zH)tHCYl?a%X&8o9u|k61$Rhu+kDc;GCp&MyfqoEl>8e;M6%W?6PUKAvm+_>p-w*Nz zK?($YN;me=3BF7+Jr4$z{=pSlq-oCKx)og41NGD#qL78r%=IUp=Cy{L-Fw-_=_(6%=Lxzv%l`mT zs^SM@W#QG;j&*S6th4jgzOlxZkC;hhn6VyvTCu`1afGlM*B2Z$4$~zj3yrMaccELz z_|K;_Yl3;0I(_@=W5womX}q)CwQPH1KWERwy-MV>-&EVQ@H%!vv84~f)Rut*(}s+q$EYG}OOwCW)O zb*hP207-`$Tk!bSq(}k99*V09$px0r=72Pja+Un)R^OuO*%JD62uDz=2L~=E`4yC` z1}5KHBx5XpBj(ZN#BHf@J4)_p^TLoU>Q~61&$)^>XjS{?bo^DW)q@sk{p*63IM@xN z7QX@49^@1NO~E>tC|~Zn>{YAkr}DVQVH~$!dkWl`z83`@#p#eklhv^1B zSkA&KZk~ga@twKN;>}TAKxWhNhuzoL-*YnMeW{+XT=!`7_<9m;z%}$q|NKEdP@2(i z!rOg#a&`-%UDTUCM3F(;DrK(c12WH8WhgzdZ4vcer+HT90BD_=Q|dmB<+rU_u8te* z_iB!h`BQPKJ_%Q@)`IOB_8Z>p1}-w^2z{>IKI8Gu+t7xO2c|`JQt3wt5a zQd$?fiHL=@Uj$Ki|A|&>zZ8<6vfYrt8afz4|N6fZtl~!P#T)pobjyYd0R^03Y7BHh zCpaW2w84Lo_W8;J*v2f7_=p1s*t4FlleZ$#oB_3Q zm!oh~Qz_FqG~AU3Wn%w60GQ!ul8?lGw{n2}#1tHhZo8ZnZx2hs>y^f+03bmP1#`FK zH(pZf8z~X_^<}6-N-loK_evsru@^|=l6mT#+mT1HZ1;6at*?Q325yT@*=ln%on?V7VVt*{+;NMtJ zJ}}Ywt)`T{xZBBm1$8V?y(yvuz!82T7?F2HYoh;9z{3*r|1)^9D9u(xsrji#K?c}i znFlagR#5=emJ0goaic2!X7Z}+tJaK|n~#sd*L$Z7Xspjwg~{=L=Z!31K%di$#93{C z6D2e9lg%Yh1YD+8=~166utB*8Bo~5P7^TvF(kBf|Uq9{kkvH(vyXPaaSB4Ya>pVO2 zW>=MUyD@WCMZxr+dA^p}!e{if#j zy;)waib~}TV)-Zk zkkYC~t9~4xm!oA*#0L$j6mOLKk;&oj{G=j&AJA@?0$61j)(<%~T4am(S!cQ5T`iqU z44mhv+F$|YN8>r)!;8?X{rKG0cj~OPRf=VBtL%rcRIgVyVG!3z^pjk!f^n^jX*&g$lL1{jgfQwz&!c{TFzuPJ;v+*hRT|g zx(fhE;Ew7CWibj6{yG^e&+QTr8tiRfC*xT?+3`D%!+AMg?E?Xoc}IAv+;+|yF}hGG6(0W!5ZQWw){J2`u=+O;*%DsS(-@=Z&~-nrmg5?N)5^_2Q>-qe^fx zy5mxq(W_gQfD&1--9{Bet*526oo1Fh?8~x)M!OGvotz54Yj_ib>K>|nS#NVGYL3U7 zB`4V!k54cbsBNe&?{0@hvlwb`GokSg(r`k|xj!52#GQ=?D>k;(vPg>pmjiGd2HA%`ar1u296nx*MDsnwuw8r10C+FRIcYTd>uAfo-0 z&KHya+v(1r__D*l%IN9OU&m(P50fUrVlohN7aN!`ME=WprmXt+F8<1LACFFz}@KnX@fBmsbHuuQN;b|9;7ZRXO)M)259 z*4aD1VYAbJu1rr4Bz$4uon5QSf`3|m3*%C$sbbmcIKya1az}$6`nJ!ZA{Qr>FYQ1I~8m1nhbhF#WD45K)9;ivq;_j;^hxOZ#Lg zr9+1LIWH@Eq25`PzZFQ%><3^=%|7I7vAf^a^GEUd5*`_`ny}DVi{=h1dRF}`n~)a- z;>UE1P&%kRvOERGF1Zwcg<5DiMZDzawc&W@eD0SZk^t!?0d=k5nK#IJApYHT*w1Yr z3_wiKdFWvcz)`k*yHMfkjZu9AR=^)W_9Rs$vDX`-V?|i%U8l)e7gL{WoxCi43kV*n zUVdiu^zQn+LT|&-AQ=V3I2dLnFoQu?eQ{2QGTu3~l}&G-GF0va-4SboFWU`&3G3_h z`qCplc7%+aGt)u_>W1REzxV$e6H@>vo!GXltk=`IJO+bU>rx6CHUCAZx}zgNgk{oO?H z&r>Mv$fu=|Fh0oA0p7A-cJ}Ph>V(FSIc6;q9`b3U16ZDwhkAC_=fNG2 zYH0m?)e)=T&I{&M_6 zJni9ALUNmob{V}}iOXT8vd-!DsC_mycAF3QRJGD>P7}}aNbo;rgx6Y{uUj1(G74-> zOZBc16xAXK7ii4$Vw%7c)wnwn22az5_c4=C7r$JkvOP=R%Qp+Z2~6WZ>#FTZK))T zlf~2RDiy#_W!3lDL@l$EtEvET!e@#9N#_6Qjj3+eQat}>oTveK)uKr7w@p&D8P7ci z`@_sIssq@BsI=bX_&Dqo#9Bd?8kuE$g%3m-N}8GqP+L0v=YqwVn;E4&-z4WZKpxfG%WV``HpOSfGGCELIDDJqgq(G(2T%jDCr^qahxLU--ck6Hov8 zrsxErlFEGh4@cX-+ZWSN^OXattZMu^+CrHLG)fLoAM4}|XK^LUo@`12F-V8yH2s3m zoc7!_KRLcWKI|&bgF<+q5tqF65=F%Nrnx2tJToeCq=`Npe}49^g9XvSY`dL z0qplqTul%3_rDB|Y6&_bWXfnEQz|&F|8$jv8H>p+L&ZW=2n- zGtQwFq_X>ZrDon zjm0u%`?*l$uesISLtUKLO2e9i(!*IYmRZc}vHGtOusjcVKxF40kke(i=a>A+-(ON8 zwd+`3i5@J|2lN4;9I(>A_5%6lwa`?Q{g0_AYy&>D9@?7&vu+FuFR#p9#K_ZF$b91j ziDFh;N4=F(p#Z-utZIqEHk%1z@IyFbUhQnDVTH5YTWrM}?k1JYXmz^!u)s|0M9BOE z_j9{79~`b78&-|I7dZdcF$8etWeDkf8jIGMn1ph1+|gpJ^b?50NWD@bTvH>f$!(EK zUTU(d#xt+PK)h$m0|FZMMCCL6pN)PVg2)0Ex!ki?xMJQg34N;7?Hk_cw<-O7G`ol6 z^eI76G>SbBE&|*#Xw3TIR)i4w@=G zFv_(fSgorc?xD@-X+KYT1UwkmaTq;cxv|=5;=Xp!F<#lSHxP~Og(A)G22?QJc3#@Z z$OeFboCQ$F2x-&$OYGgvLF$B@z!~OCH zfUDpYj#NQ)J}48dRo2KP>9xnn)%{;WT4D|hQK8)17dMB&2PsSq{eCJ8gBSlgcNA?5 z_9ct*=~MHu0;m{m8wJI;=Lu{cb)prK_5~{9f|OEtNy{0q2vhGL#$w&~ou6Rh`htu) z-4<|U^iJvxOt4N%j0ZekO*c|=Ks5AS5RwqM(K@jUSX}z2n0ePM=B|@F*a_2&R;r2i zPZNUa4nzldU^W5~VpRlXrdNdV&oeIKpa$gisQU>0tq2IG2FQ483aEeo0u$jgw7UnS zd8*Z6k3)d1t4TR}FNUY|>N;+fAp})~qCvdfXhYFvLq8@bi5P&VHy>qitR4>ZG+pQksJ(Bj+*b>U;ephU)SMUUM2O zxzn{*>3^{%m_$EXp+Vq_TitFsrn_D}HAcQvo?Z4s(|y9uL*%X*e;LJ{1(fdLgzD;V z2CDNyx4zhZlt$a~X`~God8?~ zVFi8}wz^Uqex^axGeWXHwEO+9Qk*n-Z~B)v&8s$q@dRuZ_p)oS*K}VWE9WL`N)tg& zyX^mu{)c~WN@H>6pL_r9XJYV;G2s6>Bm-aWKYt1+F?$Ih;3omqAc|wQ> z&fQ*Vt=aEFqgq@^3oY)`_M_@-FcrtV>2o8Z3b#>4`76}ghY7#fX2Z&E^E(WOvi={! z-;99$u$ua2uAE|-$7>- z*{1*PhL_~~r0Vac{dodbg+_iF({f{8sa>IlCl1$Io^KtOfNoH5X%jq$G0(f zq)E{&4DR3bshP?o#+;w&rM-H)S5ED{2Kn7GUk)`HB*;Wq7-5!Qbvs>pWwCOg*WJp^ zIj1|Td|}mcZoAIb45pJbk*YSsB0fuYcmy%H zHl)PVi`c=Pm53he-23}Rysng9bG3XV3H%^;O}$Y1$qBw?oy+LxU=G{VC+{BGPis14 z9FeEva`JahsO9ucr<+eqUWmVDFXz)jo=hRv*Q6>be3IwHpNKm1RC%B^6)rC2RnxjI z=G28(9QRwn{kgD<{{E!hnCI~lx?_vNLf?)c^jm*J`vi4gf7T7CL|+9|^iD`BT&&!J z62&aY1R!W=z3lW<5KmvDNa?Lhb}0N7V*Rah3&5v6AICZ6gJm;;{4U=R?r% zU`n|}OR0X2IIq4QvH6VY^B^t?nn%_C)cj<3;Lm<0J1q#^Y3){JzkhX{t=NMM<4}l* zne6wyo`58nJ^bI71xNyeOhzq#fPIs9&2CUPHx>Tl%cYc3mvoWZ=jXw=xlr)CcO-Ip zuY0L*wOI2k#<=HI|0;3LOjLUp<=l$hV|f&U<+%y{eJSV2kZm$ptqu?>s{xhE@||5= zNVqnXN<{B17s_CM6L$tZvg*7TD+DFv*dL|Pk2V3pjP0yQqnD$p_IW=nkbr=H3^y6= zT!F@{Eua{GRU6U#c?WBfdg@+0W%TSl&phSh-&yy!%%+giRa?BVJ>b-*9^nz2@S+42vi zt^t39-EsEmHFvW)09PPd(ttoey0x$StC4)*^C z97{C%xVz9-z2ub#HoPg-eB5Yv&-}5sF6*>5+kNENWJ9r5>6PDL?gTj&*J%Z|$CRdi z*0IxmN}(9o6cbnYaHtM>xr}X9)Zp_+7wZRbCpb<{@$(`?JJ>EM^mev3eUyr^gojqf*rR#I%Y1QrRnqQP>2K{CXJM4QSUq!4l!QpkY7$*<+;-Q3_9o`|-(imB!3_ zflmz*4FculkUzmkXlkGG_cS{0vFD#PW5KO?U_3M1t&y5_C?X?>E$`z)@9o{u8+7a0D~tW^U-{kasWk{1)GkL9{arQ{_Yk>48xXCL z+0G;ZVgbMxHE%nqba=2e{x;ljvnk-B2DN3JIj8@QPxc)8?rd7627-cE5ynmjzd`@|)u#Q@Y;`i{i>srfcpNq_f;XI7x?(=hCG4Pq?p zQ%4|mfPH6EuJWKKq;LaA)0J&?P3I4VG@`z|(fk1|ML3u4Ev;^#vXqxk-Ypr_lYDAO z=J%h(t(rHaQH2B7S}Z%5GRMz2vtRCjzZQ0iS6WW$CruYJ zK1auAJr4!c?K9|wvn4oaxDLE4Gal44lR~H7@bxgaL{x0Kz6)_5%U3Kgn*+Gjxaj6P zB9&qC5X`5>_dc7;DH`Grc`Rhl^B{qvop*v)(C6f97Ts%{DFf3f7F)EOqXB48^$ES` zDJEifMk@uHZSL{VWD1|=>RUnD-DH@G zVL$Y^NGt?+yaijseE_-cx^b^vldZcmJhGt4$)2x37A#p7`6LQpT|&Rvt9Z|L z+UCTLJ?NJhNQdfbJ6h8WCZ|6LrSdBT@523 z0CwSV4yq-5u`WGbe^YUoj6?AueR4Hm^`vkf1brEd0u=;8}=HqJLoT#7}cctuWw5yB7;ssvw;ss|Zfln*^h$pk^09ALVcRZ?%2N2iML;`8gpzuKybBtQUF%b5DcKQWytZEY!Ok%G!daA*T`rE7lucMn^Ws#~h z9a=~;RwJi<%D?o-F6Nxs{sanqe&+;|tnT)~EW6d9!KJ!w9JZJ6kFW=8uqy$zL&RH) zQThmuD2ENYL3+iTole{b7nV+ABTJ9O-fMsUjJ<|6)@ z6EHRaENeEsXktNK@0N?EFH6boa_nXK`lvpD{N}P#!Eby9Y9g+V;k~=qlij$FQmPs| zocoC(hKw(Ub%E6_(fdGhzXvvz)&X1niP+ytK;S`r231XcMvQmaFhy5`@v1ZVnIcJY zUmD9$S+;7g68tzx91HwWJAYz(Ogd&COz!wIMM{&Rj2bvI<3KDx!Sb5XWuOQp^8b<7 z!+MP;=nlnE)H`*81x3%8VRm|Rp;A{pE}g%rjL>lp`@{#O1X)EgwTFWrU$+wFcWyP< zing3)e;+7}LDyXmNJQ1uo}DdauLcrHn=B6=03DQeq4h5G<4ApB^`S=)FrYuIU+To= z4qpV*cdUBY^34$egz`7_V(qUUt35j7`07|7`=n8HbLqQCW^~Rl!pd|xF%OdZ--7`> zmw9tB69z`>u{Mzi2DU&P>7tTbpgXg@FFvO{Z`S)1H2$Aw!@6bsZ9?&r<|5{Ij{d9B z_>5ZoNgx)R@sb9q@36O$YNA{0`Rij_EWOBV(KS?}<(G;>_l-*(@SJl_|AZErz4AA^ zhlK&R1s3wuhgZWgRE=7j(2W-ElrV^oWnC7q5|i2fPEzG)ksc@7O>L-2Un*x23?Uq1 z+3^vI*LNwl)i(a@5R---COSo1DvwUs_SBn(Vtx~<6bbG&k%!%99rlBdDZNR9UteGS zKOCts2O7r@kU9G8Q~|L2R(BwZ|4|?t9}}LVI(CMX-dbJc&Xp|iP^!J#3_YzA^V+n& znzUbKg(MTi;?Qc{!hDIeYdGp`wA+o##o$)J;jzB7fP&I&I>*EeS_EcipPlAR)k)F= z4qg(eBv_P2w0md_Bxp>s`;g5NJE1ALEIbMl5yhR4i9rntb>3E0^J+p1rU0N=hudU8 zEA8$79EMx}SSpK8A`CZ8A15)9Q`L>8KC|xi&f%?ZDxs*h)lNhGc)t3wxDFPs6o#Qg zhw9*6QvM}1{vZmn?hg-dCd!4d5i!-l2ZImYs%7Kd%as?F!IVqlcUu~<{%G43T0?SU zAYbV2{uH)_N*wDhx~V39WR8i|5>jN-gTIp&=&d0qs~hcc&;L-TV7U^5|9`E@V{^ft zy?2brz<^9)^;>adjgzu@bx$j<8VU&%2nj@Lz2AH37!Zq>D(9f*2Y)%z*F*1p_+vi_ zuA9~@j_lpX7p#z8H}Qh-MR9UU&h@^?cQ&Q}H6j*6e{wiFhXhhS?M=kL_;?mH?C5w@ zXnGqcjlQ24yIGhMSIygCITUW@rB-lRNV{4e76R(!kZ6W_MGR}_eAxDiaICIjA z_pY=G5ptLy*~K`)`>7tN$0OT6rYeOHIa6xZEVT#MjHdA~cxRd~toA_kEqL1Q&$apn zoh@PC5;E%&0JVChmVGE#-C|$FhXLaKa=Jm`7VRMU({dU|m*l6$(@A&*gw9202S+`8 z%h%lTRS?>fky&^H13EMQxi6$!GO zTVK;0uB+YWb$6pif5}Rf>^Qmk@Zis_ZI-t@Q(7WwU7ia%eh#TuG{-e2g{b5U`vfh#vh>D% zr_VgUapx!e+_lShDu)$&&n&#bO5(9`Sv>azvDl~QILt8 zF=(LjD)zvkD~Cy zI}J;1w!RC=*~2mi+}g4FgA1T1Tn}m74-CWy>X$e%!MB@&`4#GAW~dl+j{>rrSbTrS}fht()^0D=) zO-7A9;1(F6Vg33xIwsh~yX}E}uloU_LjMGp=C3W5o_8tS#`v%@gnF0IL5}?0>|IloxrnExp=!%;GWD!<%rxDJYnR_X| zdfv7icw4U}K8z#RBm0VfsFOI%QqXl0E@)vbxvA&N$xa;-K2R13B{{!VmGVVu?!eVP zO4qC_7>dS)TBcRK5RdJH+qr}Qu@9_Y=NIqnk%4d=>56!;`|>L-?F*awt`u13@?%?L z&ifzoKnj~L)Wej%Jm;)Y1(RCy!p^1AJ5(c@ky9^1`wz17F=PP~o3w?*E_5{^KO5nW zWfyk;6owu|SYbbRZyS_gbfN}jdO67C(m>ok4o2mL?ms3@1vXctMeVs3{l^EN>~p5hhO-`t@~9SK~28RAtE8y9ewRBpEm^Jet5HHLPuXd;IW zGzRK%cB^U2hiaaUAVX-d=ZE=7CRRBv{KGm^OdaVVnW{)rJ2kd^TN~n|T{W{#8@r#| zR%MqzC6~#3Bb9~U{KuUE3$VYN9|7_aPea010lk2Ch0L?z!@P!Pzvam3eM_Yd)aFlK z;#PP={PG_Vhp}zxe~WYM)jSuE9J5E5-juHTD_pJG_$Yho+0RFNv|}!TO2zvXpuE}* zsE0zPdi~2CLN8+qm@^XTQ6Mv<@+jj16q4KnI;vic&f0?mTW&tStvp;`K7q0&elI}! zadKs^HGYQb{d`icBI#)6ju8Zvs!b2q#e<(oZP@n?JAWq6^P$)V#axFKQ6gEq^H)z# zk6vyki|l=uEAE=`syu@6`;YUlfOGvB2+~J}Zd|B6fVIpZvOBlhc}fOk^F4n1$Cwdz zhCm`hzWDC59_FJOQ9ep6a+W2Xj*x&RY6}&@xTFa^+h!ufIY2*S!L)%wq~r#}iJ7V< zuj2n`P4x$JeMYWC2TW-a2k@F?Y?YME_VS0S)+ENv)FI zxs|@i>8u$juPjYAFHNN@v2*NS^X7bBSGU*In0abVS04jD6XM`rqj^93opln=K$NLo zb+^ZLHf(MHQeard0rzl|;K!xm9}5I*=CE5^rGq!OzpvVR%34gMj#vMjGr`vzmkxF% zHGcrh=H^iKdeW%1Ch$L;PJ57r$fVcri<^XBE004^Dm7q@_vp!X#P@mqxy)&bQhZ!L-!pjklON*EdZSnFnX9(9f}d!4w`*L}+<>y)xmS}Ghl z<-y+QNsW3*EGGz2PaIlR9VsUFW$cvwb`Q^Q?%Avk*RDR)644rWX`Qyu%i`6Zf$>Eq zA)x@YL;M1D)D;6<4&H2>G^jT{+D?`?D3@&>_CG32qq3Aom5eAHhrQg2jX$Moq|QZy zcsX{9^?qX@bM$hD5o#=`wgF{rOt630qh4L6KQDn-?LB~*A;kk;#Q(oARo&};n_&W< zjeLNr)80HFcAFu>FcsCCSb-bucXe!zubyv@tN|o2$LrsC{OA!rIex9x;Mpap{#B3x zh&-E1i|T`3=dvc*jys^%3>P5Prw?1Sw1(E*u5<7n)eXMQ954jl8Y3_W;^_Ccf@bps zZ47=oNoLIBQC!9-RiqQ-qYs#$=>m#?(LYTvQ!DeGnvX%qj@=T&bxKh(z&>D@q4vVA z-l7f1om)%(oh_Od&IxTe!p-YJ3R=dQyv6!esBaC)sTKOy_7 zXHfrO!u+EiN|TL9!JBrlm;sr)-X^-a0&uV02H;*P19`LCoG4^A6Tg9Z5B*!yu|vgi zou1U$G{T6ndfwT+E8Hz6%b)Qx|7JKmzo`Zx<|y&#P~v0$J=UPFfU_!IZK!sK>+1|E zx}kP5^3eFwriaKg|JK{sL{|ERFQmoAQ$nC+t-|!T8*Tn5Ws4&GYYjlprJQ=_f1P7# z0>IQkJC|9TY#aPO25Um*^6Hvp_aScx-zzx~2${Sq+d25En+2fn&-`;J<3d~lZgM}P zSM*Z~iwplQnL?#zMNpSXAKS>;lois8l?O9T#l*Z4SMgTSQx_$`L&UvumUNGs-lkgh zn%$1ROggBhy(_nGd2j%Uz#<@3>d=>?pa3|<$Fz}s+~@(`?= z&nIDpG7Wl9R`sP%+ZX&8mP;hJ6m)u5ND^*fH)Vn@80FVs?ARO7yzn$!^JcIloi0s- z8!Uj(o#OVvM2Fo>_;Qg4x?p9y+j+}52pOi3WVpH3*n(%uh~tH%dUHc0a+QWZr2U$UbZeY(rq^M@Z#5FQ}EqfKE3I-?s7ss zki54~d4od*3t+kU)DHun7R1VY>PEtI|1Ny^AD}ssCWXJ<||ABEzGoDYI{H(LCp-X)-i z$6xF1gG^7#1eHG@mO=LTe!N=C?Dqi2!<7df_MNZ=X;XfBbj+LtinSJ7-C~Ph*mc^! z-LUs;(%;t{0C8`huc`b!fc^gf7W^io!d5DMB(kaLU_OWsjv*zvi@#8Fx?!Jo8sp!Y z7O=-}Hi~=BDJ8^4V(id3hKb0Ia~zc0*9^v#pToAY$^>+R?|={fL|#wE9%jZ< zX7oSM64CkpD^z0QRLu z@Q9q$eoo;6F#RnLp?l*b&!%C)Dfi8YD`89+oqi=qa&mGHaf)3ZFd00;xn6(8;me+!^E$rNzHQC^w;?H8TYs zd;BHFV-kc)v6vjKFA_mH6%6>6Wr*YKevhnb1ezZ=)92_@Z`;kT24KHR5fYIbGkOw= zmOtx3R-{;g5FB+Mh!;!lPaf;+UI=@AZLsB_RHtV zSI{zDOkS5guca-y!;I=4db`c~wg{Bt8s?BY+~DH#>IID@b6%X;lfpxsoMHJQ6!=Z` zafk|(k$vY?9BWkChs8q?d;v38uhJDtSm(Q`%9E8Z*Yk^K{zW(T^Xcq8ZmkK;_{ zJ)Qv=BZIBB~IOqx=rA ze1gJ9SDJhbPHtqa@s}V(tqM#!-u5)PEVVNJx3?JJL^G4eU&{2JC9-#)DP}U;z@n-h zcGi$~7oQUO+w1lJv2|wKs$yHSeil#^>k8`v6%^~b0zm~8Q9;DwWw3{TA(;q@N{u;opWUxix&c)l2p=OJI;Qeq z2534?Q3rFCT11_+k;q~cSPJ2BH9-=u@BX|@-uaA`_jko!tw5E0oI_tM!`KJz+PuE` zQ>zk8)M>ETWEg@|`Savsr4WT+76MV<&Y{aj!Hqcf$fXhL9S+MZu|=MXE6n|<7OZOw z8+B7wfCJtRXSkw^N4JtP{eJZ zz)w%4KX`9c=Vkh_sRVjN>l_g&wo*$JxFLC|BC@?KM|;iY`tx2W$lTYa6+M<^Bjdhd zhE`K_x%0vY_y&rw< z=XfAXlfm9SxjGpI(ljZ8?DtGxDnjY?I2y^zr9lPZ zd%l*X?%j**^s)5!)Nr2##TZO+c6&W%nl{FXw#-csUMx64!X?{BO^ z=#MVQVAI1kqxgD$Jsn{mM|iS!6EBZ^E*k->z zDbn3kCcCd@9mmz=JG+aYp6B2_$NDD{CC;(U8NQojuR5rb$9lB8a$ZeM?D1WLa<ejCaZAS(~dt_>J=_Xd`Nn0*T9l|tfsM#JZq_ycGFKOX@~XCE2yjM zwI8>J{Kh?}GyYzSk68ZvCruiJTtOdvVO=N1w6N9Zu^}&yQXNP>BcrCta^d(&4#NMw zZ~o(tZlB-Zp~Eca-1J390R-~^Mvp(SxmT8<^N4DF!^2Yoz;kIXdS9ph^|Yq{uaVST z>gvrqY?y+1xTA6TTTz1gU?1`0>MBR4s(#6e_0dLbPopoqT;El{5o(Lqa#CDtzCys; zd_H}XE*y}HHudVuFI0lk><0%+v#V3H7HaS8c@x{;r{PUr7qG*OQ?Qn->CbpY^o*1G za+S<)w!TR;v`rm}`(5LD7&S(+m9`!MBop-HCx(V-)dg1syU-SsO#F_NUs)t=--`faOUg{OWfzxNGU7Mc0=)y zgI{h`KsC-g*Ip`QEOb3|?Q#yPLp{nwlw!tCmci5B0Lt>{g5Ev%>8SWg1%$ZoFkUtb)^7j@?ZZD<Nqqb z6FqgaOejw^kcW@C9m*`6=TDhML^#%9sf_e-XKf2gZo^4gZe;~o_4!6Ucq^027b>%$ zQXkjb#o?<}bUE6TjTerjcMrqQhDJsdn6}&=oej;Xo4bul8l8Ikiu20--f1OO@0ie; z>oeaDv(EhI9^31Noi*kDK~kBkc1!tV+^D2$yhZTs`*Mme__(Nm5XkEB&K z(#iydJh6({6LJ8mk8fepjkFO;=n+w1e~=|^dVjAR0ZqcwFTr5E8-mvHD8WrzH|pF( z*NoUx3AkC*vUejHYUMRNIdpV@KCIO)9XWOaZe3eLS&y&z&G$0e2&WH z5wE!f8`^pS3PXKYF>+ig76f~HjA&Xm6o`csxTLkY85$Z&+bft(8wI;L9hXB1yQ}RU zze@`ua63O&IULp-Iwt92D3?YQem;=Ot$m|KV|dbdQ6BA6$wR|m%}zrVb(UVV6yM=}f#$GV=i|hO zy^}U1#KvB%oVVogsGpKI0rfQ78Tv_>$ju+!MW$^%?a5F_RrM%w**xmFKTJ zBA2`-7%g$N(%Y@=|F2JnI7})}QAxGGFxn!$A3I|7uI8%2Yhrt)}Cki<8}U zUX2V_84UJ7^wnV2s#I=m$A_Za)MkaIo7zY6y46)J^NNCjiMsaw>=qUm0Vf-Jn~8gD zP+3Ps7A=uNxeQ1|sF8Kft4z8j5TQ%+Vz0inYC!4_@+U|Ehui)FTZHx0EbOZN zG19uP{&V?$CFj%dOj!4ymk}(xog=PvA~I!<;Yo?#AA?Dx5K0({sn}%4&sP{PDmHLt z<>u&j>vtL4MYc0ofpo#eZ+_$2&x-v%Pwo|eXlxu?x!-QtLNYT{sz#Yv5TV7cv@WYx zrYfYu#M_pxIx>s)eOJM2=m3)_Dv7A}M6)7EaxU2dJc&Os*P@eR!q;!#n`%+ZW9-MtR>PVH~ zN1Uy;u}xOCh~38V28+aOazYHNVTL{FD|#ar8}zGSV|9abC)_HfknAHR;0*9~U!!3PyB<)aym_A^;jH;rJGf^j$RF5j;8EN%9qMzQt@3$-KKz<=V5f7mFT2NvABiT4 zw@z?HPatWe+^&M{v0!1__oGDj__yC1(cSc39_hVXSIJHHC%4lMln3IKZp7A+VSIL+ z70Hc4Gm*u(#&jDUI0xe?^|iLYzM`{z?*nzV$QSB5eYV_ToE@g)LNuXxsp zqIh#MeFHRS%;ZMtrLOt;b!z-GdLFz-LaDso<5VbpUn}g=pQm%VuQKgBmH1}Ic1qdk z+Q+9_mtHG)%oQ@br74_}Oc?vkN^gFyGO8*bDF0HJ##z)3@F*1)rCDSCa*0< zOxT-T$Va0g4yJXqopJ2ZuS9o?Vf)5!a#v*c#wY*1(tG5~RxWEER#hca+~-GBY~&aD z*2#y39=T3B-PeL2u$u55VC$}jPE92rk+c|bl0y-?SXo?m2yE1dnD@<}Z zYYZQ-Q24bSqDfbtPN&hRyDM8`^J<^TU`Ah`^~%)dbvxI87Vn1)wwGUkWUTEL^-i5~ z)@X**Ml?HJoV+*g&!;mVhe|)e@6>g=37VsCb_N0(PYb>xak>}c%&8^4f~csg+C}W$ zyO3}js{Nc-lmNgo$u(xLvFskAgP&fheV~vYKj+#Us#2(!Fc&_l7s%1hZZSx-`E~UA zGJMQMXg$76@fg|}`vc40jlz?vj9>TbisA#g=6?7c4lff`^B*qjd7;IxDs##Tb=pKS zq;^)>>^mk!;bUESrzT5(9&c~#;K40)@*?fXyGaDnx(Vf zmye6axCt(UMOEx9rA&jn#%_C^v?AHIwRnf2kPbq;2EHX=yh85$E-uM=PM$|M-yL&3 z#5Vh~m|B$(q48bAo(77EGi~7o6m_fAwjy%iGn$|I2H9Bk<~#awRy#u6BZKZWd0Sm~8zKo!qhnh>}5$sw-j1=GNK0^UAk(j)LaMBDS;@1N+6qV0xGv zJBS$F$=_Y95HWrQurl!x!hz?t0@`f{(XBVrwGXp%KVn7Nn_QzXkL)d5O{;d0mi^_f zk420ag*#C3aP*An7*FNbP2MbU$8na?*K zPcj7=yALfEbU*-AM*$QtuASbnWnveWM~_Ilz?1Iiu!AQp+x??~6TZ`tsLw-SFJxkn zxFT8Yn>xQK#E2g{I1bi5x{dc7A`~Iry$JX7yUxXvb@zeKBM18x^t7F=i;K`prWnNT z%y@3|Hb2=(eq+odz@maH_sV^~qiaN2rfMI1cIFq0V3|AJzvMV~Ob$2W2hlb^3$+I- zS7!wlBJFz+dP)_db~C+7JdLs2eHnqxA$Z60Z_q3ycZqK2qXpePr@}kSuyi4b!8--` z_GwzV#nL|0(V}_2RWJSg!YPF}_q8v<;ujuCz#>7_{`|t}Tv_ygIzy?J*$zN3FT44S ztR#fO{yr|{t7ClQ6JU}J7`7WCX=1t-^C3=uo-iDlFx%c9d*D|=L~^CzyH$OM@n85% zU1<^1epp9On)HZKdZHxz+vXdUkvFWRRYsNR?`Y7s`~sM?V(pj^06?5dSo8m7F0uo;tDbtTMehU!P-0=*{Lo#{m_;$XWdo zZo+=5)-K(}5S*e$Jr0nF)B$C7D3-;{a$omtDmF2>3$$2pe24q}1GfKaMAiR&W7OQ+ zd)y7FAl#`)fOVsNxO*4F0t8?&T^&%#s(F_t6Zf^TcJrTCND}97hf#YF#7Iv* zX2I$yZmzM;4Tqlh%*(48oVC(Eo-}{R!WWL3p zH-@%UdfzHRLUaJIX(Z=UCk9`PrQ#M)Ppm}S$R)FLPy*by1FIHn-op9h{2q);#qbz% zv!+CD^nrR{r>c+H?S==`*7=Ug05#i+NlVMtrEv@Qqw7?YifsDb9LE+m{gK`vmPdXW zG&PB1U%QS$M`=bmTh@l#s&!lwqATgc2qZwDn#?yxeWI0X-j*&nHTnO4MdB2`B1Zvd zVnSQ*Henx;-N(|oXpX9CxxhTtVXgAw&)eakrNI51n=qx0`}m4fH|5+Z5z}A$yE7HF zdUG+J4!in-2%Ox_NTgDHzrrY&jBBdXdtn5T$%hzo&h@#!L>H_`T+eEgRHahj!__RS z-U(07e7^J7`NJOY3QFpU5j9uSR>?T0_=%D@E)&pM)=9T{whr5#UBFCvI~Z2dJgLd# zb2Aze7x4JQ)2K&Is=ds046<^t3~18SHy=2#+w&8xn1B^`)`?ZvawB~@6s-mH2pszG zzcjcr*mQk997tIbMVObsG@KP8mX?bP`(JzI2f2>n0Dun37eWi-d@XLDLLE^tY-1c=|n9RUsRuusJjo_ z{_zz2y)cG}!0(n1Ri^q_wW(kG%l6u zF}?rhqDr=r3<*gs%BAjDKxw?^QDw5S6^cHCiWx?lZl7Sk&Q+dEp?#O?gD)`WgJK4ph;KC$HUxw>`7 zZt-vJ-xpki?QFYg&nc8xi^4ehzQfAi8@Hy-W4*quH)e^Mpd=cW7?^ZlheBR}bxBX3 z?BmtDe~q2{qVs34e2lBPFTGN#??gP6YMhD^X|EGn?R1%0@%c4XD>sLySb-QTz+4O1#kHXG))-`kbiOxrSNUJqjjYwaBl6CX1=TY4)#!F z=ikk0V$<40H_UvO@m{C6d-OMdHmgfx&d&Qc^ilB78b^G=+O35zE!zj%$9BJZUsk!v z7qME^q6e+RrF-7$LQhWkr>?S_P2Vl)l0MX`zRSY0LKqmjFSCZY3B0xdk7mO)=wzuy z+eS*--nK*Kgv2%S*~(XLt8Rx&8Rgqt022pu;l%Q;?(v$jq?{N3YvPW4Y+-k$DYn;~&FPGRospRy(cv<-7m(kH~Kfs6F5UU;CtWyv9x=maWa_@@A&` zq)2#3YrXQTr{s#w7yPK1Tj-cyE1OV9o?^2qOb9j*v?lFr(0mnwH%(mk17Acq zVa%>R;9pN0@EUHPs5O-|APXrp3b?0pz{kch^VO|6Oeowa!D_o3hwNbt;$FD158%4f_IagGD864tKWYTDw|TQ)V5 z+u?1KlZ=PYPy=1OU{O{3AwxxUdZhyfzuj7^CDqo!qt_Tb41EFe!vckY#rs;nthtg8 ze?Rwse=P#dhuun`O&PtPyL#*`N8@ro=G)g^H^aKjZplAi3*d0Q7p!%jRCYq7 zz4zIL+$9e@akXFR6?eNzMh)(zHgMd8_+nESS;}_wCKHrr(ICZ$0+&|c+Q1t+-l`_$ z^H-mLueqSQfpUBmx^i1 zm#V|E->uU1{It6aCN}H)77xDgY9@}lUzjXr`Rj5FedRkmpOx`Rm_VjsMNFPU^j_rp z9oLqm8`FDr>k)XKZ4o=2_pGZ(pLB8#JNs6}G&;#)^?rN&bX68(h{9#2nYoy|7b_9F z@R%3wgcw;2g?OkZ+>5$r+WF5W*^HTP{dY$O4?&hn=j5SN{GL;rd;aJX$Xh2F8+TiE zxZ4Mzzuauy1OM10)bsaO;XIRFh2rzsNuxdL8)CfBaBt&FbJZzKGMZIrZ+%s}Weg-T znPd0S2!2BUBy`{Oa>jJ}nL6o2H|D8UTdR;=HJa1K4dA*tud^X#i-_;4o0cm@LaHu@ z8Z3A9kM~{ahMDM6YEegf71mox_ew(0N@Mek-PH_76vEWi*+%v!6=UG`U(= z9AHEJ!-;-;(?Or(A%zKIz|*g|p`rQP;nE?sp?9hEpiZu@-T{dfb3uUXw$tk`qt>mF zz5w=7{pdh@a6TUqDIRL<;XUrd#ec-!>su#@;de}uO00gV^WEz9SL-iUDlIQVyNqD- zk7&Scjlzm^Cmnq~@7E{2x*~2bF8)8C!BPc{0uIkZqSC~Q2(1pkmPWjFz3s?0sdlQ^ z37e;XygLJgTl9f^eCCLlA=J{NYJ2*uX8G`p?5EZNE3!~z_c@6fgFT}PnKx^9#8ozT zn?8mg)k^ul=zwogo(X3fmS645sDzmTR2-K!0j=9X%*ki$>4~>25>gvq)mRYXvfk8nwxZosQLfNPBBPx6U)x2gyo507Zqc_+IKN7cS@fa|59G+egLin@ z7dG2H>pQ_nF!M8?XHR`LrLBNri>!Qm{POlllU}1jLI_5+Nt${ExwR~;BasHM)&_Hd z!xf7s*m`r_a6tl%4~GEXAHBp#rneKFJU8=XkAkl_ zqX(LL6iJ*;xjI}fLlMgNXN&BQ`9`g1HG)msvTyj6Vn_0CjyD{AuUh*l_z3rLfY^r@ z2o{LlHsZq;$|!_>aNcX2{N4*>zDgC@J$E|{;*WXLk!N^9mB}GMC$1M}tH!?XfmKzF z%F`EIU7+LF_wF5hJkK3e%Si2>?bjo`Ps9hGc>EvJB7D!!>)P8BUWTq{qReujl z&0`{}Mg0lCeSk(To?Jdlp}#9pmibs5USP$p`oD(XX}+^0QnEk_7^sfAd+5Imq7HigOFUv!|k32X0}fY?ECOsc71o+8NL zy_bAP@8TeKRx*3lIQrZ89tCSl6Gx}zVHV~HII4l>5mD94@ZV02<2{J8`bXgkAZrF{g_51WLqA zNB_}pE|*btyfMM42uSb=B;`c!bM6%a>Z6Ilg+v^I@5wkQ)yV((VAK#!#qDH8dVVDL zivfFy+{p9er&>NMvFAr?W;1kemlwCZIV>@;^?}i{Km!bfMBWbwNI&>OJ5PMd`ertr z5AWYhV|xlRNRCg?BQ#xIZlUU@E3diG@y`;h6W@e#W7&1H(jI=xYL-HQz_ybKNuBOf zkF{HoCx9BUwn*fl1td`Iqwg4hkDposTG8jzkvPE^X5gZ*>=3Z{AGq-+)ky@cQiJf1%wXXcCG!Ydy!7OJO2HC9RzB(uwHFT<3| zZ*#R%G&^3!ePk*;S`2_%|7$w9w7>DvKU35Z+@-CAX3uX<8F#`(X*eb0kX4`d&q;W{ zZp#0h1la%eduK!eIbYmnF1O0Ec3+@_H`e0|U7MDY6Xa+H`+K$62sDE2O}-u(1aiLl z@6`i9!tUddvv$I>yhO#bPd+7;;^P<;&e)0DO=I?EMi?cO)(jHo<0a|KG2B5*z4O9O z&2c6EE=MXgbk#8`xe6WQ7kmgk21eHqL>5%CW9*j8Zfj)1{TCS9hr!xJ%`QVKKVCg7 z_40bo*X^rGB!jI?^;vk;)6zR?1)SJ@pPBWJU2n$tuL0bbpEkut0T&MFBA@NvxH~;KQ-T%Og_8!*>iGe7(?TXREmHb&Q=W%9)AT#=9iW|- zm&&qCNml%oUi{cDc&4ZqebmSY&O>b^U)ZKJ@Y#=~Q=>AQ;4$M!$-1xjvHJ1ld00YG ztl46+Pp__fXutOWT5jukFQYrXDD7jA6IG&I%-VTmz0gT_ZdR<{W!d^2@}#fr*p8sd zcIfmK);Z?}k)0rz!V?+lwB%p!AA&B*ljeEZ;cpgo%P z8}oX~H?p<4_G{N!30qS*>#>YbbN;BC82kcsOO)&3Qc{ynQiixJ?&$37B^ZKd`GCDx zM)=VNC7T`Zbp8D})-Q0%)1kS$MQffIc|2yjjbN?Ecf#-MNvrQo6l+zKQ#U>Ng`s7K z>!+BSSmz~Q;ud%Z9MG(ch(+)gJiOt&B_jH?ej9T&wDl6JAj-YX+?NhYw-!#wY4$7p40f5pSiP1}@MeBf2_yVK?YJcZXVxn1bm7 zpH<~_k+MCuG2jdL=eHVsWf47Vnji1uxUFhj8*Ku;DW0P8Rjk*D%8!Ff_m{qrMd6XX zh3s=iep9XEy*dx7*E#28>_SEWu;Zb#wd!e5_TrFyEEbgA!Sdu4C(WAn#PJ|LQP!?) zUUqe;DoXlUK?BIr@t%ogsxRkUv(X#vyuB&zt+64l6uYp(w?c4)B6pEa3|?#4M||$( zrTy8Z_#xei6+SdRBJ!|I-o8POxQ!l29Icr9ZcCiq2P3DPV&CDP2V-_v!5aOlT9L^x z%JNRFd43UEwXxN2>|cskfYAE#i6z3`KFO#~=c&J6#am$@0`X7sn_;yh47Y6VbVD0L zLmcOZvu-~;ZBLfhgV#^qnhA?sx3fSOK~o;Xv(bDVY%)eR3iVMSiA7uIi{mx3OavuW zfk7%56)KBqXLVx}aGbce)$}EFKD4@mJrcI{++bUs9bhgkezAPG?Y(U)SnlM6K%JsZ z^oTJ84*y6*o|x$lF5K?>4p$e$}yU9B!uKyt5j{iIO|3|*$L%) zp=m3+QHxjhiLB~h2PhNhEF7*MM}al%Y`<1`OI6*fJ;lg&nDdj;Mrh^nx8>5aor`#` zoXw@&5B`_Y;5LA(&G#O986%nD2|t@ zSRjT=k5#E&>UXqzcG_F>9apJHc{oHL-mm+Yk9+9>mxq+@ikYex&2q1?zQVq~8UZ8Q zR#qb5FX$u?CE1zdYmaSf5P`J*8dtmi$BmKr+&o%B^gUuZetgx*yOSJBU{t)s9W^UQ zyXUaK(%lEgOIna`={1CAHz5CmRsYNj3OK{pkZQI3~m@1G;0JBhD9pQ=&~bkNim>!0yn3p@)_InF3YB2X+|EAn}uTCN{JBrqN>s@ZmQW4 zyjUASM39zYYl(uXS!<9EIkyhHS^36LKV^K2_>*Y)W5wxt^qMZpBD&?c{?HsG0-`>; zeJA*Zzd|BaJ0+%J>3#5kfwd;qvaXC#$G^q<>2WyG!8df0i%*<`gIYfAApC%hyPhkE zzhRd5Tu$_o3$46?434uKN>ZL{HauBa$W8JzVlatTlB)v z0z~6<&b!({0&*mD_dvmLNk0X-;p}Ap%Ns(3b6UNXB$X*?ADhh^DmDCuqRA)@Nc4WmV8SQtOP!Zi0{{-|G%2MBVP zt@{gZcoHZ^d;LmBj71M~*Cw!ji;cO#-g1EoAR*+uJWZss;^iIr>)22dm2t#d$S#Y@ z{ZtQa4k@Ys7&L>Audi30MJF!^>B7g=GN!nS-BazSQ7XE`z>G`wm)%X!)lE)Vmd*ZU*t!IFQAR5q??}!w|I^ zE1Q#-)Jy*OTeF>Yn{)XDJs%(YyueMG5E1p#x3-{Qd!>w+gH9Gw` zf^Sjb_bz=6pvv(Y)YNb;7<(G)a6BeP6X_J<7e!s zE7dC2Lz7XhIgduMa>ZJsa+yqxOqEH;s(NtHSO5}~zN`AcpNOZ^<}@oR=$#7CBl>1Ikj9+?Gr=VuhA)amHPJjhzPLgpR=!s$$v*@w!WBu z=~}gOs3a%#(i3ZVTSmz2TJq9ETykQrUFeBl;Vy4X*TY5rXR^w!=PxbXsqWS45r!Ae zX4b$^wq8H&@V+uLT(A2fYpYKuBt4d#@4fQvuNEWjnEC5FF<9KIaZ8MA5APw{AE%iR z{{NdRJ`O$UD~xX!?1q2`jfB9d^5X8?z|f$6qyS0as~OkU1upeg3ALZx?%~D~J5b4K z!(7K}Xl|e19kWAqss>#>e`}SF_^ogG;=$`5C|0Y{$??qKr`C1y_2m7^Qx6bI7*vEt zeX9;T93_;~U+*r~zaG>jMuw9OvKDkslWLL{Ta|f)Geo74taH zr?$P*_S<^A_@;I|wj`;nufMUy9W22^B(sZMS}EidR)@5-+urGF{Ai@@v41!^FuiQz z`RRV;#&)1zd1u;QyWX}gJn3ht?Z}~^lfDTVx9s^L_=YpxZ3~|NuHfOo{{6C~6BVLT z)j7NW-INCG7-9E-RSgd3eYx*Q!DQR0=j8OC7L(_@KiF3G4u-0LF&4Jf2YjtQ$4FN* zwv;Y!;B$~!OWRlP^S>00dV~fh&2s}oFXkuL(z_F7_d4*zr#b>BEN$M``Hq`)evBUF zab;sw^W|)Ld?I18ZW9j&QA9|=x zKcD=t-PxPFRbIsHU2&iTtzKw*3VcE4?J@e{d>-b-3(#esb>oB&3##B($Zw_ra`%K@ z?wtP>NQ?Alox7%zTh#V86#QfCU>b`AD`{B<$vn&ra>|BRJs1r zm*_j%%463{(bf}tDb2gy*0q?l=ksJBER7~zUtod*1QSr~_3mqfXDWS*&EYHzw1F`} zV@J$0N8_eC0gSSCWv%IL`JZGP`%qh45 z;>FayM5b{##O?cVci&ao>D7I`9$cA5^gyIX!HF;JBYi)9jaI07PPRWv{Qy2nGczYQ zB`dh;t|nNpTnTKa?=tGApI=w%3ja)?KlxRcX@9Ho17LmPr9VT3i^|xH+TGTR{P{kSR4^26#$IwN;Jg&Nsgm~ZUMWb$S{_Bf znw8{~i%ED^o)QmQ-6S8z+aC;5m?Oy_U)(&*KUQ2yMO!xYjAHc3@r-kz9#(srCfFgr zG!@142@lz?pLD?#*0jQA1I&xOv>w&WNp0*Cg@$LwF(abJJS0IJ;tk+@U1&sJex%kP zb@PeVAz{f+OGy%j=`8F!9|PtUdg;RjI#@_eJfC}HDCSEefdZZON30+Io6^)TDU~GwEZClxS1?y&1ch$=T9ou zF%M_^YM7~6v!?qtX_5(b-s3NMtgg@Yd0da=9zh`}-)G$N&c+nbBu5c+dB^9~P`sSr zF6_0T51~Y?%A1ij`m8g(bk!Q<{vv@+0z& zOH-;zbc`^TNT!yhaVitKQd}7Kax16b6kbWtE(D;sJ(WoVuG!gDO&EyyCj?6#(4 z>&HDQ<=HOE0#7XrGg!y|$n4R8|BD*eSuGhZr|yALcf8%9pb{zW@bmj;F-gDjNPHKP zrNY;0!gsi&k;$^8Yj4_N&<{7b_ab_0Z~@*=t?8|ML0tGvvU6cfvTx1GHj6?w^~>F0BpWz^bU*HGY+2H2 zEDrVaA^jX~nQiq(Z*}kD#xGva_+-5@YF#zbLcXRNwoj>lElvSxoc`I&m4_ zR5$Gz7v*(?s1>^fI=U^G_ovu=9~Oodu=9nT#K6Fy+CVhi)8^|5lPjAqZhfZ%gM6IiXGNpfo zgt#t+V*4V%zW7_=C7MHAX8u^1f?FDTbh%bD#O(ri?J@zum9TS z&c4LYj?2{ZqaWKGd0kku+QQ)AOs$UE@~Cf`mr;8i&d8F8UczYg)!b%Si0wgtGx9X6 zHC!sT+*`FWlV&<@VWV6=D$Q`71u{Vatdr}HpJ_Jtwg||!H!1lFp>T;AmMRN<>P;_~ z&O1DYOh6~lE|mVjRWVp2+B%A3nTFZQ3oYDAh{$n*4x2=U-Lt!PGXD$%oSE!~-B zqK48_cI9Vf;hS$0aN^g@X2|z_s&|aL55vzhivSTp>9zwN%*uS;e?-*YCX*PD3heb0;q+9E3Z*MuF#UN0xoNxyQ}H4Otp z(uA;1>JNkJQy#V|7wc`O4emba&sQBd^TKN>p@5a0+Qf4Eln1HtF30&6zh<>@(m8p8 z5fpqncFm7je=lGT2j0-fghpa+5=iJQt2>-=NCeEhze=W7KJ&SH*wR*J=vDveZ^ys; zviW?!tsV92e_jB6fRdbw$L3BiDf=-&y08~vW{QP|fKeb_pWgP*_n=59IWp2gHK5$}Xo$@e?y|%fK`XK2&Juc?Xf{Nk%+! zcWZr`#Pm;WN!%|~6E@UKt8YGFHf&U>qKdc5rBXH|h`_WiFOG?DNHDxOQ9fw71Wh?>yzx^D5Gb#+= z@s5dsdW!l&K^$lQ$EHrmg-+20ylsF4G%!y)p9C7Qeau$cnwWp@Emer6mIt@IP;>MV zX!0v|)>>4B&8Au#Mx)Xp*j^dw_;M_O2{p3jFF@xZ9ak4JbGRdayX}|%toG@xnymA^UuCZth65#8y$XC zlAcUNL;j>@N+q#h!&q2DiWq{TQ7OR;CXdwK&z6oH)F+L>5noTXnAvyhlzz)MeAL;y z3jf!sL4GRIyw?()&?UmC$w=5+YY(3d{p-e>27zaFgA4|Ud8k^yNw8y^P47A^Rf}<3 z?=3_SfE9Z0#w2wwCK-iou~(}c{EJR6BrE?H_od=UnV zo$-S1J}}Z{3?qm>UWjd9jmY-14fa5oLbEmL@~8GfKUYeRDaQ}DvH5?IoXEj=pV9~C zRk-l_DGH9?1I)rc(*V3FmrdDfHB$O15jTy@^bQRGmSpfTcMCVPu=PgMSf$xtViv96 zlxBaE8-%DT;EGDSAeMyfakMypgxc|$*ytHZ3QqYL4a!Dr<98RoqZpDk3gTWW)vRCd z02rpC$FuZ|m-HL6oN@CMSGI1P(7tURVG1s^=u)YN8Ft#^flZ6F4@4Atr5q#c zk6&UH4Yz#h%Vj*}c=2p}(v4g`ckFDFt2z93!txYskp9+GdH1C3ejQd)L2C-lX^t%e zt?2pgAhT+A36#A0WNQAJWM)AvYh>l$V>XZ}^bhs@0||u&zrdQvoWn?@l= zQOSFU;fCeA+w?TVk}qlgWH>wvNN+eeq&d}XOS>rOZ*XU-&&8ev^i7|6zt8rbI1q!d-l zF*zeSr-NM~#E{DVcr0BN?lS`@cQ&T8OPSZF9b$?7ej!;I@2g0Yv%V5aVyF4jHlXML zo>o#|A}=L32U!J;_pG_t*B%yi@sxfJ5gITP!riovshz7myBT7rAW+w29GZ0re~n6R zpYc6_sTh9psFcl3n{tIag~KnOXXA@UIji!q@;4)v#SD3=D%lxC6ka@-+HY-?7}7MU<2 z?!(q{sK3}jEhZr()T-+D^gYj<)o@3aAlo;GLoUTR{Ba40zKX z09WhOVm21FBd+ML*6l|6>f<{btX!?d(q{3}PzBTM(qB02mPcv)8f4-AXmtW1KcK+w^2&QAG`1ff{X7%2eNW<x&X!#k1T#KtyOJh$Etn1;m2-)ZXq!c3S}3g?$T zS;E(HUiJImR_QR^A-iT{Tvh(e9aaFYX|oozh|KOooRSc8SW1Eu2-%uB8t$Qb{B|wZQ<; zGPB-UYxN9vx#K%FDz8J^$-eZTn0Y+fl@f`KFSKc%Cs_%x0Cz1=b=! zw9V-y98P1Gm3oNn`f{r$i6U^a--OI`Ka*;E{4OQrNjcBw9vl&a&a@7bhVO?DXEoifZBmD~hH1CXDK)|YgVkK}t4ObdV1WnUzX zgNA1~W9WExem;v!LGZ1xE}ddyy1?9+T|n$p>t&43I1ZLS}kqKK4Gh!!@;N#R{wZd2r@e#)Wv@ z9+As>gV*8u0by;PT1T1zLYfkKp?w$vs1($NT7~O;rG0W?rJ!)pO0$yO#$W-8`SF=tV{UHl z$IkyR$=oemcs!+Rg7os&TJNwcPA}z`WLx{p$LP3dUenA%-r>U znVPEZn(2#%^W1ywwU5>4)fS5|`72PD8{aYM&HBE5ZYP~?&pv%_+afxKfSLDFHtx4~ zDV8#?fnTEtzMMk?mpmD9EPN)++Av z5s8P;=|;@F72My9U^#95x_o0!x`E4Gdsus4VxttgQGdwsu{*0~NAf2%B+}mojyNs$ zh2IAF-p6$XM0@mAe4Gr+bwU2lwtDB{Gh!s&)AV4n~49 zh#fOy6@fpwa{1W6Vr{3>xz(sE{Bnmw<>fujPLR?T)x39o%7spRe56vOg~^1olsl(j z`P=dWJX5Tm=OW5w%seyrpP@e=b%kit?XDndi$BP3_`|H3Bm$Z6vaQySU=3CK@O6W3I{q_P#k=ODWUa_tbQv zt9FAt7KN_m5*10mZdSR-@SnsIgiWwm5Nrlw-J?a^AEj%wiKURcVZe@A5N9klce&MK z0J77_tBi*!^mx4x4Ku(@BurYp!p!?*_ce64h>bgVznU5)2)9GmL$vT6NzL4fO=jP< zYbA2XTnKD1^&tKM-&+HDr>?0`k8SMGMlnDVSC=Kp!CpW1Sig&IZQ$dTH zm_qAOlK}p{BtFA%?rRnJ;09lb0n1?p?`L^T3VyAJbJ8|9L|&TIp(H03cJK)>Zm#w2 zj|}x7@M&3|vCXh3gO3&O!H!CZk1kS^$2&Pb8ID8a`mxQfL&?Z=v&G*2<_)c||7>}y z2Zh_X6^B;39l}18-QtppFRl1BQ8vgTD_7q0+P+zRY0=Hn$rb@bqLCu=vm?VVw|0UE z_9BJ62Vc=+=Z}lMOR&BGyU`Z}1sJ>unrZpNJgcrP^Vg6wmVf&|St~!kgPQ4t&DHPm zS=0v-yXuoOrIDG0SzNA2`<(W_M&%u!TY+36UjXMna0p5b4c1O|9TxUMEwxRTNNrBG zSLp!TmCFEq1&X5&kXUcsBZjll%E*8E_(k@&Ns`KM!qN0EwT$IkxtftDQI#%BACLFZ zk+W5;vEX(6Al zGxJ~-FRt&-IUe-|ph z&VHA}qJxnC$KDL}E({wF@KeGs;l}y zKl;jKQgT5`VTg|^)&OX0*|$ds`!Mws$0*hDDDsUBL3ZLI1+qzo0Q^UoAXIH}ClJ?g zYO=oMflt+n^5fKqe#~W){N_|J)s}~mbwhTLdT|L}*`EZU7}L$il3gS}*8_oMwcZ=t zg2%l@U7Zx^@%iA}|NEU_#`Gum$>XCJgk)e)#(s9c^Q+m5Cahp}9ZC2mQ{u~){MzhL z%=;^Uk)mKJQour*jE)-sipO_sZqF_fH#%IfZnq+j_4G9m>8k8`Db{TSY=j|4h&iq( zM?)4?C9!yqzwrF|J_0cl4Q_Y4d2YNt&mDwBQuTbxgOOMFK$Xpwi}AH`lyjR^PCy1M zi|`N<&h~1&+HqS*?<@qox1~pf=2s;sJ&Vlx(;TsfU@EMC#Q@gTVqz`X4c-gwLwvm( zxp-Z1A$Rj`hko4RiLlR?y|$0T?eW(4W*z>9$~m9>eIW;!0${X6;&YywSc7O0@Riur zTqt#xf4lu#U69^473^l86scCc>o`jFMrYVkg)jZw^76%|Zu4}jvb_RW>0C2=_*Bwx zyGawlg}kfWY~GPAlF1&>);@6;BkJZzO)py0FA*N%%MlS&wfswgcyu4%F9vXOi)q>{DH5)>xnY34`%D3_rc0EeYYGd*!A zmz|(o>e?4|;BDC_6^;k{=!?_cKh(qgvqq*qMsKIMXkCmE%S5ocw)E~_cOHNPb>Z9P zVj&_U{h(S?ajen9z*+puo!4FXvOZe&r51FH56Q}BZugBKjCb~U!kg!5SY$KR{sc5n zh1?X^+>DB3qDge?x8)Zc^dWqE^Hxi~Ja(6ANj!%iKKb0Awk>JD$XxtbH`=_XhYeE0 zgU9crA#=1-i%6BiN;jCLCJdG||MLsBj}eo0BfY`SjBgi0Qs@8+AzM24BFvUAIm{LP znbSME$Y&D-&>x$?Z_Kxk*(tZZ!^yM{iDU{s(=ASM_u(EgiaE>=S=V+>MkTABwB=;a zGDr!iJ&bH+_O47bvB=l$J?W|w;&Y1JHt%=#K{B0_zp)JkOF*M#>IDJfS*d=@uGhg` z#IN1Uj|)b2TW{4{TEAG{Tt#eXL=GJPBJ zz9Mly>E+S-a4O`A(yl`tdTqa}&?Gnlf)7cBlV2q)nH>dSnnOVQtaq6hnOeI3g>1u3 zQSw2bqHR9sm4c5#w6f_MLFZ*;g3opdM1_xi;;H}1f5?qbUtzYR%x*uh5O7YbeK;Gf z(vP$8bbPF+yFTn3ZrlLY|DryKyS>-^VCa;Spv3e~>uiclZ@RB_ zVjeQcyPTTFSCz%+!*E|?{? zT3EUY9Nn+Z_F0TaJN=PecD4P=oy^Ax%y}Mr`Bi*_{gZQ-DF2ktZZ~AxVk6yD(oEjP zNu^f2R%74zinYB=pixdi?weH)dOeKkMrQoW5J~NXnM_@8$@P@9IpB=zf8YRHNQkdZ17C4U&1K$lk9Tr7p?&1pPOpE))#4xs#jvI zZv1`FCVu0N6DS=5p^MPI*(F}vj)2QJN>g| zlpS?8FoX|B*xKUS@Ks#(pHrDhtJvB8s!$XDlP;V9y$O~wt324|f%uNhD|ucU*GLu~ zqH-g1h{fob8>Km~2%tdA$G5so-coN1UaE< z0(n^RWMa3nkZiLl1ji6K~4V zjbn_>=Z(Ov4^bM@f&405GcrTCBFqJokqb*5GIlv1>>^nrw0mdLzM?NDF!$|6Bz)PV zFe$M4`Z$;4^S2}@PNV$R@~%HEX1O5ee<*;`swPa8`u3hc!ysKM!y;8jA#>cH9oYhS)c-~Ix2nX1w_ex2UH;I*e`-n2wTKPON zIuT7G3#bLqwtP%}9~P^t=j~yftHb(Kr)k5=#_#x)9x;4Ms)))b=uhr8!^XFDWOCO) z{ks6$YwTZvVJ$L`2!b`x5!kj(?4q*f2 zboCmX`u(BhMg*lYDOqaD4IPe{;rS+*80Hc+gfWr z;Q_2{IJ0*?)I~F2GXD6ynv=ftD|ZjcF{T-I@8=hjzG6sLuuZo)xhH+$l0Gvl6FYnI z+qi6}@O{OW9#B-{29-g*S!y5Ovz1aD1RMCgmao+zgdOMSL>fec)%(>79x6Rfc;j)7 z#g%n&v(F3GbP@!(;Bo1jp7R-oP|yLw?sstZ{S15R;?5-_uvD8CXo9KFg8ZH0`O^FsaI^O6v3Zs z-mqNIGmp?g+P1$p)n~Ar_Z`K^`b z(U^t>UustUah#-$cP?}2@>`6NhjWY-b5l%JC$rDWX!HZwT)}1B=1I@@^5ZhA%FRoD zWLLtME4OUUI4-#`v~YB7}x#Lt(bUqo61-2#G=V{7kM7|c*c78#ugP7 z+_gf>NTyd0ZzZGWhm6d1;QIqNQVGQ_CvSQv`n@vPrswyHx`Yv+AXEQB|26^Wgc=n9PZbVfb&UO?i8AA+&Rxbfdx+rUW-@-H8pSqZ zuO{oS6)AU#9_;UIqTP%=B?6OEN%<^`=#YZw88f-8iDOx#T3~%HD8NF&sX8R zqyrdQcFA?6qz{BtKD{Dh)kS6}oSkj#>ipg>M`jUlqcwHr^faCZkxDtqp`3LZVKWQ_ z3M}1jhxeg{tuO9jdQBhu8rmc9xw@s_Y>L+hiML7$O$B8 zIi8z`LTP_}@2i%@$5fv+f+-=`%#PeGnDG0F6$tom9a)HkFuQ#%x~WpC{f%nQ3z2^A zFW)0jJ=oeHbUO1MaL+Zs1Kol55&q8Th+PF9w{`M+FA%13OHA$phCav3Rn_afRInZCo|_|jLw`32^DMDlcoV#@Dv2wh^%O%@Dm}Ia3_B}3?K@& z5L)&b&j0foZ_Y!A_fyQR%jfcQtEdDTwNC_myn|@KI((BWDk{(Uw79OQk7jnv)y_R0 z1a;rYT;kyzSbj3wb-gv6Pe1*3(fh=1r@%HYcE62jcowNo4hB>1e8^!xW0ks-NQ!#7 z66b^GLqFPh8}1JvE7Y8fTlcM#IASPXoc>w|`A30xVJ;aRPTPzCOB8GpydN|E@Ayf- zf?$&m(CNGt2s+=Gjeqz(0^>yhWTcO!0m)P9F$epCaur97rAs{gAl!nI@qp5#|rN+NVBSGu^l*xJ0h zn=%%)W%?(Gs&=Vo)|&zijF9K7eqC>RL`9osV%Z2KWPSR?{_tdYH-D<^LinnFEqSD? zwQu;<3M>cv)a{oj5~RzIYHBh`Tr(+C-3Mgfaeq2S1I6en&~5vbSOKChOc4Hb+DtV0j6`91KVC?5DwXY0=1)*AOOh?FGP%)PgQurFp@x_-3qZNv<))7;D^JCK@XbBq2o+>UHj`4yXxC=KNf(Dm<&n} zD1laO<8goIY~DNsUjC_!{VtqYv>jZ{g#j8PSPu`@yIZkYAhH;;3 z2Y$A7+}1u3TVEsBK_`8~sQPw83TM(^y>+{L;YD$U{Rnx$8I`yc&)gExp`RUPWKA3! zBPSGw)3=<`@OD&$@7sYnM7kl)gMBl;&E_|{wanJPzcAuwrD{0(jJ;#2XY`G+`Ly-% z6t%vYaGP=Tpi)?=TyMK4X$T0YZa00GX=M8B!-ex#2J8PdU)NX`KKUH_@n`J}1#LR# zYSDsXlnM*$bMD6vh~fS5#~t!91|WI&K3vmcu~qvdv;e~2rGHken7CLzw7tpr z5_=k|!w`4&bXc(~Y%A5UY_T6Ph!c4fw>EkCaA4V((poq)+}_c(^)ibHJW5t#$8$D# zWX#_7Qz=I-PAPSy-#bWZ2FT<4oa z5u(B9Dgxbj_}H?&p3q$?>{+7K?DUa^ZnQ$GAZg2!^3S%O zdY-FQm<1NCXkXZ;$eYOUwX?#ZI{TgOgV@b`0FmZPSRk?PEo5Ssp+Y7vw@~j@PJO9e zNC@=DQ9U0gx$ZprI`yBo4)6mIXNIc=jH#1s1C*_TUGgn#PLB1@33s+dPO2W)>6u#U z^)EudyGR?wiSWy;%XSmLkCJ%rXY@TuPY;3zz~5(~_xeRuwq8AB{P|c+=c$#oAA1qm zqstwyq>dq;osI`K0pw_`>g;04#0+;-P+e39LTyBdOEOn!`syus3ntvJb($i{UB?aU z^g&Gy9k7<%ew|qc?fq_v&Y#2Qbvm+lO%kB5Idw5 zmGwR0+nSclxjJ1JnvdNJt)^T=MlY2&n1XfhjMPgBC|JeEPsF?+oJZ~nlhz>?RFzfm zvzdqtE99P_D;V`)%)NEK&=8>9)A*x3??_RFv&wt3+~(xvY1gULjH@@(lHNYb_0RD` zzP+2Bsu4b)rS|E&XX_3XKEsM*$K7QjaV~k97_gp5bsUcJ?P76J=e&^rx?-GBDz|v* zWoImMq0#-U_MLb9rFabgChcKYeUjWQ+0O3|VTDNbnwy3aLEG!jc_wA;i=cnM4|08B zRk(;&Dd;PkD)PCGpO|XhxDXKAoBsDV6G|5d-w3;n)fW$?ln@!fTcmhtFnN@Bl$hOj z>z1s)OJZOjkqh$M&YDC@9?kC+FA?q%$WWsVv{pmVsfGTO@OFDSm$$shcJ?ubVL! zp5=u{rq`@K%&b_P#Q^~H9x#72KE-brK7F;3hDe0su5&HHTY_#Q6FDWyJ@gC z8_RsWfdjd8>c;$$#ci(jn|Ih$8?U=jrYy^-)I>3PQt38{1r-gO%qsFVI=2+e8!7B= zG!6m~i-C$;3xI+jGMssl9{t^``}QO-NmOA zGCZVi4nNb5 zj5?m9@opp|__s*BzVNc9ZtmWNW1ng;VE-4v|7esl05-LQ{*$B-MfEg13#sfN!XAA` zdlC;9w%)8|xK;8ziyg^#LP$u`K4}OTo&Ws~ou-r~({WIn(EW8pb2DEwV(G!6FP5|l zp~TF6xn@)@JT`$+kpd!HXZ>-|9blZmEcV&lx+?WY%$qv5%B6g;Q3(|4wAos(O77=N z7GK|`HG|HUUlJ+oG0kn6Jbj8ZeLc=C7;xURk` zMD(>9stLKIblJl)d^?K;Zv0FXZ;j&6xh4v6yiJA9UoTCeRqOQOs(GC>4BzZFYxQcS z)~R?9rxi^rNWSGM00YyN!jcxb%WCers*feO2}-lGhXt4xUi^F1t*Kyek2E-&ST*wA zJ;p(g3jxk-=>B24i_wKO}2Q88-q(Y3!p`^w2^{jjvERn0#Nv>HFm&BI7vk zfUgyYzAmVALR%K%E_3m|-6Jy7D)-L?B52heyPL1rdSP+&LKwW*FR$~66{F*4d%2Lt zS0>Uh^$FiCY0R-)+3w#(Q%#5BBIj?FI&QW(P3B{5{APS~-?K?Q9EnOPlJeobx)1PP zDWXwcB(_-%-8x)DT%tMGw_@TVh9GZNUahcKT#JG881pt?>0HWF*HcGGgwn6Rx)$OX^pS{hZa_Yvb$xT~7CT$Bu?|*$(=N(l+iZ%6gEVDKK?5~%UCbtmi zRM*Kp>F7HSDfu=F}jy{Vpl)RWw%r{?YSivIj=X-2c=DfYMs=KF6*rgRxSAw z%6sl;9^;`2b#C-4Ie#^pv9?mgqI`9-A&TKZ7#ThRX6vkiB{h^Lg9s!$lNq}*9PbOe zwpvijRSMjkb8qe9rvWB8RfU1QW@S_5S2bCC2LXr34P6XYxH)T`OpSVz?JAm@^M_#; zn=8kLuy4?HuK=n*50`^O>D@Qglll=^ZUUaq=J}>pnc}l@Z6@5ZeVavQHlEIcOlk7W zY-a1kaMUWUQs;KfRwE&?kXqdSK9MGgOFvk~sGOP?3c9DC@9ChA56jj4sBOg7n@F+{ zvy@iBN-%{~dm1O2641B_D_!LmKu@<5nNu}XAm+90X}z1O>TXXtxG=X3XXH+8#r95stZs)`#LrXVbjR}+|h9b5KTWTZN^%v==2W?ZD-^Y12S zRDeysYvDC3`!OJTvFq_WoKn)MV*|iNN#e=MP#LR7H>a=ZJs(d_UNLMh)y7`Ka$3a`En`$*CKe=vOA2sL?vngFWDxN*)mm2G7-IuB6JNQ?QfsN1{t^ zTfrwe3ar)eW7w=dgD?G6Z`$9tsd){ZTPT!T4LZ*D15Tx7NUeKAWG!E7j5vVR$h{Fz_G~Z&GJ(4_+ZN24J4~snm*k-)FXSa(;*368yrnG|sB?s(ibc-Yu0^W1`TyuT+$KI_Im*CW>b5tUDiwj-- zm6sn|quzAt_Z|zI_2o2egs4Yr*I$a+17zj<&_IUD^{73X`@VMf7w%6H z0S39*t+_V-{`}asFBFJfmy3t~*d|x2*Ft87-S=tL?5E`8$F~^NnM!9amNx4OPbP9! zMi9Ne6^m`Z&UF><8h)X`kqhC5)~jHp|y^Q0PMwu_9F z^I(Rr71?_9oS5xzhe}~wAqLM27Vb$TGqgIv1ifc)4j7TlxF8PYzS&Z&ZfNMH?sa=A zGqLnp_x$7>^}%({MoJVkLYM8Dj?h^qhq2LEUp*3p+~`io*>V(mEYi|q=4|o}ZxihY zB1X6Ke)fn2+V+aNsBf?%3F5;ePX2x*TyELyG^9r}wFs@?IH6aTjwDn ztvfLACv|}+4D#O+yU3aK8e|1!*74@g&0{>-vJypIUcOG_WvcaxI6j2#7gE)CCwChn z{pFTktwhvUM_Hl7b`nzd826z9NTaxV_GOtZE{$~T5(|7`=+k4UZ$n#(jMxh?Ztji- zy+EsZ0>r_?7Z%~lDW2qlW2)VWOW(2)bB5iFfm%jLN5S>Kr0C~ohUmXU72YWPq1KmEn$6_b3f0o@#XX47g z9~gVPWSr(5i-p?_%EtPJN&7B&Sb;C+)jSx=Tgi6lHtq3*!9EtNN>1-%@oyK7Q)X?? z?@*L?KG(JgB1zo}n)1lj@XVN{w21CMYtZ&is5agAx-en3O2tae(1Kv!yQmj{v1&4WasOk4WM#v7bk3GuJuDclD=LI+! zE;X!$zHp*fuEWexaKe<=oL>&5mC?5v{4;+zbx%X|$TpCqh?NKB7V?+ ztWgp;r3Q^Pbj{Jg}S=mM7T= zd&>-oQANIU4F?-|oUhAmEe$K-WwdxxGM%!X@A&+mEi=wuyY+l-(VELKs7=e^?R9(E z+nW}j#D?@-fSYT6ga5Oc50Q2`5V}RTmue<-xgyO7^v_`BI5r>KZYl{x-oOauY>KH8 zQG2D!;pnv29!0T2LX0fAw5PrWY15aC(fX@BSnA%TL`NoxM>P}=2}!sLki+Q<#EVR4 zA=b6{ZLvTJ#$BvHhZh6o{$Yu+k591TzF9*-%9Sg1OH;DlP)_bxq|rU#lU91PlD@3^};1=Xt@;~RsAXrJmgp@ ztJp1tZ~GDqEv*+PEzidJ@T?DW;XoBQ(``>$E4k8SfVs{+af}FQA2OSt8kgZ{gXf~o zCKsdBMtWG3Fd}b`%uz?(N81T>*yj6=?M9mS;#hnkGH&yFeM_N=hb57gT*eHgHT^y? z+CTS&|5d@y*JrMBnUdaHUKXyEfIwZf90mCrSngZJnh+$D@{Kooab<0eW{NL(oALa; z9Y0&E^GOUw!9?APt+;xfFA}@cn=LxSpz`Mh$vifq7`>f`_3S30&FJYWhS-mnK+F{W zONV?cHhXWr=lodvo`nIt>pi*RuG#6EkGrq!_xFTh%sK)66T2G(69^RE{H7SZR4FRf zY(H}|ZJRD0SFL>WnBtm)=Tb=|(PZYJoF2NILMb_G6;|t^*fJMgh0g$G%*Rc+y7eWo zJb4`*u+1R%;_ciqu{T=v@yjnn?w#WTrK#00*Yi~h&g=B~Q*>}!gsjYsM4>b0sBX^h zA5_5qs`=>XSA+o~FrV1>i#GmWZ$zCFTIj^rdXf?@oT$zCouaecGIG%>%fKKN-)ed6 z5v@-iLm0QxYDgV|1#uD>{j2bw_;ObPUuaJ@NAV3~MPtfM^H&`QWR1aLR$_9UKVGv$ z49S8`PCI;?$BCN*JG%QwtyAGcXAr;bpA-yxi~f49%ZoNt1m81fb_*)948pb<9OtRm zv~^#-yN^k995L!^DU`e-X=|gdT_>QEbfA}+*;dd8R>{qGb(!Psvbe-PNglqI5%Ssq zC+vWgsJO#Cw!e#~&yhCV<)^Bj4^MfD&(NA$wu|&olUNSSlk_MMiy;5$yBGYsHoJkF z8>|ifQTRI!3V$R*bQcth!oQ?Up4m*tTzY9X?qzOLqug?0|9SPe2}fZ=`evm1C!sl= zakq%nxCM+{?Okdw9^C2EACn2}qorwcm_OHdnG27QA#M67&O>2fjVQ+7VvqD&gxNYz6C-Hu-=U)RY!0p;`8yXEwkl6P+L}Gw_uUKS`5X=R9s68xRM;n98Nkoj^bgse| zbMPG}?Vb59-l}i%f9Mu@BqofEz6x(KpDWND5Db1cli-Pxl4}pLe@lO;OCFK0^oMq- z)Aah~R?nQSP8{tC^Tn-@m-`r!ku0By1pSg1i%&oQ6&~%<g(Hx6k+*3Jl2* z5Bz}hVvD=&q9--raA?ExI<&rdaXL1RJfI|r$5fN^5)!BGr@v%U79X9nV0A29K=baW zENTL?ZOXSwAZLf!^OaREgkHQ8MGF-{m`!e+L~W5$%07N^J*mxg)kZ?qK&geJxYpDB zXi3hffit^Aaj*WXXL9VUoX;O_6qYT*8RLG!#>nOw)PdmCE5@8eb~9vV$#02RFb_P( zsL~!*u@$UcAP1gPc~Z)}zYi>>^>0_suj*lL?{;U?bxP%v%VO+$$z;tBhjzQ{n61sN zx_@3IJ(o)Ku5QVw@`3kTS7N2Uw02GYsH4?4Toy3LSW=mby`D^gBrfLnneIJuki7nZnat|Q5Y)Dwg)FsYxun|ei(-T z-#3g5b(@*L->`i0z>rS#;=!(tyOg!>@JZB$ZB5$a&|GpZ>lQz$yU+BJP`iTgf->=C=5{_?D>BkXC z-`ZdE#h@1ul$iG{gN9qZMn_|CM|f>q zA`X4R|7|E)%fDLxGzjYDbp4|?i~6Wl`}LCeap-kztV;4#`)_TQbjiBI-H2@alLfy) z0*VMN!O`=BLMyT6x{*8r0Jvx`Q!Q=^^A$v$?ac3bZcSLmO3BQ2b*+bM^F6xmk^}=& z|4K-$dslu&kIkEvwW#u@Dy(8qB&%~iL266`T)z`|aM)TJ@x8LnwcIyKuo3&~o$ zk~B=l?os!GS=%BjVhP6~8B0FGDx~pciqW;%qo6pC>G;v@a86t**@^F=|6A=T7jfvf z1{^xZ)A*fG`9=9sI8eJaas9hh7(e@+SD((U_TQx!*0*7!hp5iX@Fa&6>!ka=fsq74 zZ_^M8p$6rp@WD1DJWje<&A)OypCE)=<-91#xM3?krDla9<%j5^9i1tCp;sx5inU9j zw*{t!)MEr&TxZ(Xc&2v`t!I&$wuhj>)18aE-uJ2e}05A z-;r#QM-*ra1_fFJG^t;Y?&Dq-)b3n^x|2F>a31iP+<9ZDNCyy1^PAR_W zzsDR)H}j4SbAbK->bH8TLOaiSdp^5X3lVvhNgPKx1Ta<4*>m@CUH$F|22VI8*1M@F z7Z3>QZF#p+D-3*vA$utYSp}%-&Fj3D{Y$ePml}dkB@I)l1X3Al0mMSzi_qe_? ztjx?nM_(7^75x^H^ER?r&ep*S)zVB3Jyp`Upt$N$zM<6+ah z1@5h56%cis54+k}@(J`1lmJTmjtBNJDVU$j_5$GQ@uLMO*gf^F=8~&fm~yZ4T}+8L zyLzPI4@7Sld!u*Hg%+ge$0oG4#N}8P^+O;pG&U8oovDy8F16C8<7k`l_FA^|XUd+` zcYI}lotfP0bKc+J+)n^U@;@i z)e^eWRD_N)NIuy5uI{OwcTM1DjTce3C^Gg!U6PEWE>SYQ!nV&Nou{cK>%MSLiCc^l zR*X0tD78oawDBi7lvz^oGrs+)OQNo_?#jJ%+f*v&!|W3%$l$Rnho~!`D;=-Lly+0I z0JnI#QKv)FGE*MMUNve(_X9j>_!n+YLD)~(7(E~tI_)G?VqyR2#oJqo|l z^>uz2)SkP}&Z!yO4zOt{t8+MhV5Zy&JsBYcAzeJ)ZeBRt{aXToO^ui5cvR1+h0mTBj;u`d(f z{r!ePCGOnmK(u&;%D!fcgOf%K&742`qaK}nZ%edO;C*!cb}sJq-0)OT{NM&0`Rd>G z#;VhuUs1>ANKxS7_Ii$$7Bq!m>!%r@^F7#5WxCP11`erjr#|ZKz`N5g@xoqSFU{|W zX}BF7U*ToQiXC+ojYrVK~U$?lzF=yYFN&hAzw?bILzvHI6Y`Ac^aDI2! z-TnQTDmrc^m34=h0whb*^;wCb=#6#Vd=C{{p}{gAE)80M-( zB*MbJwt$nAueN2XU@0{=ODEK9eAF2%C@nFW!XWV2&hT;0vj*Afg^`oP8v9GGtxcN? z#=?%%OriynTbSWiV9~gQcAc}Twi>x&*N_W!k>pQnUsF7WpKR;z=jYr9&rUU_n<2zH z6$(}?nY=uvE23g6b77%BHlsFF-Sw67HMAk7;o0c;64|dYH&|bBuK6x{*A?P(Z{oo1 zJgO2(kmV3qehQT`&(x~B(wD=S$(B=_=*tn(+hCuSTGK_R(5I`5PO06R-T~V!nRs+w z@cZbr)W|{!flf2B6sA2;?i0zJ8fKl2E2B0U++|wMsc#F{r(D3j`Wh?FURmN!R_T4; zsh*?|mwC#ZE$etzlZ=saU`SSuR!;U8<4wc`_sV}tLnZ4`v^0hPU5GFp6e8E-%B%EL zD4OR@Kxm$m|LHy}ovLr%>$#X&mqA0725&AvwX)K!uG{)%S%&ScH6nGM>d(E}IWVVI zZzPn9r{DlSDUa!>ukyWLSq$#oG%V4>onGj*7qZgqY1Q)CW^>oiJXx|*t}3mb`TISB z_cW8$*(rC}l8Z@;PF$RHt66+UTTDN4#4uz}{CXrGX+xUi12M$SYz#X3tg)|(FS-`b zTnTF`u3x$}o1#BmB@Q+ z2Ex8A^(?HHeDAYZa6B}U*^CvpUI_d=(VtJ!RElb_QknL{oA3LQRMk7{n+whZifc%^ zFymSYwQ@D#Zv_)&v_}1S>!IZ-Bg*B=+cgof_0d1sYJHgSF7<^NQ~(#YqtHY z{!l!fg*+FD+zK$-94r5QrEYdna_u%CmRdH~EzPF?88#K2<>D}joizTbUa+2Nh2+XO zS*ZGbEZ7+Gu=vY;VyuEF--KRy&6U%-IG?N+TCpHrk5Z(}`3Hxt6KJVPlcHafW1_A4 z^do!D9m3+qf++TwvX*^oUC$45QTD0lHAYN50am1gYlK3>mUxbfHZ&gK`})l@o7sSG z_Cc9rSX-|aeyJ5}B&L5_s8o3LOytyHa%7#nh-hR@H~6DAYJP$O&ecr7Y=k}h+-@4B z|b)i4?zQ|Y$=}}L3>SJ0Bk#iK%T+l z{K)>;1#iA!&8dw(2UOy-o&e4f6ziOe68c*>B-(ES2H**4@nuf5v+CSWX>ljsL6L8- z@Px2`@vGA;#8;`n<(ay(z5MSc^Qe~Q=V1Uj)<7hOVug!4x#ot=S;;t79_eFztw+RA zk~V$A-{0U$u$#r=Q6OBJ7Jub2dTm%&VxuFA(~IDGy!yW3UBOK@Bj6P;BNoZCpYY(CH7>Nwh2 zU%rpd-+XqLf7M*P%()Q-u-wFHV_XZSXzYf~BX)}j0V%K4sAe3%p~#l%=+FI@QJRMa zwena*T-Z5Yh)X*@hO)x9Mp;qJ!RS@-X~jy(1zsw&1uIMMNaH$c577H+c>vlxmHuwUTqjuR%s)a zy_Q2_@2<#f>K4R?D|wn}b?#gjXqjqog?#4wnAl+6zxY~nKArlf+C-c}I5&8=A6oMi zubI9c)*F@v058L(kx8Ad3RPl7>lB+-$tlhFz0&YyMxOgr9vj$Ah9~J*qkP;S<|}y1 z?oeXF<76<|%ptOVKi^%A2IR%&&i`L|aamrcebzk}>Mnb=)q^=F{r8EjIjhRGxlQOJ z+%Bs8$6J^87J*Xr{hWOFt{^!AMPNSLqA=yI_0XU-88eP}W zuGcpSVssu!CM5C-WwrOqYry(_DCimEJ7>a(Q!+M!Z}G&4F1`DLPwutMI1u9(h4AJ? z_w$EBF`UMi=P+V)+olQB$#sUAZV=fhiPiP&@dEMJwF}F>7xw+9WhBWO4v+H;vY|4S z>$7g}BZ=nR)u>*AdpsI*Y#~_sqs8JV-5GpKYO2tq|^;4Q`V^p~Jf zt45smwL}-aY{A^bpRdj>Nwj<2W;Z+eyLv75UW%a?i50qJI4i)--JP2y~%x(dQtcM*d}ryu**#~DiT zwEQDddx7sXmTspnGTf&`n}hat1PMD#CmxFcyZ?^PXASGN{@M|$ZOOUAt`jU)Pr`kMcN|gA@zOo_ z<3Gr{SL@R;0~>=olp;HH>w6KACzCqL{6CqGgLuk~s7G3)eo5ULC0|2+9$_2mi% zpFzIYK{u^5qX(GcU?wXgD6xu8GY~9^PsacCxH6uaNc?zQfyz3b-)`V3!NbLLELlA= zQ*KJ6jn}FsKPeshMuV4-{kCr{DpqN(gPy|Tg@fA9n%X+wjM_Q4e5!PR{B&adW!xH~ zy$-7oQTN^I{uQ%;MI7XOw{_J*-R_$xRTC_|A9Hx){9#~(Yu^o{j^E1;!gNVQDBN;k z@cH|o0uhyeoz&sG*NWerM*Jl*D=}1QEGwcJY%k4vXKW9=K;z%qjjFZfe8-=N>zWiR z;R<{!)UzHeD`k1Sn)`-eh|Kxn6&Ux(Bxx>!0POw<*z^$}v^Rw5CvO10Jbq<#Q}R!Z zL_h1);=6G3eDU@~-%0H^OfMd-XHJ!rl=3|2!i*9g$m`wa zhQZ1t2TkaHSvj|QP0<12jpQ3wfsZubmQj42GD>X6 zZ+WgL=8cjXmi`(^9wYZ=bN1Khs4CHO!)@I1cSHciB0hFx4}s&VNOj28d!_L#WqbcH z{jfLxs`2_*s>Fk1aU}TSB>NU*y=;4ts-POc4t*FNg40OCZh`xe{-D?S zE+Hd&fj8%I>C0c$8NxoD{FVk}kDNUb`b}Lr$bAihIlCj|UUrtYsAtsr*u>kgzLe!J zY6ex}x)c(9gC`(XSA?!TVzmtbmBG*9o2-;4>CPr>KB`Lh5*@7V>@kSUllpuTkD3u7 z>kUV%<%6jIaZ<0W@K7LDSTroOHleuNFyXM5y64KRarL48qn70>eII{?nMB^Z4AIh z?0<%9pOz=-G>RjiZR`m15q<<=Kj*M=)W2D>pD9kv>l2uW@)ewVJZrR~=U^x{P}eF1 z9Kcn6Op1TELD2^(bf5RruQn+1EP3NjN*0SgAye)ERTZcs0Oa4M0it#~OP07jF>+Ci z0lNsw8u6$&5J;=Mx?SI!^D6D{4KA=|Bi;uw7qI0KKP|Tj7RR3h%k^qe%U!0IR^3f< z-&y4jn%$wQs3Co1&uY)iqQl3^K7<2{mhYIbo8wDG6wZyv#Yfeuuv#c4&i2{Fpw8o2 zEL|Tpz^53Dv6fN3vfgvJ@Xcq`&`L=5_**IDSN5#>t>uAi6zUig5qs2EOT@2znBi2n zP#pV(H|{8=K;f9fGDKndjG1dxHdwkf7;KIb!BW0!md^BiDNo6#vKkGd4snc>`pX6^ z4BdRB)}x^|J&ylTCQ7z-j|;wBOukRgECMz&9XBajWKLeB{+e}YVMa@L^mKK+LkBrJ zqxxM&e zy~C_>d;Y3hde$o0?e4mU$h1TvFIPsRsJqTNHxx?!d^pmbL6brAh}JDT`f&tEuO6UT zcfSlG77__0?$rO^1(m}@a4<$jAp{5veO8qV!G~h+F@6nYe%VqE!%Hs9&sc8?=u;h` zIi>OVRCE8;{H%loh`!$m(KH>n@i<+ki!GTSDjBA-$ZB?d{*}0L#Asc@8-&&lA8)2&#alhYm+J^!}q3r89(EGw7FH7SRb&RlU8eL9+ z$2v{(@sYSnEirFhKHp{jGvPHctE}xmYfC#NNkvmrn0bl|^n%^3n&=JX_AUk7-)j~b{a2%?wU)6?BT|2`E%jru9083@ zY@xQ)w(Eo20+D}~BT&J=mY;#8-$7t1;h5@0?0JVUA41T=`OsgTwMRkMIj1`DwEkwF~m! z%g`RKm}Z=OxQXdF5gVqA2xOqW2sw2FhG?DaeXZ1iZ6PrEey0DAwD;~>6iuUkKZ~d+ z1`L=3K~c6(tZnA4fmL8<;0li3UiK){BwgHnvh#tR7)UdQ8q6<+IjA*RF+ufr?; zu)~o%U4Ue!otBDe^`Fu-g*i=>y{^Y)^ev|FYghCRT|5CCA>R~v*Jv}1MU=lMyl*vh zYOb8enho*qv&$6g+?YXc_{o^uRyAl+^fpDqgRb{d6+QK6?AJ zm)NO}?v=H-HV6mc9~1qskVT2K0caKpJ*%LzgAUS2PqIiykZ@Z5BuUTxOkw9r)1~ z6I4f33=%2jiQHx}@^9~x`LO>@b%;|m7***n|3BZW*I6Fmx2um_>&ywW!$}U_KBDjJ zYq7vDuhEq<)w{qF?jp#+s+#~h3c;fY1}txr;b2jToekQ>u|y{}?KcZ|9=DxX+U|tT z{q$SEGX57~v;6d;y+@l0pS5y1D5oaXNxog+_(y(e z4>BslBR*VfUSIvns{81T3u>jI45V-~D{pO#p5Ra6F14V6ozsN z-Q8<22_e*bdQ)e|9G^+2-_JdLXDt}TG1u0mm^7VSpK5n&$J@pCKkA(rm=Kk1X(?ud z6R8-0Qli`Z_ne{FKi&Jo{%pVW#TRwrTOFqqbLumFX=C@#*%UiA4gn#TuG7bQ+0G&* zIsde31+WM$WjHKVVTbkRR~0LjjPvQbP-Kgzp5NIx&Ayd3Kd~rO7pY^THx;y2^XBtZ z!0>QPB>QvOe}{oOWoT7#;y6@``$)bXa{?G)ZKJb^8N{mcq0u(R<0z8@N3nE&A{v%E zSKz@8Ot!u{(}e&|itrJCcjp>DB(yiu-czg!!&H~Wbl)1N!RzArGZLNLsK$St&!?n( z)KRxT4pQOruw&x?`i|1XJ7A#y&CO*e-*%;^m6fwpCMVpV(ZS?ikSm_y1N#cDLRYo} z4lhVF<(U1yY7^&twZ_Q(y7f^XZ7kDWre4uTs!~wrP+^YN5|5te;NJN;Ihw zM=R=|cMj{tz0biuFHM9zHQAS2e27*^bAS^`Iwz@f{^nedx%Xk*MREX&tO*v)llH(Z zF1D?5X=`z%R@J{Bi7&4L!1(mM|K+L4`!ZfHSv+5rch}@cPyiA$w#Pjow;oOR39qyH z&*tYwO*pXyd5xfo(z3CDVQYa^Zo3eLM0I6HR^s4H7$1;&he?J`vWW$GT^rAbIfY4e z=<68;8HVKlA%jS4_fRu-Id!Z&Y=*F}n7K_z;gWpl%ym8(3tN>5y$;sM-xTb&{A=xlW)}2!v#C;bY$793i6@F zKH7W>lJ-muf;|iLz0oiCDBFKF&DXp<4h)L$B~974yLf#)9j3dQ#x9u&!o}qpUZ3+< zf5c-wZ4e4BXSvCK2#8ru-VTBf1PCkyD1*l1dhL6yVCZa0bKqMSOK<5jo!sDkC0qQ$ zcUIdZ=6`u*nIARDu(KgxD(Rj5km%PB!_oLU);jS@L2CpPqv!Q)%IV4Kg;ApCZ2#F| ze2*kW@Q;6wN#mum+U#VeN?x(Eu#1K&(Sq2HKL>5!en%OlIsQ9qEIK`GwEuA>x4T$& z>AbAA%c;~*oR%J0zrA7K-uf)n1tuP{Etu2)vzr~S@1Z<6+sie)xwy1XQsLEIZYEC~ zPClhp)!VU5Q%ql8{g`NIs{RU2*Xj7I<6y&(D$_y%Wqp|1HRC~Xm#8?NK{M8Xw_+w)1>tt^KU=Rtdhgp`E_x}hCEguc_>mg zj}dg6W!K3d*8cy!iJ9)A`C-l0C>CAj&Ggpb5VHPy70l(cfE)&95&LL-D;vG78hD~$2lYe{ul zj6F4Uk6kg`be~HNA2Gy+u~6gY>fsLFUW3`>c7S;I9pp6r-eK&m1wuNNPw@Z)2r2n) z=Q#Nz0TAYu7S9awtKSflwo+iRMMsdBGuco5+f<+2zFPBh=Bm4oWcSx2S*Lkrn;l6z|LVE0U9B70Eff~lSn)Yy4~^yUHaKFO-)&7Yt1yA& zG zosF)0?f3P^k=9=z>lS97ydkKSF+u~{hWACe3e&bX|7+QaQy~0Uw?(A0wC=&u|NjrNQj7z(A2`5!2y*bj6d0|s5u?hd& zc9&tDa?FU@vt@F$uLr9oCc(D!nfzZ}U@|(Q8+TVjs?m0kw>yz85x@PKU;Tbn{#g

cUSK7aPCU9Nt1de%?cpi+sM;H?96!bDWFdktdf(veAT*=ZJ*b zuv{$67t!(=yr$}pA^v_^700>FO3RT>W%G`Qk;wipwo+qW)|{Ogz{L`ag;ZXQvxtz( zA=sc@Y#@_+XkiCr$B!zpLw%4+6D=yBco382p1+pcVXj^nrY`Mt3sjhUt#~hWKnf=| zh^cBlT!Rp?z6sT%&9tZ0r_!)mCON5)>1)HVtU^E^ZsXDs?e$Wcf>8ZNRLwxAes(}Z zCep@R0)THhT4Zt4VJ{wUh`6mDh{DU_>bVY(fa($*Aq01&@>xaMdlt0ks=Vlk&oPZw zDYnoMr7{W@X|{y32oOQCPdGe%}rgjjZ!P-y-| zQ}{Yb(VC$D5ORK`le`ZYi8*DRW$H-q_<|O$B=-eE&YLnUDcwoWcOt?y={Q5naUzY4 z&SeppjkX%cqc!3C>-Y(A#hsIfRk|t#@x^3rr=64~J7Z5=TB=O?x{yAWpSrca&-Nkl z-Ns0H*8a!p^0PVXisxloZ@enUN4hxR*PnaVbH1Rx&YDoOT-MezIO*6!Csc+g#i*OO2zT2k4#CDN}9rljv^f;T}{V0x{Wu!O^ZH_u$EaxoB-m>@Ie$KQf ze168Xs>f(r9*$xRuv`~@H*)0k>9{E)8!fG)z-a@~QeW3nAt3i!Kf8&lIL)k?Ud7hx zHKp+`2GxGetmhE;XRhaS`%l)I&EayRqcu)LLU-0{|+};EeOxv9r0?hRz$q( z@8`dS;NDCI3r0rY7d9kXfLi>AO*N*HV{X>0C964Y+=(rhTN{a+NGYs1srqb8Je&@< zNsUR536oz_)f5l0DiMf1_A995la?Jg+1u{mIcv$sO?0;D^4+`s>RqPvW*xU)ayd*1 zmlCrjYc_N7I5BqFc}5&82-or@kG$~>}A89L} zxkvl73NmCjS(lJpigg3Q@;g{cUSN8jU!YQ25AL*Esi z*C~J{I>R}lDsL~==d{j0RDt!rb2}vm>Ei4X?d0+Pt@12<_Qij_f6f=9*K1CzIztqN zf4zU3VC!er>{{O4&D5daCc2qvkntts$Hgq<32m_M7Z{@$vDo}Nu$z1SZud#Xu)cLv zv7=q~DVP`%lVV`MK2veiC-%8p!--vc4R@K{WHJu5d%g|W^i1QB#xgrURTFgMaCUO* zj{8oMt#}@}P48(1?8qLoflFu|6!}+*%LWHUzreKttB za1?W5S;z2a)@rC>*o;%SQs%yW);05eDKqcQe>aLX-L-KCQ8!&8)I*vrJvpyy z+(XTZ{A!N=PD2>W2>~zvVsR|D+W>0kssGxhwf6$XzL9^8?rUf3=p3@;YGC{vykS?~ zQ^)r9nEj^%%FVh1t}vUweS!77pZZH-U+q-!``$GBtv3P(_f4_ii87%>D^+tOZ<#$_ z7Zz3^dYOgc$VH2t_jfi%H8LaF&YD0YQqRDSpoQ9}d|Sz5l1J&$Xe~K7j&mhNYeW{y z;A5+ETFFW+#qfgPF`4tkK#^~KpG7XZcxP&HO>W-?uCf*E#K={Sx(`vpx$#;0rh`rW z`mXsLPefkhW~+Sto%r}Y9H;9@b5)pTJbj`9wj_*R0dz{i=0DoYDp{akGv1INM zw01RS(R`Da3gpmn@n83<3vZ-n>H#SbLP`5m3_ue(dyTX`Qg?y}-?@^k?sk#-SoAIj z0H6j*Y%}VL1zzBHwhW&->bbN_Vu+uNuip9RoC_qoy+vjp|m=CcWKvzZvG*3`vOUGJn;cPdt)#94GX!hYh zzQs|kT6;F(`#j}bIU6H|KJRIIATurkZNpNJ%XJNzbAI{v0l&f-rY<&8;+k$rU}in! zP^4Cb7$tS{T%mqQ(C-jTX0{W~jzu7>RC3+`3;?#UJE-`NA@rec|35q&DH83cgKu=C z%d%0B8dZ=Z%>@kS``N!%`!4&Rt&AJ3O_I@)pg~7F7m9zJ8=Cx?mxeZE+EKf8PBnY_ zNu-*~aKS8KI2avwLt=PPBAAT*_8Wy~yK<9+;um21!od|c?~s@$7|uLG#wu^=)!RjC z7*hXR1emSW38jx5UPt46=j8?N*m48i9;>F@R|>X*&UR@R{Be4L8Re5QvR4CeWT@ae zx-xfvTDGSK=lq3K2pKK|#)zDDlnOIL9b>+E4EAhpQo9RzUt}kASKKifjavA~;}nn7Qk%_kCuU=xr;wJ`Hz*A|xuhOk}3W#ln)`1!8XFty)kCi^1uMm$l=a z<|jSqavucdQ6}P3FZYbA_R7KwwIS*sh$C@SmG15fmj~c*oyhoTz3)TM9fQWTNMFCc z)XHFkwOq2%_O;gAkoR@oAhHUzvtIDf@P*uS4E6-;;oS z@pO$qxSws5;x+c^@BYF$?LQkNwhU6REFK4b9Y1WfU?5H`Ao~u});QO!3*m{h=yk1t z{#-l)l^$j>F#hD$ror$vTod=MyBDgXNSz1JXyY)c*LtI4&HvFnnW;B+n8hhtH|TY! z{mU-(GpsoT1E2gc+1b=Y5-}d-Hv+c5H6c_j4cuO`^!vO@tvm!|xLsj-$xE4{ny-~v zUTg!x1r`J>>fs;E8rvi{@m#DuDF3i$os{$(A9HbM?LW8p z0JU1?tmSY~x3g+yjfjAlv7%2-qYmRHTG96X9OLZ%kC|hM1Jm-0r(p!8K#Xx5)EYt zf&_@V`m6N#M1+jT()&kt(ls%!F9zxv8n5Kspo|1r3nFd>6O4eeN%ov6W}I})U31Lv z)Xtjw)xIbBEx?GOb*KA>cx1k5)~HDKJ;+)1lj`C{Dn7+lS@BP`^P&nMro8z_N=RL* z^!J=;)WQ<65-yCBR;gE}P_xg2R<^?V*TP7j0xp7t&-S&_-uezLXf4B-OdOc?9f2pW ziCj|X3e3W6ZCNlP<5x9?aY7?cz33I{Xhc4sP(K>0-72a6OyfZqf++46;-rDp+V~B$ zg?`P^5BW;sz~+x^h|k5*Jz@B*kxqKlI_OGr+ihD1mZy(;{|hyAOBo#EE802Je`&AlTe-tkf*pW3LoyYZ?ySMzi$S9|i^KiKT3>0*OU>5>>zISls# z?dR#aT(|dhx`#l%QNGcV4-58YUdlq)dT+2iSn+neJc8OU5D((b8N|=zv7Potf053{ z*Z4gST=qBdo{zOIL@n)H(w8%3#H=L-gZYmz`>YQ*>+L+S@%>c(|v6hQ^DA_=UWVobCRXQvew?>09K zr_A2ei4Ko_JDf{7xz=Id$0q$XDJ*kszdP}&%Pdj$0DWJ1-h9&k%Qk%ZuJ)3V^v~fN z^E~~Z-7ek!G+!{O1t4`|_lAz!T=mPAFf&w19y>+zj7TWIu~27hTrOV94g>4s25d%MMb zcDbDZHrrn+!Fnk>CWOh(v*s0_OwQ8*{9sD%Z{MMw3y6!B3(;<&3sj(wn2sr9xGP88 z-Y;e$9pxM-CC;gTjSai@wD}M1mzQFUDlr^A0f4)qX3g*$)9%2jns_!!XU|`jA*2lt zxR9kl?nuz6@5%mtKX_|@3oE(rQcbRUp;1&vXjvI%Q?=fR+hrlu7mabWS2>;E;eWM) zImiQyRE;r0!vl1iv+f~MrFV5!+Eu{zw%F2s&X(w&aw%@J+BSCBnS3|Kl={@A;FIb& zM%-Zy5WU-z6EVL5w!2)=)@wbB{!GF6Z^wtlVYQniBRYsa8{9$nJK2^672T*Z1RKw_TF8G;a`~6=CH#x? zGrOPm)6y)do(tiU3wJ5uyhp~^wI66p(ax;CTnDu>xU40|407d@M%{OdN4{PXd;Ys) zLo}MdBGhqqZ4Ne3=Fy05&3<;Gknj+#!V_$kW!D*&fkHR92yttl3t#n5+$E6XrLxX?UT+{Ewb1LV)rJ*|_5o`7O^wS1Cv#+AapU(pE zS$pDroXKFhv)wDh>CDCdYx`L-%?%nU0a*+#u)TGh*HY1LBmC!JYvCY#%eUq(2o}rS zuw+dhpARBYgXf>()_sY>5fnz~qsw=ErkO7&$p4|XZ*z~nTjWz%txhznAQzd#)F-Jn zQ##X&qgAl=bC)uc@ys!2U3Z)Si)LVe5d(CGePd-_-a#Jr?ht+BAJuqZDhkaxNA8jj zjmZ1X*Y9;Ot9>u269B_WSn?Z#5Riktr+aC}ypzl2!wtMvP#x6#%#6!S6Z~2=Rblq+ z2;Op|${eWk&tT96zNF0)gY4RSMD+~p{Dw#q`w$D*%C6LjxCS_$!hE6gk*{2+K!9{@oTE;{|*Uqmjo{EYBE}he-1{I z!HjtW(mFWx?Mj%N_3@XS#y02m->$GyAMKOc|5I0<<|!ncV(jBs`Sn&L;Z*!thD!zYjx$lx<+~0^0 zdjkQx^4Uq?s}`m5V3c-WxIr8*Tx^Zq34_h&-aO3GA$n?u-zI|VrHmU?RC%(mBP9XV z7g||InNaOvNL+dof9I&b`EA{xmcS96)x<3#*^y0UfBYqa1`^II%l7&CT>dG`YWvb5 zZ}@kMKg+UlxSPN1)@qqvw@TUdyci5gy>M|PjvlY33wzIa%tzAb-8yJ3+01R9e)yp7 zol?I(*jg}U73S|qWwsvY*=2cbll-A)y;_+{Hf?CI%Zij9^VW~2N8uXT3(>cThG>2K zk*I#R=X)p{-6uV2J1iYVCTdUCEPGR08J88&^srRTt&u$dMT_usaD?{(MdDsNGTfuckXN;!Bh3;~Zh+CV^9gR49@UFpX(}qYpKfkZS{9kVana|1pIA$O3*)Rm)Yfh|f8)*WNSwU^RL2RlJqXi)}4uM;#?xu1{XY zeC~XR@7kAVHF8OFlP}mz&&ZU?SU`Vlc=Mz^%+45HWOfmR}v z9haNWRC6;7Am{8vWkvpcSyd+dMU4_&GpA3JyI$giTqA*v&Nyl@z`F2N1Lct3{|F7E zqs2a1Je^Y*Ki2EOW$q!!QH4GiFShdjbX+`pTDm$LDA{$j8eIXiHX3*ndN&zThIcsq zF>>|GN&0oozKh;Nl#k-#Qp?oHAkr=FBy71-Mk@N?cU&nn=QiYX^jl)tLs8U=f%~O= zSQA%{XGI!jNM~F02P%udMdXRc;H)fYW*MvbhPB(uv8Yo)oU4&h+java#JzAny$+rH z_c3oC#Nc`~mC!lRL13u;$KTGs{o8*SZGGuYcpk8?7p8mtq{ETSp%BZ>o!d>lM@yws zS!i`rjXg0g8P#aEFyb|BT+0nx>*srwQ85}4)T-~>A?xzN*i{>PBVp%9a2 z?ASlzITso46H>Rm=jI4ZA~DOWT8hkgki~lH&aAuVeHt}QLAA-+M0oVt-C>LTR!vWb zvh}~500IIFMMMs#I6*4q0Im+?eg;QBla8b8qvQL8%hATNaSVZDNW6C+cnzi0zVfvsaV$~P@wyb5l&Dinhvn`%F zqaDaqE!fs;%Mgqlk{pQVeI%kVz96STS5V2}02OH#%q*8qg5AOQUX60r?+m`3SM|tWqJSu= zli!&tI#tf?69wopgl)V!>H`$z5lJ6rTRrDL`CV{OzCW$uePpMSpOiH=^^sR5x7*p& zI3FVgQFiVlv&P&0A^3y|%nC5HTb0()`two>_|hj?^aIrw6FrV$Y97x@?w`nHa)bNp zcbP0)kp~IG?=ab-Aa40?@eAAdC3AWE{W(szznCqNjbG!U-xqF8o5>>F^O*{ivQ}wW zJLSuGrqT%u_*_e4o`(bGll7vo586Q`wtH?FNbsAd8I&QAw?gR5Vmr; zkL7Z6S^gXEv1t2>(rSvOkE8MZ6+caN8D)uSyu%nweYP-LvC~2^pWcS-fc4qN;4VJ=@ zSBItJU=4l!tEX5oQc*K-0Bk9d!O+CStKuRTx^jLHXcnNixcCNlmxbO$6owN;_R1R3 zOYc|NS?;TWaC^XgU?u5`o=Q*4FP0gDNmkOhAvEH*8+G==#X`dph%zN~mQDvOOP1~i zORTxtyu7fAzmM*^%l43dm2_)=ERumP_n>>DJ1&v;p35BC19eDQ}l9lMEr@ks&l zC{D2EoyT!y24i-1f4I6rs)RdbqP_U1m$WYUpa@-EJk@~;0eYrt8&O z{=2Vbq!=`$1Pa>Q$`7i1clUq){nooIHQ|SFjKFJa87!1Y>4XH$&aSd{w5O!LBg)k8 z8k5XbNB(Y==eG9FMU+K=uMbTyfpFROh(^*h{O{LcllB+Hi!r5^oINVNIjnS!b|&5~ zWd=@?Z$mrXMZ9Bulz;~OC?teyc@XVyqR?XVE1PVOd)bJEt|MO)N^Pdyn>;t?O2C1X zLVF$-U*)0jdp{nS%UAAQgE+o;MLQP!Tg2ALWrQ`Hk-0Qk6%9~+%h;$QYU9ifmmpC} zPYmsbs72g0!sgrLq0lg6aFO)YW4f|=amH1T4!?9ou{X_X?(ZFoM1ZX+aaL!1nnzef zG5)qaq^s;coAZ0(mUUGx3a<=v-QtEQj?@4fe2r)OV8pzyR*xRB)=271aBfuA!qUI_ z8TBeHy4Wg>50{drTz5k6BXMp7o3(B2I>)gBM)LJeb`oxbF9T~>*F4}*oVMCTJA6*i zII5tv+NhJAGpdg|Zdrj99p9?hBm%m;BdDk;wHTUtUG6eWnfIn@=tK46Z@SYmiXr)O zZU-wijIm^><0-3)3@5bQe>i<4b1~Ty7bs^%pU9PGCHXXo;~N)g+W!>A?EEjt_l2c7 zrTEl)da+u>hAkA9+s(~MPB*E>O|@s!@b;k~7v$E~^lNB81o?5y zmjul(C_$^?)8Qhar!SeI{lnnqbQB|@;rdW$?%(CNadatj#cWRs@2cqc6U2nO>J2ZO zt*&i-AuYMwBGq6%AuHoP3(_d|d1ID%pK+5=cUPQ|^=dsky+ht}<(d$e(^-3AGPl@O zZ`ud*iVx+7B$>Qp#JfP zrCdbez{(HxG0sCK#B0$sT7FO5hbI}zgQGqjI)uvjiUBIax00Fr_`EG8mpjbO^XtiW zIBZ^#0fc9u=Df_rLVzWQeL72#0D@>M3?%IhnA}A1SCmM<`*fcv`zt>xgSFeQh@?J) z@b5n9RldnfmA`qEYlS$_L!e|%3N%?XnanKdtA|l(9BbbaI=P1UBR4Ka=ZB+T9f<&^ z6D^m~lS&e)cpgceD&hT0eNu1~K$cK_RvB+ED+xPXO6N$ z>#s%g{bJN-u-WDUl{bvt2Yx3ED8w)VH@^f{B%9@^@Fn%5A)- z_9W%&r_oC`;^-!_G}_Wk1!>HxyAxMWuF+ zF1}BQ#%5XZ>*h4fg7tJ~Tb$bUsflqxtlUVK3tH1!MuMMeAQXz%_#`p-K$tmGlV$Rzl$Ey}f zh^@*_;lUarpTyH9C-zbH;j9cV)FyZ|FK)VC>IU$IUiK;vR~_T?!n(?>w6m2T#=2^f z)d=WL_bZ=>nL_bz5}<6#{mzo}cX3lgYwxtU`jSh#wj~54?0_x)Stxi!S6@xO9+gaB zTPsV%QduyI;AMT%v|_1_X0ZGqgW+V^1*qwf9ovrPp@>@0k8jp0fM%dasfPk!EH_`0 z$X##N09@niyG-G2w|(7vvJY%Q zvy#(Sv{h?kb>^7Kv_k8ncoErhQOnTkzOrs;_VJLxbDpnZ$dwZAZ@2 zCNI(Hh}C>0r6iY}#Sj8lO{B|}74l)1%hAo36j~p=X4}v;;D1;CW-bFr!`eBaO*xOM z`Z@@6G^meqXQI4%Xw>?onjF25ZqZm1Xusl)eE1j)DhE2g*^*b_el|V#__qtz;sJj>BzLo)8v=O9u)+sv)2njL!KhZG=TrzA zI^FDnEn=JI@SbS=eUM%G>wvXu`H;e;@T{Z&Fz7iB@ACpQCS+K0o5KBwn8AgpPp0>~ z^}2Ea6(P=zRlY$Y9l6;*W$ufyg%BDVrjpEkzMzUnINXxX81qT{OI%Kq+4aqzYTgkP z++<1-q?=7*uJY&M)fQEx>j`@Pwr7~=T)9BV5(=Zg+=?3a4)t-eCI(~mQr`w8p@9@2 ze=X~^03O2>pPHJ&4Mq-)#ET~r&(rkcJ*L=mtX8~7{6o4}B*ve^mkaWRbEO>cZpr<< zmED5Qj!38fMz6)xJq6(+zm93|TQ2^(0yeU4XLqV0G9N#@Zp+ax(cbTfpJ0A^!=_8f z7uiR^_L0J=!bp1hlyaqJO)1xpD3X0TbLnNxm||x6Hi7MR_N!o~GLXijgzDJ@PrALl zV6;{)B3jKVgHWtif8C8tCDHsWZ#U?wR*akaHbO&W?A*-&I8)tIMhKOOqc^SKYPn_K z%zl-DH|~G|oq?2fu=D@)Ox{*uyz?a==&!zbOnP1;GOH2{&Ss?>T6}|Dv&fe1irrSj zZ6+y3nva1!3K2n7hq3WqEr?IVsy*VyN%$kv`E@8rBW&v8aKYkS7`4m+&GnmnXT}W|U4v3wxTKLtaZ`?{h z3Y^=jCdXLWcS6g^sG`HLuPk>BZ6$j~FxaP+0WfvKWX)5#TW|qBv z7+<4kM|R=9tl739mf2R9OQij+l-&r~QL%p8BFwS^S4C}xkk~PK{W|OgAjxR}wME}` zm`~mo8xKJb(%ise8!2Z{oJ3Eat@-dSPGw*n3U1O%N}ml6k681$^an9kB3P_zthh&= zj55h~cW(wd6;L)8M^{9oAfBY%?1qef$gap}RAS-v&09QhvBniYv2Y=WY{luzof1QD zk-_pVY&Ju=09P2g6A>Te5*TJOPuoJP^=0rQ;~qad*Pr-aZlM0(Kfh=m%*wlXsyyba zwh@_SHWlkQCy=MqUd1b+B7PfW4*vY#X+fgv-~XYfm#luRI~-pcUhHE-DHfF1aJu?pEQ>hfC1hS9U|y#QA&3!e#R0{-~$iS-psleo6K>}p$n3-rIH>~85dMqmU&)vOpX7KO|X?};4i zXzI08O@wNb5HP3xe2~B9llD=$N04{iC)^cIy|H^E$n3qdDwxP+l?rG0ia#a+v6$QA z#woS`4>wLZiy7nQ!U;#QJ!72|>C#-B>ziFuM)fUZuED<(JFA)0q zzw~g{HC={d_+bNAnRsJ<)o5W0<;y`*zsq@j1(FW5)(;rrr?2>tVSAZ8j$Nvn$vaFakS%^=O%P&3$*YkL#mB=aO>o@|F#Bv{G2V1d zcG_@!r3XiZX4h)eZf|3dmYiu+Xq}&ywsCrWIV3{Y19o`tn*bQ$%%M%*!}m0zDcsK5 z2)AyT17fjaGKt(S?6G^Xj+66tA+?&7o!Y(bF2f|eVvo;r>+;qLOR|n3nY6RJ}>fK+L_jj_=rLAVYDtyGB+F$nT#f;`+&8_swY}1L~Ee-P*CnxM`RV<=OY0n11q<6$CZbh*w_CfcX$dICGE4 z^6bp8TrbzI+wyStrkWJq&At`x>8WqZCp|O6f#FM3FW+glDZ^o2kM?ufm4Fo#WnDVT zEO1x}wW%D)Y!?b$EF_p};ONR5(pm|sGUBpl%VZ}zJ1BC`s+6+Bz1({4eec}kE%kf1 z_3qrS`DLD7!(%be=N6(6v@?eQ+xqg`v~#UK-_aJtwAB0_9hX=Q z*idbj|c6O^p$Lh159jnZAW|YYVGk){%dd>5l^K02_ zEUjWhO!N<_{`dOWHcJt%u_|d}?FCWreCJe|WpDurE!DN@O(PdYnnm{@1RhO7=+b#k;f2tZ@0~h5Pxd zm-5Q2#}?6M*e)$VP*Pml#nEe-Y?QQPTZyq8gis^Pigyi&ZdD7~?_i)6ok(oZ&(B7R z**vXpMq({Ar?;k>?6>+?7{ar;JQr2QRH^dtA2j4_j%lYwonEV}td-c6K#$r8*n>1y zJqZfCj!iD<pb%4=ksxW8bS{FLdcefx!6L; z-=nAJQ=i;nmi-1Qjq_qM+*hp){zlm5TH>%5l&YKE_1nxYuQmNM)LPwz;5OtVYEoUr ziEJ`L+o9IuV!{Nve>vGo+OCL|Y7>K_V9D98>Qcd!mpZ z8Pk4V!Y7t#`&uoYyvN^tCltL{NADE){mX)s_QE7WQ|ER8QkCpOOw(c>?u)lj6NpP% zBhH^^M!AgT?)(~v*Td>U#L7xo2iu}P?^O}J+$=E5sx(QYi>dB_sINLh`_yCY-g{iv zKK^5PmB^15)nlfVyC<%-LMgb&CZ!2%ijC0`34WT1N5>!zu2eh;b*F4fUBsnUaaw$^ ziW1-0|7zO*%X|7gd@k^KY7Zy6zh#1Q>dsl8W;ksmH}hC=>)#oFy{EU1i1$=zcbb9J zBDg+l``+b~f9^Xk)88i^tJkmR7g!L_d2e6O5@~Aw=PQbHve#5(QHJRNIrF{PwH;rx zGnDX9HK_z&5}*#mHY!@;kKXgkPgiprtm08kc_vEzrIUjEF4aNv8J z3Zer;Gsq(@)dwL(hS^nrRSAa<0d8g_21?vEPOR3)k-I)uKewn*u^$LQ z3_u}6a9nA>)AUw5-ENfp^TL?|HLa4_$v!mMHDf;qsCSC~{BqxW7;v$$xD%)i zc|_m$Y^0xTH`2U+jaYkqou_MJNf>W3Hpkz_#dmTC>H*C4xYm5~{ zW&}EQL@M~@K!V(Ic!&#<*HT$t-b_0cv+;>{@lIxmPUzCQdPjz&id!G|K}FJmnyVFOy~3?%Gl(L zrG%Q*8GPGhEivFx=isV7Qr{U*1*TcU6jvwtG@?U?LUE*H&|He-RYSf2&c5l+w@C)e zs(6;}){FNl77xd#;~P~`@uaED^`azqG7|#7XP6{i9%GVBtbN82xwQ5Cw3Tl#-Z|-B zF~Qa}SjITFtJMzHYBq)9twaMtZr$wMd#CRDR030(hAN@(CkjTcaryvgyUiW~RhInu zx?={3)ogP77`Dx5m7`<9WH+?p?tXg}+B5HFc?eeBjZ8H^v!iuhj3lyCX~qG*?P6Zbsf)4Ab0Eve|9=1ZO>bHWmEXPir10JJ*=foS^`)>hkRh+Z;O)QP zx@qfcBaC>OUGMsVNtK+FvrqF9n{9>a_Fxjp*Oij@{(92Se=5AC9bRK2lDihs@Uewx z@_qTaXX34c|LMf+caCC&_#VupR2CIu(-fwe@+e7<0#Dpu^>1RJR?iY! z>UY_~`51Kii^@WdGy)S0tQyVfq?aD{xW{neibn!Juhh7kI2*4@qO@{~DVClGeY96+ zBhHMIQ8!i9pk_lMi>FqY3-45b+?XXPKsM_)_X+LnOV@Y$KUdt`jKtWXRW79LYB&#@ z(SX}-UNwqdcSfewc`wy?bsVl@K7PK8^fg%BrKgW+1*0e(I&uiP9QDsP$v^S`^&;^{ z9LP=H(Qc{CtG+~crZ+1i}b{($Sx#&UNT^h4J&vgJl)x>57x^664ILgbX{ zdbP^^;IrZdyRAGhNZBE>4HXX%7`-q)*kr3~mI_(rnZG`;92&jwLkX9>H*Bi55_LND zd9IAj%p7}f*dlvbu#A=de(obbu=@YqvBYAu%0nejnA5J5sK|2Y`)l7h+VVPBOse8MBAut7p53~tjj-X9#FUoKlKA9xN1COH12;!V7Y?#Uvn5qC<=KR`uQvu6%{{hv9}h;4 z=`~m=5Z*I&VVXAdn8QsoR9REl^vhGXhBF-E*q+q1g;`(ig1c;DE)?+*yZ6F4I#bL6 z+_UnVnuY#6@E&e7fg?>{aB*r2qi~D7<-JvZZxp#rW7!>_a3Qgm!z^tSQvFn|8c+E& z;a3+EqC1dR=aedA)u|G~dm7zXYraZ9!rP*O?lBZr$hW^%t*016JVuMoX`FvK2_`YC zOehM9+c*}g1!hr*C1+yasw^%C_v(cKQp>fl3bnb%SUhzPmL5uIowRR*TesB`(NG;P zZQ@E|f;+@Sy+gI_d^;6ze8byLShbRHKC=etEK}6u;Hws9Xq)3cuMP6*KYY`qAc?F5 zqZm%TgRkr_Mp*LlOz7v!|3lfEE~~0#>!Q!1f+!*?pje;?qKGvrDhOB$Dv0I#p9W*h zoOAD!C*t0}87pF~WORFP?JIp*FNhVk7~h_vV@A-P3~@B$>gfM5#B9HnYg`}o?OLtF z#B%XHSBTM4F%y(?%@rz6} zCndYHoJlppekZ!tWOLgphn?GfB^fzaa*X2C9_LH0Tqw3G_2+#S>mlw<-T1}51N-1$ z3_soDqn^SnKg1MX^qV@hnwd+g1m$%DeSj>~^=}QGCx2FJGFuncyZi8;hq+N$WN`W& ztbaj?`rzn2HYK>J>_NRY_vo3yf6!EPFae>zLiK^`vV=*OI-TJD#v@QCoO4n>MqC)U z2|fG497}yx8Pt=1PG`}l_K!^K=XAOi!>F}{f(UU6>23YqK0OEES+@^bH90K3f za>k{SiRw)|XWSCJy{b(>YNn)hSF6FzbB5?5r*r2`*Gf(q@DV{dMO6y#)BBPQvuh~t zJ}qzBHhrESGxs!gWa+;49NNI+Clq@=>c-T=XFk{aaphB~x*N$7RXcljdx+%prga|j z6c~K_LEejK6Xvt_7W~2!zn$K6h)`P_3{|ao1$`{T4W=cNg8a+e)CHG%EzzeAqE%4ya@=# z2VKx;$v9tW{0<=7_mVsI%-r#Ommn{<4eJuk`3EYcoqYfm50@+E8orU^R9CJoAa7rPjtr0L#ntJf_}UiK&!!)UvytCGA45C5nmeG zX*Sb*1VaPbd_foJ@h}hn7yX{c3ybDyOt%JvORG!S%%t&rL&p|o3eSD}`ZbD4rvIRvR-Tqdp&D8`a zjBq@zXU=^d0%T^2n0Kr#{Ps&7Xm7Cb)4M_B^`_wuz0cJv_L_BJ{l9@AUTAx`S>u?=^af!3m1yCjwUk6p~d50sKk!PND(x6q%RMzF*{e+{JM$>dmj9)IU6b`*-vFd;oLHPO}%aW*PK01nKU$~_m z!h8m9a;WWc#dn$A!XA9F`nT`oR^AKBa^gZ@?GsSq;V|`X(Q_Xw(|>>{h%C2qP2Gmj z$ig(*{Ac#kT&M!GXDM=i${IkWYOr#yp06&NZP}CkNBB< zgL(~Ld-eBtSrHz=yq0eEORsg)PT}L5tZ`~~mzsF{ZVqkrO|&b zvsTvr47e(S5wmGoKSM)&mF9DUdPa-4gNsb24$X{jTl3C^&aR;j24F#3l3X0zY>QNURm!oAjkQt=1Aqh1jL_J`o0*hm3!OLh zQuX$woBBf{Eo_)KB|M}M^=ft(Lxt^KE7az$lYmQb2K5UOPZFHpm9OudN2kj`fxP~B zI#HiI_-P#T>Cf>zZrSjOOu$A2)%X)nJ z=o%*=x=dygd$3ZxP3Q$xNg4X!!(`Gc=vvUejq~{oL4&pIjBe!Y!T@kV)!acsac8X+ zlur`=TJJ{*sV|)f5vd=}Gz~s~WDF!}0H~CZWaL7ZkN;1v2l6`pJqD1cO&VQ5vNBG(UbI|T9!0-e->O7W0>Q^xTTOenC}MZ4O%&wiDX zO;uRYaJioon@uD{@P5!!0>q$z%guBT6$w5^QpC5FK*A}i`tqKm@&PVnxNo3^0R#w z&!wkA28S1nt~v7{u6b`xSNQ~Z<&H>ucw$EG0;pB?Oq~0vx%=75t3(zOD6^x~+$hM=tPI$Hg(BZAB+H{;=a^XDB;B*hEJ$Y5;#nuV zHd~ZYzI92pj5W2|44pOd$qhkp)kWO8{@u#n*ltDN8FVP!0T7@s(kgGW%y_-*eG;g_ zXJJp)_RjoHl2*=R`=G6e2{Niryv|3jfeC%&16w6DTLz!xwOg&U(L)Q`Ii#JA5xqCl zoD_tw_Vajs`#r;=9oKMGmkIsE89MaUpv+q)1oyGC-`N>N=lG|%6uUyZzKY(LT!XQ z?vF61b!>i)6{)fw+}?+ljo0bwp9gJoi1qRI4Ctxai{|ym2h+uic5dZ1`672mTk!O6 zsFsvE`@`6N9l)^0pUFoGwGl^ST=nbYxN%t+~xnXqiip8w`=N61Y=W(e5QGMC;mFB1rB zIn6OO2!zClej^F%X9m8#HaJYQdFvpZyTx7yoo&23QZy?orjLwEITiqHqV|MHocu~o zY2vUF8(*P}$a%eVvguuB^drRw#fn0J$dl-M@_~8C_#_=CKXBNdk37`8-%kG2;IS_m zaLN)gkxz3!KItc6Tzx5N4C$#9Y!hocHXGIhKoPIHmCukT*C*=Y#%C5LlsU~rxE_*Q zFa+%yMkVvkT4bl+foF_)4wZ{ZTKMjoM;Cc|29REhappdf28@;|7VX$;Fjlsgu2CxO zCwU}YpE&VTI!C{+vjjM*WF{n4xPMq_B%cHS>$BB5HbztW=M%f@O-ijUAGnso)#Xvr zg1)2V+F*hyzmh`(_PSaw)3*EIlID+Qb+!Ksh@=< zX#|-`9d|cS@g{eM0oUAc51m+eAA_$e8@4vmN4|2%797Z36IbbzYwomLYqTD_Ansdb z4PkwuJT6(Zl+(cGA=ki%%D%&NKNwu39`FW@DmcwHB;Z!{k6IFvbAdVuE0O+l{IQm3Hw6l z5-jA2hx2;$+Qstu_End&Hih*Kw?vs^oJl5!U^-06_1a~6+`i9i%P>Le(b8|@CWMcc zwz^8nhN>Ifpd4honR$ndb&BH48Gi?Fs;kqh=R|>i;(=OaviPTRRUbVEq#&nbB0q!Y zK9vNWA_N2UvSXvOt9N9qzQ#MBvc7IXf>cJvjZJB~y~NkO9dDf?`33i0_x*n%;GsC> ziP5_o9e!+*y6A98k9vO~bcU5QITcS94|s3rW=%f&>`Q&pyZrs^LTzQsKc77v=atvK zop1l=vvb2)<;z3yT^e%l#dzMHtRHE0^LTO;CJug|z^!BNopW`$Xy9A0v-*L2t~DCJ=+{b{U1iSs>_McqZvpTgzbW@|5f*41F7uBt)N#7A zJ)G#B5X4Wl!Ys~Y#myoqjMf!$UVwc%n}T5x@TB%m-fv0^mizyDygU0dXF*`6pq#yJ zx_WXmgH=LlA8H^mibGQK9{L2tu6lZ1Vjy7nt99AlSCn!%u>BCP3*{W~bL_e?i64c~ zu!{HcnQNlH;AHQYOnfG$8wJ*1qzbwda^NcFQ#v`5LOydVRg$z<*HR zpikMYy$m|oAhG5afspFoH^n!xkfhhk_IFp2x2n{316}OjvKb6NA$*J#VSnD(XFgRg z-A2~}sQw&iUV%+Wfs6Z5LizE{1-;`r_}!wD<{;ce0Ay>B@0d z84PSk%e|>x&OnWxiD}-zdyLPMpzUoB1VP`=d3?MdK`d{}$h^mBKQ>e2m=~ZKC{x<> zL8fagUNCB$poL00?bl4H@cpfJ{zZi)$7HNTMoxCk4ndg9)LYhAd(?Opa^$m(7};x^hs zu=Xq)f)^KvOi`)x9$gUh8I+4w9@cq0r_3#+BhRXwBSDSJP@8r=oEcnXu4V!aZ@KTwK{h zwXTOTB>AhHfr}S{S48I zg>mO&iP#2B#ANHmRBybM^s>_{0DvE<)Bur6Ki$-yH|90X(Zb=iN`bKq+9PVR)tayh z{tb37&3LSZpg4PWp@VU*fQ>s3RMB%Y$|D2l^!O!Ej;^q2zBYfbjNsXCB}b^6cRN?P zm{{L`bW1!jLmNZBRRgFlTZ8Jx5?B%j+!|y|rMJ-5q;uOic0mxK976?bw+_bY{^8$Y zJCkmKsDT@udY9x~M$Nkb1)m?>DhVeq`U6Z^N;?*zF-tLqyH!yW}x`n zO$bEMj*gm;C>9Yb!=Z^NOwQDVbu50LGuDpl54mdmbnT@k`$OhEsPUm^0KA8OyywqE zdwDiTulD)8yFTj3o7LIO0gK6aJzrxFBUEkgs@*Qm9Q>D^X&)~k(d^{zjiYdWk3Sh) zY(baJE}3f>c{MS|ypt4@i~9UbRu3cE5LUHBx08N#LDW?;U%5T)M?#0}elqT^wfk}& ztsa!}M9QFEbSxiwrnfVWnw&uM3C;^?rXAovx89CKPM{7*)!~~uhdju04jbsJtipM| zdQ5tx+YxFH{|LrGyJHadZk-qzNyE|%;&$L)lxU-^LXdGQ6vh04RQ=!_DdTdRAU`bE0G&; zZVb!xE^EpFk8$~b;7>BW<0t&LlZ8YPsCSUM!3GLQ%cuXnH?ANzi!{f3&%&Lu@J z6;G#!rWb4X6y%dHXhnW2JA9{JdBf14f*BWl89?$u5`=fd4rHvxAikG7U8eO@QH4k@ zC=h;Re5Kyr7z3b4BniU77}4)1*yZ0>l6wV2B?K!^0*e>v;5 zrlRfJJr%LxE{3(#|Nb`4Pdr35xNqssCV>eqaRazjC9womBjk=vrptPMzj-mha*vuR5hF`lIWBAyv!($T0=p_l$Q|@zUm=Ac z4mN;qB1Lz#R>55-J`RZgV(+^-u6NQpCE^+O9V7SYAaN^&b%_H&qF)`&ai6{O@DtSH zpQM@bjsY3-tZ@~6HES>1t509<{=Obx!3;?eFj&j|{(D{u+$ueXZO81bL8*A7rKy*4 zkAV=Qb8oDfjl%a=DLbD6T$B~uI(n|7cxjsi=%6H*g3~!aPPNC3QJbIHoe14mz7K|pm_o2F(8V7$UpHp3iw!XpXDh<_`e3s{;YR9ru?lFo zDH`whVTkVCda-u-#QB?BG)%PM9)HS;weohqbM^qSrBfmE3g_%;BR#4uU{=Ywkh*nt z#6|Ol#(FpDM7@@YmYZueSDCk>AKaujtTH)69Szo{e!cTdj|%d+KJ6OAh2(@FLg7$( zlt3d5fRSz>a=AIE7`Y4aJ{ad}8Rq!w{ghGZ1F71dE;#ga04_i0o_8YOg4XGL`iapv zf1J(EKB2m0U~j`Zl_JX|E`FPPe;e_`4kz;w91TD0F^KUZnSq-|A;qnI^FVc8g=<7Q z{4bOQ)Xe-rudc;eqyYl`pYej+|8fN&f#D?0#3u|~D5#?=D*eJs&B--l1?5bBRsG9W zg}1j9J`wza%9r^w%CrY=0M%av$9}smp+NqPz zYE~!f`5k1<9Lj~#6NCQV{Fd{HprkCbn`1sh?MBBWXd59n*rNS?0~4Mp&#Muw$EMZh zgbhv1ZH!-gk7wNdyWHIRFXL7&o7c#9WVB zC>ygc)`b%CTI{IRyKw7mp&URA-7rn{Njz^I7^h#k0=&3C0(3!rb9Zq6@6JX)fTcgV zb0Htcg_8gOr{(CGb5_hP+mNX8C95Qe4m=}tQcZ{_&WEWhKKUY{%F@t4z$hQQrt zq}da6jr71HQ5E$2%4de;sX<8FJ!`)Xy~E&lNe{LNTXLM7IEpF!A(=ycCw^^y=FNSM z1Z%)+0jZcy1zf)stvbw(mXOdRd5kzL7h+zO7fxC4Mkl}Z6&pw zvzglF?OSUMSx%SjD&Xtu2LyU!)5`w(Hm!JVJfi|qj@7q|Goz^Ty;6eks7uv4X4Y!d(&g6_jVMJbx4 zjvcKO59J)klqD1<$$9cAmX)s(YUH$uvYRgYfArLfsqx*M4J{uahFukNE9J`aCRcTa zO_=bg+p$d0We0!Ot&T*Gg@q|mcgXlK1=CgM&2{?xHd*nzO^gWJYOrj4m$%EU z`S~qYIIqRe65@Ri>QvASm7tb#p`4yFklD9uuM@AoV2i#F)G@_Zpg4=8L%Yfl1Wk#h zFn`k$)co2LX<(Mzg|(ey;Urj!lmlQ=QRBV-V%mvBaXGYpJHUuj0%Y+=W7ryj6L^A# z!%3^&UhT&r6;OztRERda&p;iSF$QjL!t~-v^EUm0X5eS^d&bIQ(EfgWB~c}q(i2fj z((!r^es1d33CtKlvr@sFeQmK$I3 zXb{qn>$jr3ux|vkgpvpJhxG1pj~c9&(-Uw6X9;HI*sQcA~(JzHfO(4_Xb?HbHh<6zM?#>9K~ zI7Q!i$}+bThOBw+cyE(Z<0d>Ttz4egd|@QTi)FdvldCR77W7Km?PkWXn_oB;)%tYE6^3p40W6AbD=Y=9*ubrB(%JP-0mx8opp+!PENMl zOee3hIe2pg_ufB=HaE=pNG@%&a^WK0Ai+^@h@};|80R0Nfr~ud6SglB;%9271|%Sv z6L9WQB`?eYa#MxH`>+{xXeC2oLC!w+3CDT2|IKa{sCb8@RjB5IL@wqGNwrF3@aqwrUlCHGn-yW`Qg9t(A#&Ui9;&xBgtby)0OsDs;B$sH(f?l-cA z>-br-c<_8-w+GR($ANTXxCJmyBxXjS#GnRz?^nmhEXxn2%j2HzS(8%@|2CyjqrAVg zWlUI>$+-<7xBgeAKk&N=E;$0)H=HV=bkF5ozpY)u4$G=2pp&VUL#Ojlc=v!Yk106w zn|BIFl5HmMf73vn(+)_CQhM}b7wc5@8)*ZaRSo8Gdot;v&#^GLIt&Nu1h_3Q=a~9N zg9x8krqw}VxY4Yp21|V+iM1eAWne@aTuA#ZnLz!Lk(&ee(OH{}j&ewMnekQ4zyoca zA4G4X#x_6Ytl-?Xkp>)Loi_Hn#mIymEXQ$tINaMI3M(UIuGhboUfRZcq!NcM$!!~t z3_cPcVBmDF;NQBtOS-L%s`X6JV8R;kYz$3nrs^VukZRXK9R;R+e0c%gE+#8dj#&2 z=_++s2bt2kI#!K;hUn@F#sAU+s8gN7nDQet*jxYexqC$iSgO8OZGV;-kl`h>fr0rj zDYUO*(48C`MAF_QIxl-Oe>GNn7mL1M=B?J2!4iWQ@#q?UymnvdI5xfL8ZHz)@(Ppz zHz7Zl4J&S|oxR^E4re#L(#b89-Ue0_ES7VSK4T_rPQW0jEQUaUIi}ZQN1w3pP7}j# zjXnKmMfq@(W;~hzdN=Xqga#yaV98x$e-4E#NN8e( zVrgR*IF+$p=P@fOo5)SS-94L_!Zae0>Zox5rDC_9gXIUsyL)LceoT@N9o*iA2fF{o z4z$yyewE5eU`S>vnR7;%6UqI&i?Ytf=#VQpo%tP($<2YER@GbWGd_%n^wfDiO|+T- z{YEFvftC*dXcBv6)+hT|kcBx~ReIU`eaCvk^$VC?Wt&V_z$Lj(jhpFqssOd6+B5ld zKg|XE9b$B*UO8~LcQ34~>oyNT77y(p)$kLu;Gem3)J_+7I$|9gd}sJRppVyts6$=f zL0aJrsG!$y-6)!kgtxjo-^Z6J4#}AKyjPI&rDv##^HeM#S066M09<&xJ{G%QE|8jC z>s=xVW9Yq}T4Xm3g~TAYs|vZ^0_L^E4m?S54)TH^5%8Ca`eU)FRBsNMtFG=7M#p{4ZfuSeZ|D4;3gQfJ6PciEpOuohn-vEL_9{VMZJZP>>?-tz+H&jO6C zS#2GeXK}5*9oH^wHsR;G71kA|Q+uje7ZOfgM^k{9hcc()H|BLWrT|Yzp%t0J;JSg+ zZ%)hjta{5mfe+Z0fe%n>iPM}1E7Rb7D`yO|{e;{X`99U!GvPc{k@IF(DR$nM{IsQQ zjn+((HfLr48BuXN89Wm&C&5YN;)O(M2baF08vQq=M>lXQMz8LXilJ3v zzK^rBI%lJoPUT`#1gVZ*M3>VZess`lY`eN7q}#-b?&9>UNXnH0)keF$T5Y5;#cAw6 zlCPLlO?nL?(+m@9dI`u(SNpqOCw65)y=c&=%z+afnX+u1x@uF%)PDGKQ5B#`sHjw$ zhx-G$WgbgQZ7*sqekk0i!&+u%O!cb7hG-uH0>~_saU$lGA&$C7^r6EL^XYwN<>aQF zmiL*jWJ(F{IK>x&Yd4PX!8SJ6Y2M<>>z;~1Tnyhlg(iqICVZ$|iEW@SET4I963 zbZSzBf)aSv*^l)A&zYnSoeL;zLcVrB*5@}jm*o>3WRHsa(;w_Kh)gR>y(#GG={0{4W(C!glI zC_WjG7ecQZPPf}M<24KyFV%MU=S?cW6-ju7VXZei*C(0@drHTMjK&4d!V3#!*8*?u z=W>C%=aQw2?9&+*lf&FkJN<2UgOq3c^3+^K%nA$`RAa4oNDYPwa1n-_HbgCj9=hl?#*0k(`djbd2X&W#Ac(z zQ5dxxadkaBbEm#Z!$0CE!>>G*YX$ys>6aVz_w2fdnjO;ZE!~QQPHHRhQ4Upglu$?# zsd0?T-kyx)?gzK$qpa1><-J997b8%;%su$ewe8xGEZ-^k&$f z(aS1S?VRb=*ND%Zou78cld1088Glgl?0xfNIE}I>C%*5zwOExd&3!j<3Zt5JxD@)_ zX!6R+uquhupG#WQ9@ig59{+B>BDoW2xjrR049UUI78pu5PW&QYg%!CDRAYsw|E?%$ z2FtL&d@XXZWE$3h1iEMrOXpr_*vMr8|5EbOsQ1R`g7ROqtJ{Z-%lpd z=_LWIYLqRaPVX5c-n)rZ;&>*Q*d&(rMOEznuz;9?Du>;^!DkV$u%WqSj!}} zED2&uwCi0L7z1(mYWrupZczsev?SMQH1I8;j;Ri}^6AHOyQHA_I8~h8@5kczTm9Kd zK#IO)o(G@@A|dWE*kP&wzxrTR9U(&_yS@HB)KaH=YQv%Ki;hFyPn5dXJortIJjnPp zv5u=p_5t-`ihMm7URmZkC3H>{>!C+m#RlS!N19c-541jy(MuuKD4pQKa$z*Lz zzZYUG>d-xLP=o8iF0{ERT_>8QgG4xgD=?P_hs5fke z86GnCG&4cAy%?-Uqh)B12q0NC-i)w+h#{1~;WmsXSHYG{K%V&oLUWtV5}1p;_b&nP zPM+YiC)y4Ez^-6mQF(uNH1nBF8H=BHvvdt$Qgs#Scfq0mI7*kxsSWgi)SzuM#Sfg3 z&V#GRduuh!ooZCO?I3>FYrvsq#;N4-`$yo24^*~@I!+_Lrrx9HhecIwT%%ZV&2|YQ zN6%mFXIw#Y;N6CV=QMHr+L^mS#?LrpNl7TUQfSi3pFT5;YiGB0biUriCp{=`1r%}p zKls|y3Q}#K3fw*?Co%zVOqQXZXRSLj@2L1Z4lc)5`yrh~x6(wFw8d2_5Z#_q@zB0 zl9Z(N6c`7|m9TcyJf!5ZGna5<&#d}DG+{%&$sd*IEe@`uSZ+d5C*=N|=}pO@&Qfat?cfG+@|}uJ3tZs0z|X)N!0a721_4Cl9MGGNW#ZLce(@A=E7@Z=C#=8CNdRpK)Q!-yvy=AmpWtO< zE78UWfOF$T##+oA_TQZ`^r_KZA3bjO)s>2u_ziBHsNGi=A!?W}icM$Vr_wL#+ska{ zy{46BdSGTwF6r;Se`__+D#~L4eT3Y&($|tww;rJThc?V+7(etkfdKnQA_3%;7KcoU z(=D1Z+~UTje(z_%jKb_E42Tx1ZYH213)Vg`N)Q&7#>eHm?p4)g~CF<)`njco~)8&pJS21N>s-@L#@0oC;YsK&)6ubEFm=JhffKF zrc;l1p5+B6?T^;pS(t=8ctH7ZcuBM84p6KTp-a9;44HU*Rvgx|%BvQ$b~7K^zbaDy zRJLD}6qf>9Khlp>P4T!OK8*e?eR{fHI}VCBpu@t*nI9w8mGKT!w@%QV~lA`-p#XDcPsT(Tj{6BKB zD7IshkmiR)TY=es!>q^k8$?UZ?Tt_OKM{A5Y=V;GcTaf+La40SLkjY>6>;EZ!X$^h zU6Qc@=%mxf%4txm5ycS+LtCQ}zKD+2=z8)kQN16tQ29QFE}u34Jq-#()zGGLdO8*C zHY>F!r2yc%Oi6zC_Al#1!C$pj={7%8$oVXh?_@`=<`+WT^?^vXEhkwGrueRuY;4`9 z2o8YX^dHFhXk+Kj^R`c2xMOw>{WKM}@k$gvR<|hCDxQ8%_$=}0;tyr|JZ*{B9n@JK z_0UhpWL=C;ZDGT_=E1F#EuIYaZjUbx&bC1F8RkEjV;om=;G^uO)uP1N~&cAp)hj{UAEXuSCp zsMY0(U$@}r938Nf#Uo{{@KV0hFTGO@=8`$4w7N}0Sc08AQdu6(mbR|}E&J@i%3XQD z%DwH#sYvQlLC5MqJtl;we3ajIjLfUNWK~hcMQ$2Ksuv0d-4`+)ojBiw^+t3l${c)}Vl4q{j zrMm`_bt@O0&1sqWG@zewpK-~`r_#>SanoN=5ae(fQa5 z9#@DpUXQJGFTLJ`I$E;M#+LvDC@TtM_66m<6dH*>WN&brc|aCJ==r%-T`Wts^D`)^ z03h$$@%SX{D@yC3>lBk91KeEh7ls9a6o>ZxlU{D;dRh}V_HS$fkf_wx z3;FFUk^NLf2R)pz&NYc`e|f9KM-3Z#Slc$gm++B0M!g09g8ion1?M@&%l(?v_OTsT zjPyv*UDNiKowXbXZ$YK;b7g?>yCw>0k^VJVVMZm;ft--F zYIF?)#|%Eh(kxJdY9_k`*ITB85ci>Pkltd9?~I1n0%OY{QY&R8F&$4nq+C;uWjkhO zAG7F?MJbMU?22+vaWMC+T9p?#pk7d(F+VTTY#cp-PNr=_44m{pS}=J+_-b+%a!Lnq zb68~)KcuX}>G~&0to|-^p`TJNec&xqw4nP*mM6whAN*0{9_DZZ zAEe(K_}sk){p%HyE1YG4#4bq*{~YHd%R%e8K>%sFh3T8^b!`aP1`@8kdStHF4(f^p zNPH@k7mx)tth%+FIY07#8Sfygx4s>91rU|Vedd*!m{Q4!+z$zaVJY-ol+8YPOVy62qN43Yuzmftce)%M{W`PgK&55mP888J1Kft$BiX}cw=~tPa9!%=bD;hh0iydgSJ$jWV zL~b*H)w608$1#1pzXIGA2XDsDp@#S+34NZx#{``0%C@K`6u;W&p>eBb=9*J@mp}Uk zo`~bkceMb`!q2tzHeW9)IIj+C4jDj*PE!KEP(-B!|zm#^PW$N1JvFa!&koG{_~_+s4?ab{zS<0>%|iWnYy{qwh!s>Up{69> z{ud717pvWhX(^K#lU$N~9;_}FpFRz$o<2aAss3(#R>l2xbd%7CmIYnzI^*Z49X;oU z?D$tZBlhaiDx+?nQc<56qYAyKzNAB@a+}F*{o^Ue;bRFGcc~F3p1jYXo5Ol|&Mz~w z*-#CAMMfCZGlJo672Yqa9^~OIB(*2`x|zH^YPakTQYLH^bzt~x8|!0bLxrPSuXaw6 z32l!N<>+`H8mZAIn%w?K540hq=4k8iSFw!byt_|vt)KUgw3XQunKSri6WdlJ*42Gv zSCoT=(ujpC9#PYa8x_pkw(sTu`ZX^Qq^45_{QvDoh<~-X ztk;@N+~m=+z9IswGIqmj3_Q~Ip>z=ZLY9^=v*BHBZgkH!E zpWaBP?Y(}Q0w*#=|HrL@f&){m_%Y~ zmfLSz4EbUu2-l#7Fua~i*~x7A1eg?4VZ5{)m}6W1R?4jfxHsl8#;{^nfYGE*NgfJ@U6%2 zi9I|L*^#Z;Z15B7`jf|4DQAJzn&j_S6cmy_Dx`JUE9SfANv8J-%&8$9?P3rJ+<3kh zk<;5ZVlBu2fXE z&nz!XYIc8skbwS9h16+T3b=XB+m5YfLp-5Y$@r|Lek;*}9L#i3Jznx1!EI!yYOzL} zZ!DP?YN3dO?Er-$Z=sf~t+c9Xyq*h3u(+VanC!n_3}g}u&m7P zS8_`|*1>$-f7-;k!R#B)MS>hO(%qSRXpHjQOc|G~)@M-yh}woAgaXPI|Jed-@kF)r ztc*$q6HjLR9k87tF+%=k4uolq|G-7JQMhM$Ml@n0*{bvkPb>{Fr%L|!QYbxIoloy% z1<-)Ig%5iz)jsp0^*6^Mx(XWgZgu>J2FwM#TOao|GI7j%^~1-0$NQC)ZR{7IOn-Uc zf=;HFh0nRa@V~1x2jUTKq&Y_jMvl)DKwnrfNJ1!b1%egc(dy*5 zH@U*xkDu^GG{p=y_0X9W>5{h?C2{pqAVB_&@o5ILHD&o0(AVI*nBK}N93 znINi$$zz6IMhYZFwRa&$1+~Uu1rI(Rw?n5?QmCUjP#aP$KX*L&%YV=#;MGlDxiLBL zc-kmvVxV(=Oe16OVqZZEX5Le9%S$#49YA=6JZz|AH2y*NL!v78F!jl8Sz#dZxu^~F z(W*Ke1e|^zy4|}Noo6{cIG6vpB^s`AeqvKLF;W^QyS$9Q_RC7=k5UnQhNa_+7LB7* zj)Vk$T)0xuZqY*lW;RD#^_oT}YqNa)6~>HAHxkB_f=ZSJt%l}-(Ub3+J!)MYeN)Va2>zl?>b&r3%Lf4BPtwz4t z$XWsF6SxwN$yONRp1=K7%z%=znS_VH!`2r>D8W*wJOJddg&MPx@x*<-14Og96tQia zl7FrCc+WVt^!YcH+igwanGV_k_^~AHg z)sHs_@>z^h@X?5cSw8Krjh+|uD!!W@Axe*E_Q#-Q@^RQXpKWj(7b|e&*JaQH&rHbK zwkVhnX3D}kkqgL1D~jhsEx!YjW}X3!2vrWfT1@n+G332a16{MqMYu)CO#Nh-hR|~c zZi@_!)Pp|ptGume&I~;cY7i4~;vuIiA%ou!IRrBMPIHv`Jir~x*2FWUL`rEYTy-yqWQ$Fm#Sg#m@|&6O$-*vG!!oT)RJ*(TvN(`VHY_3CxKNdfABy zMH@9^HIB`c;}^IrZc2&V=iqfZm@%7tPdSjCH?QX@Co|sbG}uJ~BrxpDQzUa4w{*PA zT!!7pr*x=sU=cXYS7^tbJo(+Skj)0}AyzAx5Y> zK=ZV_s&AqOnz3?9D6!qk@=a2MC{zY=daX%v{jKk!u`J9vHWO*8wTA|n=^6f!ix$F4 z5K0S4Fbt-S0r==j!+4PxZ(vc3PoTI{97Q4yFw_o{usOKJKWJzuvI`fOQi{WsI?aYK z(B4ZWHxi%RPFAX!|t*W*8ZZ6ZARu5u)QXeoW(}i5JHSW&V4=Y{7svEIERbYf_o+l5> zn*rbnA(GfbO{MI2>6^7i*3B3F6iFJmU$vJ2cchPy0ntb|p3rLn;IevQe9LHqrp8}I zceW&yDaWHErxxP;If$;~ui?D57MeX{7hVeSXkf-B8T<|t=wa(T;9>}qX+v}$Ivc}O zsZd>cl?$;08x0fFg`?ytbh(6T`nkD{KJ2lBztDWv``Qu9y~Iwh%|sHvAfSvJW%yIv z15MEO>qHqO$k^_)X z@DdZh5m)ubW4%7(b#-8qVG4Z384`8n9~Dw@bp_V^V$*QYZ!A)bUF~F; zU0!Vy8An81bsW8U@QW{`Z3(U85iAFCp{LAGs=ETnyV|z()Z&}J3wgRXov+32C8$(< zY%E$_&YZ!$mUWs|N}!fAH10CF$8|WKsICl}?bNK_ikB0rme>sC*nLNzRv63&yxK3E zGYe}~FTHU{3_K!4_0c)@JMSfms?f76=s}~OJ%f7bc1=Iqft>(i9Up)9%eXZ6%TgyP z_h%(D1$`({!hxLJd029KWe8i|fcN3h3_CXknIH*7kL2 zbXsYZ(bs6hc8)8$KaT4IHW5k(X zJ#7oRj8?3nwB4Gfqn*NUD5IFjMj`VfU?rXE{Mb~t@ZD7??8tk?Pm*UQPvre*XL)NX zji?y=dP;>vaTstZ&BYhKp#p4lCaN&21B!Hm4tPoXNmb@Q=>|j@jXG|r=d3Me6{A5_ zH@3&ho}>|TdK|914?P81`vw!)l^?)L5c z>;H$WGh0{{*_QRQpkj|LioIe3*taN%*bAtji2D8~z`v^Y-sfgr%+<|GlgyDZVtjFZ zcvW-1A{KiKZnx>e7MdG4H)~x=44_ zR}dalM{WxfqUyoVD4Tq6yTXiN9}@v5V^k-bi9BEh{xxKv3q@7sH4>BlZJYEu=Mb=7 zeM2A2KhiTxm3tZy8|bLl{my(7c( z5YShR*;_ubyiGQb=@_ITx%-ob3WZIiCkXvR(mwPP-Udc2Wpjp{x@7z}`+ADMHxzqn z^_b@7*x=K?P&S@wPmf(An+cCb%f3G?NfJiaUfD%L3YH3P;$F_4rv^tXCx1e;m36|E z&)kvO`Mu%zA56#=PEPy-eor@g)J62QYh?B>_r0La(LwGTE>bHeU{`O0`8^v;JJM}; zkc-u)6mK@3YA?n3RjkZ#>7@ z^vFzz4`B+hHG3wT+w+W1IXU(BM6Q)FZB`%pFlat_7k+;h6&JJQy`kSdrERRX$^1X( zL7~C)3k><+c_70EhLIERJcuA2$SuYYz%+T*{SzJg)=xZ*%%P61#bDO`SNSaQk#r@M ze{%UZM9*^c{PYX2nKaNA?*QGW@?bFh57X^b)Kt}r`9Ma(yff{;H;A!pF;!l-m(xwZ zH&8oN^SZT}Vyhbq5}J{G?}NtLAh9nRd}(il#PY{aeL2+%$AzdO-jqN~W`}V#pQ)g_ zOvf)U!Nf@%n~@H(nbN~`$H$&=59UPhSt&_Pw5b-hz*7Dd!tBV|{JlRsbRwc6Jz<)- zNn#HFkxJ0hKwaKHrd(X-ZT2Vt=|^*h`=~A?Pl47Q{O$h0_c5>EHscb#o{I=+K6m?(bDF(M*|$67 zvxX&OHAbjX@qtp(g3s+XN3;tUSSh6fopsK|R(@P_^90`WRoZ*NBrsP3-5Z%chycvzFeIYHyep6i zo&0t*IhZIQer}oH70Bx(9UN4&#;=g0jn1|}7a9klyX|y_>$o`h&)Jov+vm$LyX)7< z@uk1kR3xM$%Xc~p74BjN^@bh^^f=6n#jVdbZ{E6)A*-B@=#F_#TQz$7mpDkszFcmjBkdu zovxqXGb9!3-?@@))?lQsSO}mRrl172-Il?L#M*c4|LVFm)bNPDxYiCx6c0RbSE;tQ zOvr#=lSiuGmy9B{Yk~)=%`oK6u79Po3%c|;Z7}T81$Gp^Ex^&7O2&S=9br?OKBg*7 zE=>4V?$sj9_vYSy)5n=5w*OM+=>EmVW?m( zu@_4j_>3ClaJd;4Q*1tWKN@swHs{UhHZdKGFLKqolCytuGj4=DaHJLH&~ipK13k?m z$EVNOOoMwY4${#hr4o+c^5^zIY9w?uv1lza_BbF~!JBfuN44WB+w5J!sHvX|Hlrsk z)YNj(1-$mG;cTs=2AR}1=}Cn;v}c(qUyIO(|8=&|2$7 zqm1+3ix)xCa_bSA_>i2?{Tl;-g9(<>@X334x=OVClSy-w44cz<`NH-VKdA_t<#F8P zEGsV|s?a@d7R%b{`Ok6VcVItgC_g!WmNq6a|5!G|OTa^5r$fa2N&M}8>NbAoJL0geWFnczdhA4C zMd|_}+>~M{v0p_L%=>|y?56U!(I;DF=4Ptd7p@9Uz;5cJ=%RDzys*@Ex@j*!xpNs2XZ>e>UBoIt`Bxh(vNN z1@2^Vz+ZHP%6&S{-(;M5j(LEsChu!!S-3##nor@eL>dsqf#=2j5N=sjmA<*gw}KpSl!AJ7v#@8NyWABMj8COC%X4o|+t6}%DZ+iBD z!sTKG{20b8jE01ey6Z1Jpwi)NJuEkF`4Af^WjE<>*>MSuQ@dKBLl#kULNdB+z!WzS z$rG#`7H$UPNxOvK2e;M>VmscG`-37@y=iTG)onV~B-7RSZZ7eRW2%>)4um zilzz3VhST|Gm_o4v#R$B^w4#%%#kCn>YHYzZN?iLhV+~qy1b={*TM4X;%S=w@OKP* zfiH@ZWOd3SZ_td#D*VUTx0`}xN$rofivh_r2hJid#{Zn;$3fdv>$YB&nYJ~{)U+_Q z)5kd*G|Of=F^~8gY;n%0dnt+Gl~SUB;Fr3JcS@RSd~SvLaI6jV?qpbPx{azK{-$y% zP<0+S-oArn-kM)>9=*RXm>;WbZ`bktJTWc}?D^W}QC-)Fh)R7XgdC6=0$u}qvbf*U ze*Io7MTbkdhh%_WjpEa?*tct+UToM*61s6qX*JJcPF3&wDI$jh*Mdu5DKs=!@~iu!!9A#_0r?HJN7mtB7SB{GuuQNuQ?^C`e1x_OkbWvPjXKiF&}V;E-we;Ov=J7&MzDKXXccbx zg1rG)GFiTuI2hJ{`69v)se)dc}EhCJ2%>(+*JiY#6>FR>nPTn`L}C%B>K z&;_Qrm1g;}K6LwtH(>9C?>}b=w?faRw{qot42ajOH+PMhlzUFy-~mdjyq&7jSkizGWk znBzFTdLZ$#{)0E>lc?831S(fE9V2r8EYkj2X2CIw`0CN0?%z%F+6c-RoE$Zq)7{nA zZuGQbk-5iQATZGQ%;oC>i-EpJun!5ItSmQ_ZrrfaTgw|kn?Lfim-h@Omt!I9H;!jI zs0@8HYKHL_7k3m^M>v@3ow2BSvJxO1-`2nN^AG><8=-gcjIRGV3xwwV@Z_DbXZ-YU zYJvSB)*A#$M!T`pz3YziDHUZL-fnp>fHZ2avm+)J48AboHs|Nd1^(9g%pjP*YP&{P zzBS%Yu3>cLz*p>4Y^8PN3Fwm+d5O)up6k7}j}PbZ7et;>n-_ljzP62r&;>PucI2~~ zkKUKd2v1+X-!&hOl4f$x!6E**V=lEE`X^oG&&p3P%hwH_k++Hs+S}Yw?OoD3P_s*@ z*gB>c=G#`PDd{>r4qkz_Cpep%!>GSVCFb9aa0<*$wIv3c1@*qidHSxyVKC@fL9Up~ zbRTFz68t{>Hb~#(i>J?ApOTZ)g-(26$cxmA zja2nos(87tb7l6BTSSWaEB$_l3`Y(Aa`S+xW-QUOsf`_!VmEwx?eh$ka=3oDFI;?g!LM?QeNsGax^>p9tSMN4u z0VUc^q*V9l4ZJN&${C%3!Mu&5o=)UaRIl%OnO;RcBK37Tn+D3`YD#uCo+`6dO7PT_l5>}{0gpS2&1C>jy7=lq(I<;Pz)LHED)Mf2FEAMMO7ZMESP7Xl}2zOzB zY{}#kz+U2EH3R4#z7_Z(3K_UQnN)c(0=U<8N4v4IE!HYRP%n+XMczj@eZZ%6lmF}W zZZrxZ5;vHO=v^}-u?m$KLG>>=SwNG}UKXZ$cCoI%)I^*I^)4i83|y4;?rMsJ2O zqy5vpD^^}F=C7kTItpSr5+h*rAwP!B2JO02yy{#K zxeK0;YrHfaeYxE)^0$k@W1GMHyKJRKYtH6ZEXKd7rYi@HbVxwZ_kl;?Tw;?pdo6TX!|-WVfa`&Yt#0(h*0QA z9EW~n;NMiD4w}0K0Osqt5j8m!EI32n?SWGB^?c!t8LX?-Zy2%5t!_ELKbF;mpP6Oe z`QvHJ-YeX6GZ!K=xi}nnA%w}BY3^pB{6{wrfuK264){*a-OaI8;unLt7OxE=M@5>` zIVMD)hfD75#E!`Ys71f&nyNL8C@-UGq5V)Y-4Cb!`EIVFq{h&kY&4}pX+=kZKUjxw zsUVSLo?q<76d`v2pJ8ox{;h`1U;L%M)ywCIGN{8sPsYh!%N0^ev@p7zQD9VW|KwK$ z`W~q;z|p%UpQAE8?{Xa(^8==~CR5Gm_((TRi$fy3eEIxy;TA`Y10*glKz*eIhJD5? zW(coPKd0G!rhN^a&hCXfX!@MVHE0b zMtHX815CRmPJE|WGB{u7)_tHo!g?d5`&JlS67~iVy+?u57S#h6B+fdX{^J!NT@??P zy+8pkxUV?+Jfinrx9+tQy2V${yxn}<-AS%kOK#+^;CTe2i-503EkgGJODAL1s<`gL zSf?CPvDiLmy|U@?RPg1f&Dzsmo2*>ShN4IPCM*f(uk>gt7<@oKg(1D!!zT`wiwOVtJF{p$iw{28ze!ptC;%8>D+W`4a)vPVL4PS8N4~7@DT&HP`Wy(aKJ6A^*=~WOHnr0on&A(up<0+6dhZ}2#xSp+fBu(6qGEvas&7(flnq}#6N9{MHhV#C>+koUf_@ZGgz`U zTbV2JZa8o`F{O@Ip1Wf$w+?@3lYV305RCNnL2n0#^2uH7D|u8lo!`{HYt{Q1Z9FTD zn-$p3);HmyE#iKYGux%)cJGTAb)s}gNQ9Ax2Bn{1aAfIcsyqHbG$}-K zWpc6T&h1vHpw?yr6b%0i!@`qMyKhF5CmIApaVorfGfdzdy|{6Eom_XtcDlOUP5EN< z=L{J`wIn0Y1_rop1oNzCN&&xxxPYwhSzmW|^*S+h#*qf*66z_lC!Q}c`U^69b1B5a zBl1QdKJpa=ne}R?Zy$N?iXcj)x#;-GSFDb zi0Ft#rGL(i`~aSWWWz{shYaE^K8gQAt^a8}l#uosj#UYtc3A|sr^3~d#^YjR9rC+Z z7MID}-$>^L_7&$Pu%i;xZvj=0tjkrTWK};+o&nYI8Gacdb+amPNFuP63D7)B`%q1( zY}SpuNw_NxXHAJ=+}9nAG_w2x37pCui1kznp}ltFl-;K$3vI2f7mb)1pGk$v!bJwa zWg%uID;T&I$$~!(tit+nQ=8Itq%0l*U$-7SmxO9u#0cN&_E_0Pn3WIT=2!Y~Z1oi7 z-VrmgH>i3T-r-aCrXgV=-bCEJzH)29+8*YcO4KK+%MJy+Y1v(s-#A};$SmT&)soPTByp(J8pjo0X>vh=d}ScH|PjE{)f{Kd;4f&CJ&9 z!+m5w=CpsS3ny_Qmfzu1I}7ml@VAl?n-`zn64QAA4ajJ=TE&9pH@vTy%@`s2#m~FZ z5Blx#$Ps0Cd!zf#dsVaF8zdYtN4~rhsJ5G#-DG#{Xy$em`v2B{WAZ4^YQUzZZ-3`T ztpDCO4Bxrbg_@YhxKVdILAoDcDmxI?u9_04Bx;*^fMKR_X`1P9-D1Rm{y z!AxN+e@!!1g<3=^w|WODhKWvY+~2Jmb)c&CGO-^KUV3L~9PDg0N+ijp>0EbGBBV7l zxOh*S-rFB$2hG9o6x5HU6ti;f&(<^90g4B;@{a4JdH+PAi|b`f^2X-M*3t5L;}*ZF z@3@-n*PXj^Y9_fv7Jlkc2PL+W^}Len56z#!q9I_Z=Yvx$jfae-yvuLemV>@)3K!Au5GTdeBUO(swsDB*IhxcJ?Y2cbnIu9pAk z-I5vqt2o^~Ym=XlsBJo?5j@KMa$e@5@e4Qf3+vzlH3pW41U->al zC;X=p{Cobp|IoChUwkXiS&b2|QO~>wr*&8XdToRV&~pT8#g0HYXnvR2tI066*j_O8 z_FgBX`Xwi7HSW~;$h}j&{%lm}Pax?(gCIE}sc?q89oqM5kJdKb)f#`Mx4?{O~!To0B-e@c`qem@3|y9@cevTC6HZP_jYdUbmkEI0lZyZ8P% zQOW4!Quy2BUNzT~q~Bt9e7U_lM7?8^d^StXLVGzLtNN35k#CY+7~Ospih(h7>f4o4 zX8t1fsDASxexDuhZj4;(dg#ABzB=&j?$oEYN7nnfAOH2xzCzV)(xhF<@qHweyeh_{mj1Xp! zD}kGBZ#X#4s#%yVmXDw$BBt&dF^T{Jal0DD#K(<~rf0`-QC!D(!atIEE+7>{qBC* z9=1vV=`s<)({3d3sUT%E%@y5f`_MB|{Afde{Vx9rmv0~?CbP%598Cq;bUP6^+T-{2 zTs4nAXK)#XyMdf(^VzwPyNw(K?swArMD z<<_OcY~7Ui)Q-aR$Chtz`WM+$`6kLX)%{Ywtpth9ujWVlf zkCHP5n$y^)1V=j#Y;|+(g1;0h7awqRD+o6O65YQkbqfO4H(p6S-w-_S>l zOMxz4y_2bQ^k2t@CdQCR4yEIsV+nu>dcF{TQoq;A^Q~iIY#CI}2CCVD^Ay4}e{D9K zMlvH)QTEb{7M6ybk>$a&dT&^_)4u+!9&^uPp?$xAu|-V^YNfQtVn15B#%*9kkNH&& ze`i&~wB&xv*E+kDHJi6m5PDK6mM_R+{^cL|607gNm<%+alLdnQyf2qI9Wk=)bDUCM zJSo)r5;dw+MZ&kx8m?2L4UGQER+0C?62YYG;kYYOSv4T{)Q{GdM~hCIt&ENE2=nRR zS~2!d#F)Q}OjMw@rL8#1P(uMdwf-H*LAAi{S)yLRjEg*)}(#$XzBS zME`cikD%~{rJgOEwBkZFAv-M5zv^c<2*MlAyw63Y>mwx|HHY{<4!3h?67=mcNA;n+ zeiS~YFA%s-EuHRCR6UhSDS$atIR4odOV+@dEGL=JnAUM0<$UGkOP~iI_5GQBV-X!t z&ON?}h`1<(i`mTx$u*3+$d*#DIj?udDc@Ehit<-QWj=Ro{sr@!<=KB$eW+?4`n$K#@HX@ZTaNAk_Ll%dyHslPTYh2{tT*B zB8pVxu+`QQd`Sj_$_7i<7kuRFD`CVFiI(0{*86yprUnpYr0Q z-KYV{eO{Raw@c;Sj0_UJ-WiKA+QW+i3A+3qhmI}IeFFG)Gbefn7rU40){DTcyH8)L z`AG5EOnPxKy2@KfZOuoY34xe=Ner7959}2W^dndRP3=PjEMvz6t(B$L#mG^PzLO)P z)hzf|MU16IdPWwCJmxKQ4zLK!%KU8GpY}E>T%mI&PCt^qPc`S#5f#S+Fy- zFDl(@tBVS8hLiC%JRPZSr9`YuHUC=p3o0qq>)TH3-mQX@p9`nI)q(YW8#QVnhBb5B zW2tT?D4x!wk9od6)JIO+h2GV!Jd2nw$vr)NH=9A-=vP6X`i^7UT)>i*K`_g)yWMQaytZ~X!O`6!9MN-GU6hV`u$OR6+R zA5lz3cEM?q8yS0s8>pkA*$IU=C6C^Ui8aJP-(%(SGC^i=%b~SuL3@aVLThao6heV( zYS}0!^XID!Uq;-~o7lSvH?7pDppvczvE}2k+CEz!&@clppk7NOc0{$G%A;0cK&(8E zJekS(zlj%s#$d0y>W?u-6e zXZD=u!Uw@eN4Vo5GOFuDvM!sW_(sC`)h*2rv+Y&i?iB%$WzC*H6Q6$YY@xx&0S`Vb z?iU=q;=ULlIP|VG+)(#KH0Gab$9@6AzIA$}-%Njjgtu43e4k1sa~8S`;oEd_ZcDre z(fD92?&)~v&+Wf;7Dn(Jde_%vFpCwZ_1iO)B5}%BQ!`7IY46kMZOZ=Vzo&diAoiVU zi&`f81){&K+2M2~Z7jlt2u^u_IF*>Nqh~6b9R1Hzo_(+NV78K4*B=qudk&K=d{^`C z&>Ux-FNhXatTh61|4SG(r5?c#_%S+r|LUer+Wkbiz3H%`lgSicLa>o0P4lMNV}O(Hqr$d+G=e_8m)1JZ zLwFvT*1}eBts=7O7^AL5nZ$)_Bm$v=P+D{6^cC6ZmAuk3i|xp(9&WY4dUnD@ zXRolG)suj>qapa<@vd1nLLx`h{J8SIfOtINe4OD#n$0(8I}9wdzZZ8@KOiJqtEK*8 zu9v~CS#CwQ*VbXP_&8^V2&iL!^x%tPmO{UD*JDj{)gIR;ou-)m2I1GXR5%+cJ~}N+ zwT1C>D=`4+|J+K^Au7-DC(n11#qzt{$iL5Jw1&mh`IXDyW0&4-F-E-(#e?11t{&aJ z?=i{wbKIUfWHxt3kMBzzmB6A7)??PNZE)8g&Mp9@`jDuOSBC7g+Xw z(UdOx7wp;;ra*@F9xD6n1hmkhzJ8N3(IS$uoc0Q=7e;oZZI#Jxo`nh_kJY+$OEY3P z&{}65#<{;!oq}iDgEGN}%m=+eS3iw7u9U4Me(GDVb6R-+k@jcvn#7|ovb3K*8e^Di zR}yUERVLS&g?H&d_A&WA?C$SS-4+J3G*6EQWIPCFgdlD^w|grx8U*^VZ!v*!B=_hS z{k~(WsTHVj;hXdG^<*0=PLP%dw{6kw8M?#jY~Gt_!8zG{AH)4%zOgK|)&6*TAG5{f zO!0gQmG!M_(!?O90}0kDzo=AFlpEDgPMfj-fdx*by0!~U(Uf1>(?;%+vg|cGM0ieb zQz()kV2eIsKv}*eW|8Z#nLDl#A@DHr?~}|^cLQeTA;`!VXLxW&=vN3$@GmSsuWUI7P3YqpxoBAI<4`SH zXqFPKwc{0#0Sa@H<_+0>UU}&b4z$*T{kS^JV~>$CJY>4ct*(6bHti5vV|Y;Zm;f!x7!AFyauXq+fVv;;fZLsFsqAGJ zjx2Vy86}R0Ye;UZy;)S&lc}V|L{}Ww9chB`GWL~QN&u=m%_fgqMD3`z)*U zX0+pLJ4vU`%!4M5mFNh&7ZL+-`p$;YKibVG;o@ule?`8u=|aK^BSCyA zh86n}kYn`)*`3wuYv>klnsNHG{%6G6wa}js>X>77pF;&)AJkf*42*tta_SWgCSuf;-3E`L5b%r~57SDr6TJK8tU3f@?oS*Yr2JxcbVwM>^wyjVLh<82#STvjey)?EQq85?skt2=_DTa5 z1jWuOZ%k&>5pNjqgK$Ok2lE&2sGqb}e(lsa_Ii!QIKl(6a;td8grItB*U0@N#CMO2 zY+#rv9O%tVXsmc25nELz_h-Gay?nM$xV}nc30Gh>G>zfN;;!7fG4a7HBt1oJ= z7pLJwfBb#`;E_LK{wQ%0*`p|ba0ESHUPN%{@%1t6xV4<37t<0L`b5qS)m=oYIs6s8 zo7~LesR~zv@S85-Y_IT(djiSGHfw$TEa7*8m*dp{h0$88a&tQuiz_R&%%oo4B>!@9 zpN|0DtH%zko>eYJd)pf56?M%9r&GtbCf}FcBO<6I6BwDv*WoHv)rlT7DvCea4o`-O z+ONJx~se)yGxV;FaZNip7vH(BJv$W!-&$l;VP7rSGl+)H+^&H7=~ zSd1*c+y{KR(l-&I)4f>}ZBf66C#9wl`d;~8&7Zh$S7JvEZb9DkM~zfUde5V&N&BLR z2oy4bL{&FjIwocNwONmgmXiHTaG-8ZgI@RC5gVX4Nad!H+MZ5G29HwfZuQYf6Xn!Q zd-LyskOou04Zy3ROS=uau$NNLJIv|JO>8VJF{03uS*nL?jEE&q*Mgf1+E{}^ zTbKa;7XNDc3{%AaN(_=ScTUD0I1Rw*!1AvAbL6bmEKm78}lOBWA`o;oj)$>aA*j zTX?iAxG1D`recm>n2@Fe}}q5y);GFUM*d3~aL zlJHuifSk=$_mu^ZP^Le$nA|e~NQqVzusV-wr-* zP6_uvA7fp}DbzT8+HHN40UV(?`LEN>R+u5gwxGN;kosn`b+R=a&cYqnUn~n5%^b91 za4u5q?f{}ySSUDSV_q$nduDr-OvWqpn}{k*^0bL&d!}`@TXgV~J!XYP_>lhAHQ{=R z6^TKr>S8SD9v9gT&`>b4?muc7#SXifDBCd{B(`~i>^fU8*?z7jh;3-#oyfPz#9xlI zj!v}(vu`3br6WeVzO(X~43~?;O)D#%NYHMM=4ys3YfNaVaNKT(QQu%dDQVF}W2VM0 zzKSKW+#c0MdodK=4vSZBz5<8ZCkLQ5a;3tP)%paRuCva*3~j8QZm^L+B%Y4TtpyuC z6d!6nlFOIRbX!7Y2~TUoSUh>Km6p1csY-$9&A#Dw?r&|d7+#|XfYb83Gu8mUhzZC& z*fR~KW~fj@Gja3z%p#L4@G58ze!nBemGDue~# zoO7q$fjN9I_169PiR0buJgx5%=J%XQG@Mdeo}`wg zqu`*I5M2e5fs@egzjGhVOUg73sDg(5ax$>?rNO=q1g3_UY}{DM4WHEka$zu$0{d)3 zD<&{yPp7F)PD%y=s-MjC(LDO^9UxMN_~OB-8K=PvE@ZbO$gA0W-=}a1E6%5VZa%(G zM9C|{6HIX#z^%;HE){gF!SKO!cE${|83>H{Dz`q4jKFKd2)!gs#`SWW%b%Gv(FA1R zT`S}A`1Dh*wwVPSvFaf6Pc_w+blAFlGu>rloZO^|0^YyJ7AAA(0FRdD0Xim0k0;{| z;puBfqLtox(Ke;4eA-rjL^r4jZc}}Av2RI)^IUIsxqEF>>)x7x)$2J+mN~Bh`XvK(f&Sh&6rFSC}jIQ@UjR1%h7`M8RB_dHOO{^(3 zbK3fms{3r3_q4xry`<-Z?NA~Q*49??*DJMMr31TCRW~vDl?>I#2w_;Ki~2bD*w4Pn z+@$WqC=nBX&#S&c^A&m1^U1sdnj5a5dI zfF04vNayk{E+U6nHlZU*eN-N$NJJgOsT2KQ?|_s_@?g5UhKLK^6_?t&g4x~8NGY7X zLNjgCJ#jXg*VNmALq3V;H2J*d{=E2{`Mm-N&+ft*)b^A=7eYwi&BzUj$^L!`!ZYD7 zK#sN|M@KHbO3!Ks2hv+z>>E2H<@SVMp1u~S+GHPk&a+o*eyahXMz|f^;HYrBn-9QP z+ZY-iE)5&)h1-1G*I3*J4+m_F&;fBE+v+>tDE9zAY8v@KWpn0ZlHx%5EJiF}%ce|| z^ygMjHYJZ{0g91x^plZp?fgzUuJaE+KaS)EoGQ*o$>RtVfWyV zO)iq3yz~YSE&*GdI$PtCkiE&0`7&Rxf>0MTz^Uv%&RhIOMvx1PP}W|xV8l11q()rm zxE^p_?e82sti+S1e4bY~=<5cAVJVTk7I${4yvXy>g z7!M3qxmMR^S15`?vGZt3ANEoh)Z4}`{RSN%CV7`IajXxC=&_vKscF8DDH45irhPiI z0V3?GbL~``Fz2SQoLESfp5gf4*UGc_q-H)}Rs*v<^U(i1{##B^%>?L5qOy(gY9aNc z9|xc-_+Wx8)8R}ehPd5vJ=H28z&4e^h`cb{o_(YDWX)#^y?VQ7%tMRZ?4#VQ1Cprr zmIUMTqOgOyKfY$JXj%0REv=-+f~$0!V#lF7TsA23%OnsUdR3=$)W`8uHG6WJb{AwZ zn{&T&8KbZgu9!~UP7{@i{O>trdjDONLSI0uHp}iRbOTw(cJBAz+9$^S*XCtnZU>(M zdm4@+iNDrXw3e`(!Sl2}jRKG0Xl|c)0u#aTkPq2^*S@cEvtr>$l;TZ*bzU>Ksq*?~ z*#}FvsY25}&M@FA4*Mp%J)@s z7Qe4`_)d1+w?`$sSGSW>ts_N~U^6vZA29}2)#}vbS|cNJjbqY zyy=R?usG_~yxJBcfzuDPNZ9+SQgQn;PD~_&lS_W8w(Fnif7CZRcyU%oyIHD9dxD7- z^Ye;a*`*&>BaPJKT~PZ^kI;hab21cI^<%!^G`fP(by{;xfIKAc+9+S&d8c?`j?*w4p>)4~-?pTg(~g#bSbZ%a zs9qIf;^b6nEEbV)y%ZTQT3u?&Hae_qIw$d;6(2YBtu8!ZLBxq9kx@ME&@8`uytCK>eTJ<5QUA-o?X8 z{gC>Dv@xG>AbXE$;Fgw1zML^M!I!Ak%JF5_d=T1eT`iRg$Uwdp3l|}G-j7-vqDqg4 z<9fZb>TM2OYdI38tFiVhL6B~@^Hx3BwlTNV><*f@+`$DREiBp}qI)nOjs+C?B?9Wc z`0~~swX#$5-pOuHT{uz$>vD>=l{Z@PEz*@S%GkSF&zr=o23|>|!G;%mG!qA=_b^7Q zF|$RNv72}Q2js)(R*1yKSZ4_}c?yYGT`VbZLx%Cuz`1>n;s@s)hRJ5f*e#_hcXz2P{jCqz2fC(^>9^__9O|h z0TBJ-qLljUbX_%L-gS~OPyG31Ud2Ab?dM!sOs^3S0?lNQX9)bpXE0s*bzpW|RWrPy zj3zL<@qtR9*_D!O60Q3;&bVEdpP}q=IL@y*Jx;YT_ByE^g3Eh!P=r1)3Kyn{7GP7B%&Ma=$Qq&Cd z(yB~JWP0b1M|8eL-HRi{0o4m25Y`CL;RBUDkN3W?mw8oK?M{(ao?$Ldy>QMZf#6iq zz0fy^qOxP(C+h+k9K{cO>fE@W@wUokrs+>P{U`4v6Is|1?mPA!F)s1KJC{|{wVJ>` ztw{sge=NO7*Ous16E*IyIWDQTD{@>pYItr^YC4-M&!Us(pQKz}JYCUg47BLE{qMgK z^19~fZoRNF>GEWb_LNGwF#idFt)+(@mZ{^La=yD=D|Br;lV&^wjpyb_wB$IPO)d{j za~}*(HLc?Jwcg!uMXdyhQ!aaHRP%Pn2L9kU)Nb@FHw>5GIWZh-f0l`skm9}+Yd}XD zCDyF?xtEJ*lVZhTE7hwv2iWa-&wC4@WUtEqwXrCntIa`y9Dl4`fUt4FR%hnNIATC9 zWSU+dk*V)WZo(GOYa{;lNV2{KT$%6p4b7`LAH0C{8r&Z4b4Od=4ILB1KM+{3R|p(m zHWgUV(c{67w&J1kI#jT|-QLMwM@nPRY!{{JCLJBF0{d!1E+QIu zz@@~6`U7JW_nOAj!48_5{LK*UwRp%QVhdv{DiY{scCM??jCC4B-$4{?k)7QRS5gzU za71)lfo%DW9gSGIq4u3h_UB;5xf%&lKQqK5WE)$jq~?-c*rLF_iQ$e<9c;ncbjwny z&?X9zSy>382AMuUUJ9otonl!3pU>CVkW4;S=jP>IzEPf>FLRA>#CJCk6OhbpUE+3j z0mJy>T1O*>ZjD2Q&hO9S8OQg|W)4A0|2zmWPtVWua+PYTTj8qRZ&z{>_vw)(Kl=zo za&Owle`=LV4Apv$bKMDEKth13UZ5&Osmu_akOlT+%?`yzs}Jjyl4`lG(*IDF5CAjG zVV?0*pCT=?)Z4XUu>xXc(Fd{)$-+}0oXxmBCONJsWgkhrT#09R6|Gh36aCZ=*Y_np zN8du*B_BW5FV*`v>gqIk)O5*tN#3gWJL&8zcJaojkRdOT_;=AB`+Kpya%CK7OR9kz{Bpg zmzc>3E2ZBlZ!($;i-PT%Z(s&ZCp*1e zD7$2>*R9R}8i&tT@n~I(JN}9OPoWr~-lJgRIf&h!fjj?uwAyk3Sfz9c$zB*ub_*8K z6VLcjJD$&XK8vk8*npQq^+iMQD%9pDQM!-g`IoW%N0 zwu+f!JlcJq6RA_6N+EZ1tqQeLP(}`fUTL~rfKq?#f0UAM=X+8YwjT#~zKMv@Z0?1+ zPFxDwFg)Hj&eC~j(P2NVW?cgKf}sMd-uGLNjm72Sk~IN``gC!9w$IA6*Lwu8R)Mk_ zQ=)vk4z2wER)_km8%BP8sYi?(%QNRh&8iV?gr(bRa+H0yT2>65#~+{%db#7txRMS! zt*@Zot;g2W_9KklCQpz_?qy@EIUHPdab^YQn5h-cDrhgh`1- zL;ChV=YD#ZSLgD;jO6@nV}PuY;uaY(|*7W`yOfYZ|c?o=|y(I+x*KK*k8GVXWm zo!S2y%o6_?%s$}GT#J%-Ee1lB_a|~G9k%kV!)^ zZb+hVb4#;}z!1k=&mlzd_1N1mn881OGuNZ#a7skv4Y9{~SQ>sJj0t8eF7Und1*8*| z(j-k`w)soR_OwSu%p&j<$;H90E5mamR|+ypggGFS$0CcZ@gT;`@B^UgvxVHynBIDEo@AbUUJ$A|ke~(l z`ox-ptm%v~UkU?&=Fp?!(&^3vn5!L+!$Vis!^z2jv2wZO-nUKi*XY~SI!*; zbGRzl`9cT@hLa-(gkM5A=CY;7pYSVO8>-{OcyJC;_n>neF33sAK0M*KBPDv2Z%}$8 za{3TS!P*p5fNg8zo^ex!tni%EmV@ikR&ET{A4|~=%Eqq`W#gDy*DW>jU!iVP{X8a? zdt33s28Y3Bp>~^YxAO-J!KF_d0ok-`)Uqgf2Nb0*1tt5fol1k${jfI`b8WsR=8?=i z^PB=+0cBd?kI+}UxY5;GfLq;e$3qGrD{%rf%6QL{mW?1bc9a9Obaijj*&xb$`mljV zIA_53iu!T4oqy{{he1QNNRZ08@MiE-eJ0#Oy$U<8Hh223zRkoimA>+D0zk1?T-goBmp_aUyX1E;4yA+d-kv%Vq@w)%Kf>_VZ z((QYEN)O6$E1xDqA*~nAoXXG?Z_T*l%w%Ys`YF4LO#X_U)U5KK%Nx!R&avrCzlQ#Ye?rOWYoq~8Q-zgaaCF~CIsnqirTc$W zMdWoVc`6BFWp7tj*WDl!t0ms~DTcDM9pThM*;jJ{p$@wfjwLHGOvhk!-R|pfN9`uj z1uHj`+uPh)9jgo+_Vla4C&ea?|IQJ^V<{0%i93=&j_q2r+RYPMQO>cL_zw?}oSN3c z{jaY?33r_vcCVY{&zQii_DA%^L;Oz%cCFswo|ruY8GOj#-l}b9*<$R2DeQ9w*Uc9Q zdUxvWO<#_+%16!}bERnVr-B`&d@~hK6ga`p_eQTuG^Gyy%9;J+VdaEYG0;t|f=Qxs zf95g&zM_0M+c1AyRg z&L3_9V8~}CbdTiCdq{Ty6id94*JdaKK9c>EJ&1Rb3Mi%2_hS+IB zK0=PTZqk{ufnM{vk|WOyzfN~H#s2ue=f8Gqcr}f0P^)jf(Evy76|d!Q_Z|Ray7B#) zoEQvx2d8m+fO7iINw3eUAdSBb#(X*t`)n zDL8eNoS!Sq{n2kadI-tooT-{rM7bG=2mg&Jv@h|AwO4Xaoa3;pI^A^#b6E@DpS+kf z_C~eeh|x)I-1tvh|1YJ;@b6-+s&xu z5aaf~XBaE}+rZN#TgnM|C(HyEm<;zyh0{rD0kgN-{%mEZd#RO^4zZEZAAj-MqRG?0 zGH!f{49z8eEi@^ZZ8!LjG(NT5^>nd@^0oQ(v4^?%9JJq)0}7v?6F<#% z&^iu$V6(J3#Q)>#JeyEOw{87f42X(?iiio3AQ&+#63ik76hZ(0ouF&2zUQ90U%IM$ z?XK#L@V+yQ@r>Q1be{}IiuADu7d>%2W!tE5w>`4C&!4IY_c}D&z1k;rOHudSLrm4C z|8v(t8J!H>^|yj-zqtEzT1$8~UCN~7$Ym?7p2Fc`ihN5I*XHpetx@m|CeVHqDrJQaI(3TX{kG*7qLT>>D)2cl#H%>OgPTz} zuU;1!kKcw^^yaYaeze2)tFk;wZLOZpWZiNa#M|-OmJv`9Fd*~wlZz#+e6XZ|*DIRyT3uzz!E$sG5x<+=xHd0?5kv6oI!O@8hsair@~(a&T#O zH>;R9ZH)j$>qwu<@KjD4>CC2Dy${`msh!=x8E!QQh~$6%32kUKqZ zB%|uY80J^KaaV3-!}UeqCI-Fm_(uksvSp_cUJBpX{vt2nSlubRF?LupgEu?693@Jt z-)^Etvi6{K=f85XVD5+yWNm0G-c9M-U>Z6)cN(%77N?l18a?(04XTbaUupI%d*1~O zdG?P@VOGxI{D_HF#n_cavYX}J_W*f_%loPieox1Ec*O#7M?l?BfjV`U` zE;oU#zQ!IEVY%hHrn?(}EvG9i?|2@lDsvia$mR(E@*{9ud=)IZObH)_xQmM+Q#mw5#+pf8*uc+rdxCdi5N~AYn$wznk=g>mnxV|fL zp+y@%KU31?&E?{>{I>dEY1lo&nH|56B-S<0UtC5?x*b#2nyS-zH9Qp1@)_mfnP!qv zMP*Lmg#ljV@z`Vtf`)BSbOi|O4H7ipB#a2zeef&%6-NpsTPckpGA&iO9~)~J>3|~* zMzq$nS2*=MLh8_ZNnc4u{+z5rSH#R^Vn{}1+>}}!r}Iwd`S5d zD2W=t_o{s+CP#Pja;nBuQ!{@{hq|Eu&->hZNKk|H7OMK0*R6}`oI!yNrk9_uBfbRp zve*s0LC|{qUndAm#+r}Ut`_?e!Ifqd@rlK=7=sx%6`M2(OP>*6QKAz)4*ZAd8>luTKGB?0fEznsLIaG*R1)%XmAr;i^HHi|R>w z3If-*6*g|S>8yHb($@Hdj`U9dJRsIX+_+l~yS@_W>9pEL`E`$hR98eI)uLbQ%Ya(k zmF*|`_s7}Tx}({ z_slTAUE4C9XwL=m95yxQz+FQ0QhBQ3|-Go*qb;lS_u^joK`Jh7s` zCr;eN@DFPIa^8Dbs|okayjM+Qm~0P}<#_NR`FDZ<4GTyr-}PcMw%cD{JK)H~9ovDw?^ON}LhV zV%uyqn+8zrp*D+d5}-Gl^_tq+kjkJRsu8OzQ&>XS&Gr)k>H@5TpN};J{h2EO|0F4J zTvsRGz5P6>*bQM{1#w?ogz!Vx3f9JEDCNo+1xN)Sbp}P4T44ah=pMdFIp91MJI_?} zi1v5JbgQtI0RwU6KwqCo=DExZvphHo=FH1&$H%WTf@<^9zLgc%i1h-Z7X?A`(zWKS zKYT8->Z#i3ZlEg8P-WH>CsAyxAv{kVIE)4x}2upg~{4x+8=+>y4

Qhvsdl{Q8uyvrhG4AGKtI#bE^)m-&~OI?&%>0a(CmH*aD$j>jgxd1j{7MGT&9 zx#HY?H0{Xmce> z+*~4VHul+EAOzZkg>bOZsl)K+>Utg#9n>QGiUb-U4LpyqREFt?tgc?5bM_&ODg$Sh zbPOdAIYCCT@WG}_vBaZU5Us3`9D(J1f1-v@{yYO+iwPY5+-iHYUYAut5E-%E%?#GD zCd3$hg0yQ2-cyXw&nS@;qT5;rP!w|rwT^TjP??{)06<>oOhL@Id#Fw=U|;5k#`JjJ zuF^Olt=_<1tbJZkc^v$M5VKB4unx3?c@u0$Dv#cO3@)$TH2r#4C*QGB#ac|=TzS+| zp$XB^u%rWbdUDfgv=@}{rxZO?1na)1|r%&U3QmG-MoG4`Jb+1F13R%Z|rRPNYjEe5A8S z7k)hJoLDGcqiN?h+78QmuYs+woIY6Fz?bgIs5m&^pO4KBrfRQo9IcunjPx|x*$M!B z)3*>S&G+-jqvjVFCq~D8NURovK=H008=W80BLpFy-|@C(A9T*_YqTj*4R&#P^yd{KKQBs8Ve?i;Z> zWNFwutR$CfK+%@VEHWOBfP`eONWBc;7BGLJ&+K|#pLn`E1{(mqI`nhGx-h=3?PWBc zK!T|TCKFmhBC#=6)_V2`$bxG6`PG-q@!^C2*lr1uatQ4|>)>Dm!_2RVI7~w`XJ{=O zbB$RZTSwl=jCrHJricgGmjF=U?}*cW2-VH5^P82vB97pB_#mBio3Tv!dPjpm>_fq8 zA?%cmV<)Lio zUDy-`=gakXz9g!|wA~Whoy!(?@rpEGZOV8p)0b8z-R~4T_AL`%k71S4?Z8YmmdHhh zhX1t+(5 z-hN1QAwQtT9GGHJgb*>8<3IvB@V;wGVNenKRq#Z>T5&XFwx7~GnCr1SaYXh5pFh@> zVhb6{w}cNB@~Rl7Wx#{&P4x=B(!&D$@5(ALwp&Q)VCxgZ3I5+>3hb^zF8{#R1)7&rjE8C+#Hk@dG35I{0(@ zNr!K7Fe{xx3dgLoh>ab$3Xp1{CpD7Tl3#h&`z{|3~Tuz~rF{sOTeX5=Z@@ht|R zh`iE$$9iFwEz3$VkFgLNI=DT)f;D-%!hs!FssogM@Z4@Yov|FZ8OqT<+D^zAz6g>k zAb)AMZW4==^#jtc=JjfX%3eWu(w#zMcRSzjbS`feWyFA;dX6-)7?l78%3M408el`B zw!7lBD_)UvY=MW1lmvz!Owhm&F{s0Be{22*)5}~LWNPgRSGYvAU(%#^H_Vk-$6y&? zDFyT@L5`EMd)-Exc=lJw@v&Z~QYfJh-G3&IdKeUEX7-eQ(!-xy79hCY?bzP4@>7s6>hp~5G*Af&eLZ~1m{bsAKD4WST?WY^A(9u76 zU&YMi;!tEt4s4ICW_`GH<+R0=dWU{~G}AYc%CVyPGh7?Ttclzy)^xYKi#>?bTOTy| zRiOa;!QmO6#-&*QOnC+VnJJo0+(@3)RHD9M{V^TNYyRA7;L-~$PeD4!Eri&8qoZJ) z&!6ZCw+UCZWpmvqYt%NK4S|?Jb5Us{`E4DlVUF1|D-;Awe_XJmKfv{r@*4+9VUEIKVmhOP&wL z6A9SKon^T0++48ZO9Sdo*?ly zaIWQ-O^cWuvW->y;T(8}>tIB^9^J#rKpo7sMG{MSfL-wxyJwTQ3Yp~nF`sOJi<8VW zd`(B_M3laQWaQcqC#9tx=(D5XuT7p{As88+D{~|QgXw!Wde-jszoQ|D&Q7+vH|G2n z{@ic94S;Z3sqh$%OZi5@&ZJt%w`l8DfIP+R6VF=N&5Hznf!wezy;(m%LXH2rlhqcJ z;@j#v>`wUJ+gnkpo@lc$*i8o3K|+zU$WZ(kyLXL=?LPryYuQ^j`eOY((ECe&$?bz( zssXKZ!vVTUd;-(>G6_fS&IDQfo`N;>9~MD=m;|IF4B7BJn1N0 zJ6=;B-8BKS=yVr!w@??i@}N-bPL61UD1`Qdr~<7nNZcrv-BddBF_RIJvMyok-m?=O^DOG4UscA<3qgwB%8kV-}t!T zuCEeuk1w;$bRppQ=VxN7Jb{h&aliiLdqmJp7>Op9VxfUi$NyDnkzqqMe_E!% zwzSEgQ;rqamp0w;ohP=pa=tf`ZZRHG`KmJxQJFQ|;F_!`vch z`#NFKL6eexw&aoh{3G#&6}EC77VW(BK>ifaJ)~)M3PR}|30Tb3x=N-r#0;xb&lXL%i z4PX%te3-c*x@B$TWmx{!OPMiX6-DMVe>b5j_)dE^7WuO_ZfC6Iw^vfKY~whS6mieY z=9?tQILpU;#4K*vP0a6apU0bo=%c3!#*11dK1pXOpj6eHV|6;Et&T}N;bgeV_50x| zYP}0G?YaF?>8CJ7rcpNATM$P*F3%m*98G@pE|5$B7zT@_;N5NBMN!_AM)4*>jxP-K zWmLL>f>nchM@BDm-5Gz!KnNG1@kB#vG65|w8M@QI!bR3rukN@sb$+8YdgH?2%JtUd za7EVchp1B4i@=l&)l7$MC0dIdpUiMyJP(i3BJOGP@^ccrr`ibuzKT-U{2PpYDMrK3ZHthf9Ccsf4~MT!o(&!s># z+O31SXpFFNbx|m(*cSa;kzD~R7pa5{jh}SrD%AdZjlhi$0`$NVSZH~|<$KUBvwDr^ zrP~26K=S%kRKPkRQ92lj+$o&)02F`7fsohWrb3`8Z|_{aU&H>i8NwitNq%=qcJ4aK!ymx} zh^~RC2rOu(k)2(qiM z&w2Y%lIKgVGP&(a?RYsxcm-S=EMxs}Cl`qNSJu=88N~lsNJn!JEgpaZ;9>u8FsT8} z41q{BU)NX5eC$~=nb#ziU?3fOMR_K1yQIsQHn@6oK8|x+m+IBo^I-xm)1f4C=Ce9} zO}fpc>G@=8Q3Fe4c{5Cd2Wyv>hgE;)N^57`jDfm;tK835Ig=VurM47b!3j*})nv}@ z!f0b5%du!SHJQ7aeb7x@p^@06!i7hiNZx=q3@E`cOai_<$eRt=y+(3#%LdEmoL0#$ z0CW_&LAF>RFMP!@#O?2eV(6QN^r9{>7HGgAZ43R$k1uslmZBcF%rdiXG3qbAC9=i4~IV);~q4H{G zI*2ALQe6q0$VmufK0lp(%hDp?)LOL5`}U=E_sR4QW$s*5D}vFp7d4A*a`Bp7ITZIB z@p|3KJzD{Ft!v$)^&D88XC$PWmLXU#ryK*rQIn%Rp&o)@Wkg4m@o=%&|Cv-Wuwzyj z^gC(eGTtWO9^GY}$h}MHdi`f%R%Xa}l9S5Hb8T*i-(SI7)!tOOtxd0e&%#bQNusHD zGJ|&74y*mAnL@{lF1WFF0XYr$Av-7)!x@?M0aEESd*~A2RDr;Y!uIqVtH0t& z^|ePhevYoAh0S1?O2HT3hJDbh1E?uj(a~7FGw9_uk3BTOtTz{*t8>|~woY1kerR=GQ*-;|$u;LudniYGnSV9=xcj8dL)3NL; zxBppYy)-n-fs`;HIHo3iIptgi-ar``lk0STPG)eQITf6vF0-#;c|uvUSKbrLXF&}i znbM+sO~OxR*T!S@xsunF+E!>O{86HuOJoU^fkmKMQSR>eG4rTD@>Ia>9)oph!$ZKG zo{u&Vf`Z;aV*}0XLF)oM+j8;r)I&1z2I$7d0&hv#o|D9yqa(^)eLM3Ok?(51)5yF4 zU5Cwrl(xoMJj#pzjMd#w1~6T{#u}DzVsVNey6u3}6*sqe3jdxFl~JxKbdt^P_hGwD z!~+3bOD%euqvoIV`~?r(9nKSF-gYTgy__gxNHP3yw3DDX%XmMj|U z&dE?=CFB}_jSj%jdAnl3gHJAZ0W4YoX2|^mvcXx*x;fcs=%4_mF*)IBs~gbI*mfAp zVaH$Nyax;3z)=M*HPn1+=Jtuoz0xjK4F6dswdxx~Orm39MNyfMrCKJDqlpZ&$5T*I zEbrg_5fQ1iA1)xVZ|P44)RHf)70>v{28fk90lmSz^j+fzlS+t{6{@gFe_7%AoF5xR zGW;ZCqZ~wb>i221x*)vjEYq|EnXey46r3eZfR-9d5K~Bcw<{laFhAfZ!W8Gu>Iw-9 zdDKN1Yj!;JV<6<@Tf1s=M4^e>uDgF7CkDPt5VeP%yyW8Bti$YLZyKI>t>L3?`Pdv| ziz;y{jryPy#3}$;0s4>5+#0Z+dO^NHg$2U>DqAeZ=MS*#aA_r^FhX#)F#?lxc@z$M zD*4E%^jq>5&Z^c*bRS=e`y!bPhMCnkW@9nCrlm!X&NnF*CG0dfi;0<}H6t7Oaw_W8-sy>M zs@w7agvYTsh+p4AV8>hna08+=K{K&F#sz8_=0S=Oe8m^=3D~mIbUZ;=i}W<4okOVl z>BbRU%gMI;sZ!xo5z@dWJ@DTf3}!r6bJZe*(DusU`x+d`&s`CE;u8JqiCdhI_ODs2 zI8poi(tG5gnRohQgrF1v{yp>>hY-a_den#$58?VWi96D&S9~wb0HDENyROAL)pr9~ z6gtD7_jLo4s>Y~RHcMhPj08{}u4drjQ=B}3ONFLlPV0Buoqv~xTDAAG9Er9H|(S1I}Ctvf|yBR&- zy*Zc*jP&2tc4{3>in$-^pE7P9T5An+(z?_R$z-7pC4U@O8xV5uFTXck3&c}^?7rXs zwjP$89$n1eq_-v&-BHe%!%EVKMb%h@3glB@af=wGuY4*QHFmv&lgT}Eczy%yPd8So zauC2AoV$hSV`AgRVIN;78d&A;q!*s#o)uLOa-mNLU}1k%@}P}kA!9^MhM2llSvYO} z;6Xt)7!1^qyq%d%vy=4%pbxMqG1@yD){A4aKN{moPHU{*_iH1+$4EfacMfJT#>VH9 z#+d^8S?n~3CL1okO+1;kp?0H^U(xwUpNI92e+o#HJt!DHkF6r9#o#CqdBmK(%v^0$ zC5QD21^vvX?QG72v`LHUU{_Rg9bm^3VM+oh5LZ)xK2+}0!PZ;=T(VmJGd+*risema z`#n#i=fdhNPz?q2!0P0e>;;quY8>b8yIo~aDz`=uN%7s$Pq*#EU^pDsFv}!iJjS9^ z?0CKAKe9iwAIW_-XvDzX0CLKuk6=kt>UIY}$QG@j=F-BoM8P~GWjqo{O!EB*{jZw< zeWC=12*Ad^eX@1jrvroEF`@T~?crRb0&3At1_h^%4wBkuI(448c*r!_u8rwyTbYtj zr5S}v8}d&cEYTRuA6{j7wN6pp?`AmbaW|`-hPNNxw+Fck0IprM`_y{(*yO8-2d>5v z+;#@tuehZm@NK&j`1Qx-T)%SH_mO^io^21iGT?qX#Q@lOzJDOzo6V(T(O>%ztF667 zNzEnZkvv64$66`T1&^#Nojv;tV(X-4R}h)l%nVDtLqtDyRZ zffJcY97AK0qb1i7GZ#r1a)iB0@JKSlXx%(T!`V%3f{C9%Wd?3BsMi7s0AzIoK_I~M zbYMJ@K{HLqIGXjLAyfV6r31dASCy0N%Sr#)IK@FHI3Lthri&lyy7|dmwXAC5ehU36 zgMr|z={({Unz~@>xP*>W3+%wiXtXXp_U^H^lD3Nt4j5J+tTVMS6%slVXnO(_o;syP z<1Z}91sdyZbGpcfi*<26-EqO+j=z69MJ{#y* z(TJA5ZQ7~GNruy?j00Q7?V1^Gl`|BQ;Z2oau*!mpj(ZYLitTGRDU;{zZ1}XO*Z>5Mr4Oib6Bhsg7)?twnF77~uOFyT zR^*tiK09>xk|5FsWM7_O{Wl!!a=NJwi73}I;(FmN@zk>o;r(XvYn}4al;p0=w zxgEA}gQfu#kivVN4W4AHUAX4dKbIG|QR95F7#6qyi)kx;s+waul=i6cGn^V$@uK-@ zUy{AdSEu^_zr5%%9_xrs{W@xLxxl6Sb3X>}b1i#o!LIBPGt5b;JXsB&LYt~Zc4$gY zC8RM-NoH+17ln@+hF!0LAyiV~{&KuolE;`&MJgkAbey`EWkdq{a`L#2U1^mZ2=l`0 znr9I8T`aouH}@GYKT(V$-e%k0_09yrDCdkVlx|`nJR=T=Y`uIwPCR_D!zoYSqu%!19vnd;ZIz5vj<&;I zS10jt)|%Prxy_A*=y!kdbxH$fTqH0JKhI?Ii@LN=shr#dWS|WpARQC@B%A)W*3L)_DhAN zMqpd_Ltx3F@X^QNQeIafm}%cS8EK8ZpRUU3(huW5x!rV%1kjN|!X0;9GI-)qd-7m` z-z*`Y0>|{z=32f{h2nLb%E<8lrT7`0S4Eodb1(sdR{HQ3Y0=A3R1c{&9^me8zy4iq z_*u1j%X)s-3Y{DOYi+yyWb}VmTjC74fWHNWL25xyC;p0FN1m^|6C=2z;>ZuiR9Iq#*Of^Rh0~^H4G5SSTE_ zb{JY7X2l<}iCy>m+TI?gHJ&?5eto=x5<(@?Q_NFGjkSu~x3FBe&3cU_rLN~L-~kG6 zqiL5L|JD+QG)kBIpEntIsr@Y(9<=vLZ*T>kLd*zGU^rTK@c-C;DEPiKtTbF`%H+enryIT zm@LvB%JKVc4>i@rj7?@L;AbrAqI3c0-XToT;9yJbWYsa|C|EWIXMka z0o{K(#`Yg0%#fzRIWez&na>=U*MRdvUh`1RJEO z$H3Zq{0i^1_ccAfFw^_w?)e@az(H^Ve6J#4kUbEUc21;r39d0}3{%}W|D&I=R}w6r zbabEX9s7Wirqhu2@DNzEe8gdIN4g=ePrFJnAYC+oXBvW?0=6qO!6uP%mIk<(b_X7O zrJv~cR`U*s7OSviEPq9&S;U_42S`rv70>f8hmb#n`H9&fko`)Tiv64;HeZp#i8}Z% zH^UA0Y>zXzWu)kZFF*8fxj}z z7mVZ>!lAg+^Ram3!V&tqFqLL_ayMgvFj*}@camsAPlq*kxA#t1_?tT0&Q`LweMM`X zV0QCxR+-zoJw8|HMRAu=(PiV>0wC9H-gY5pF=eNCYd+AGMf~-Quuh5;cZ-#3in6pPKj&-po9Rp%4%-3B8X9@3@s%ij@Aqn#Y*5`UIH!{v3lPUQDq^wGeO zzU;%nIMZRIm*?vKAy)_%1^s`e+IM1uzPCxYJ?qlJcu@)^58);qgtNfV`LPpVM<2H` zuo36iw_LVPLq{l0ai|LJX%L*QvmoN#ODyIeU_jMK3alROY(N~S*<-5&cfFJ(&S1C5 zC}!<>)%C`N;U=T;jTKa0n20Mo@P(D4Lf70QGGQ(2W z?(&c1=Ia;wwqzj$`p*DSf%EhY+cb_b`+CJd`Etfg|H4PK$i{4}Ue+QO;bWP`al;de zk$W9#>G-}rujM;(@z$)!65fyHibZ~Z+th{+fl-=ux`e;q(C>F(o$eQs_NN{mayQw?_#lmFqT#kh$ceMk*1V|e9Sc`Go$iO98RZ@WVVntJs(i0$fdQUF>ZJa zRZT(lz;NS%Ui`_g=g8Y^zS=n0Dwz$pO%E*+;(~jn4eB2^(LVdk4DZhr>}@{{|H>nqI311+ zkLH`p{l0x&AKZ<8a~!;#vDUFpfT25@x$LG(_3M-m#;()6Rch$LgQcJ3*tWk9*6}ls zmjTG|2f_kVtXruoUVVp~g?BbHuiV}@3hYwF!#QdlhoNr*$RG#lCrs$whvB^iiUq40 zu-~z;SNldKu-?hxhONPykn*rT5gq8nbsb?rK0=3PpaJqkDSP=y1-TUc$dnQ3$ zgb?EVj^L$T8LwKid-ioX_%+5$>sbYU8F``>Yr|TV%!7eA7sC2|FO z1+${Hd&o9FB_-QV-3Eu~sM~X4vEExUEiD=0lw`dGQpc!)*}a$`Hq(m?{6ThRQgi_%6CTFy;=!( zqUW@OeLK944Vqn+i&f8fL=N);jZ6)uRIM+kU|e{p-HGbSP|z>SG3&XrNQ z=@IMSBq145!wGnZ7&AHb5jb9p7W`|f5brj2DdGt6-EhA>3MFtYp_VUp(xoHzI5mdp z=pn8;dI#1Z>D^#izP#Ms8i>>-`&kb}M0uW(44W+|spJFc%J*zrX|(oLqf68C8v%1^>yl0ZT)j9$V_I_Qq%F9YplhV->5j?sz2$GAf zOt1-#y2hkV!@?XAoi{hD*f}ISyRr$v3V(~#=IRy4OQ8PD<6$uyRQkF9XE2jf(y;+A zC4ym+T4_Tkz5{8-cpmNUR~H|DHSU&A-G;B*6jU2RIpeD6M^lHFZXx zEd?3i-YeB{b*271pS2-t#9sYE;`yj)Epk+xXW-5dAr*1WBmj|56?N8H>~WVyL>On; zXyXx$#TL9WVM2;beV7DcuLkty@MeM$T&oT?3davb_{Xp6nH}^v;A-T~2MJv6-?1T@ zbdtDTKXh!`G#lhIEZFf z-F@Cqc10G-P)BX1KFPaGq8EJBY)%9A7%?nm2|!Lp&}>xBpclBFOD}lX0rQb{KKall z7_c4ae=#ZlbD{9Bm~FU~7*FgcacMZjr;1S=YW607f*j|J)g3HWWc8PjqSqE?jPtLr zXnk&y+Z)bSQql&t8mY(_KBxqjG>)z#}D-gv|w*Bp#p zqm3D1#6ZEMbUrp@J&nGD?s_%#-mVH%E}SB!I)qQbSr>2e#KwC`^d7klB}`b{vFn_|6MDtpJ}+ud;?WGb0#-9xN5Q4 zrN{_o#K&RAq3N5)7WljQc36<^N<$@iRBG0_;Lml=Y2}v7)q7Zy4LjM!!4>sJs(>xu zW)`KkFfpxvB9xF)zx}sy5e^?}=F-mHlc*F%IF0 z3A=xy@;Lg56!=66FK~tOHnf}i95%93EtqRZ7{PMcP}QikEMm5)9Qm)C-6^mKjAE<&!hS1y9Y+%YY=mE6KMUKG=gx( z@h(5wi{U#NT;&^o5IWjL^nj>K?x*|U9WgXKe9q|O_H@6JuOJlTJ68b$a0WO>Y z$I(ge)qj7vx`3CC!;w}4H5ylYVA>@)$>fPNVk6UT#Lia;^4!BW6*)#LMo5PyysS?4 zjBI}XOr_iWVS~g^d zTHPOD^!GStcY3Fqa@66?!TqEYW|RiRr#;AyVjoD&t7Y^f?L?&4i$H&u`qz`L#{0K- zVefWMfHiV`S-L)BlgZc0RCSsrL?;`GS@Oi*VF&&omITggknxoSS?CTM?4Ow&qr?m!;@8 z$W%^@ZiKcvyKv6#YxG@=_w`ymW6~5AzxScREXDbm;G~lyzmyejYe+vB&60N0-j%dZ zX&P=1$3uJnE)P5UOrzB81H|=>fV>&O#Hkyrh7au;_J`jyjZ}k z2;fZ7&n5iqgJwSS=Mh_bfEaGm;lRqy&P4}aZ(;ubDD3|+{9r5d{p_0?u8X(648OY* zs%QW^i{v$j*pDIgE&8^`0@rLl3xjbuwnmR$WA^HmPFKRJK*f?BYkY)uj{}=qmijkH zk)#FJ&kJky+N#|&Kz;-g>_!xD`w}D!p0nQg5RQb@Ulpw6gY^H~2 zoxs1#h^bm`}Mt?XSSKB3Q~ft;!5ye^zCfD{m|N4y5{pK{o&Z z$Sn6q$hHRhMR};VU(KDJG`V|mQ(;TsKmVZN@fs|`)5&VI9wq%~yufVz+vw{h3lPE; zZF+fxI-_cRW-U*k?OT5j)pQG`sZ=ATR3^mr5?*8ZyNSPbn@ty8SuvccjCbT3pan&e zy*{3>3h3#-)fO;tVWl|7!-Mv*t=utCxFmucMLhFw6#zP9j02wA0S#Yj?s%-sinUMe zIA=~-&a6Ti^uJq3ht)=4gOVYRiy@AUm7s^_Xz+imkgcf}rwrtl4C?N(+Ow3HHMQI0 z3FLE?bmd}hqOGmAcz^)M&(*gNX7zsEtxom6Lrd~hbq#QHXa!IMZ4B9H5*l>_zaPM)PvyvH2!7 zA#EUy!boK#J&?-w3l2_!Q`M;xy@nEuRE$G>5>bCQ*dB^4bZ0)i`9x!bVD#D}mV8VW zy(iytt+laB$2+3%du_EzFrv;R%RX|w6T@P62`vCd6^&0l&>cd4_g50GtpLkK)0`!h zxmM61^x>EJ6J`t$i7SX{m%$4Cy}9)*LYmBL^z4@;8jHi?A95rL4y$chmLH(rp6TA1 z6b7~G<~fA)uxmwvk@9@q@i}Czr6((u-qrf20~2esLWhUj9WYI2gQVsS;X~pOt>N%5 zx3j%9VAwW+mBwxp3?(Q4RTQA7DXkGRf9U|FBZF9f3aaJBs~vNd#5grOAU7hFNOY@_ zp5uxy{p+j)JAc$V4;ys2qk_e0c@)A@ZCxjrHc0-rr^_nD}qx0G?9$Gd}4wgT(nD$)X(pzvLIyKC^t@V^&wW9MW^<(425$ z%$oNV{{VYL8+v}o8>^2l%j{OfqvwR#=8miRUebs)-#WO(V?m1Lm$6P#Qe}=TUi`V6 zuTt{@PGJk!phip>M3ML3AgbPrgwf7HcolLvXR{|Ls5lQJODI4PAgwAu;j!Un0IVOh zDXEF%H2lOT)55^*;z}(w&hnW;#+8(12A7NUfQv8&eBwGk!LR%GpHKW*u-RGU_4gBJ zY$cI$!cV+L+WjWvZ-O^A;~p&drpX4T-Rg(>dt=pLR!W14P>?_YMh~lDx+8?V?4|eA0*ntCUQiU4%CIok&ifb-{O6ycVUuDyg|Qrmhu=xOe&EM{B)Ka^qi0b zvpm79zR7YRpQ=J^Lli=~K~18EGd%Nfi_G`j=-9N5#3KW8z!weweY;~1)qJq=T#;o% zxMwLWHll)z3{VGVY@SZVA+W4gYWB}e-FQ)Q3?OeOKo8ANsz7&4MO|r8gVOf#Fdc$et8n&C{c)rvk z?5^|vb@~4ms;9~G$5#5Tu>E@dewnPT5z%UaA{DL5seJ9D4JT`|n4G4}6pcvmBFXDP z?nH}99COSmpFLbzFtfVDRDRmtBMU1zGw8gfoD&tFY+ESyiH-*CGx=eYYKl%wBx^TK zRMj(HtcIq8Vyck8Kw1V15d0vGmZqChnEZa8FBkJTgla6L=i{9PJ?ruqz)05s$+RHU zfm!UqmDgEH2r)W@2LQOmj2Vw-K`}OQ{=p&UMi)K+? zwj2Xl% zrsEi}X8{1R7p6?fnW4##&v8QQcfHa-tl18u_@ z)3COy_Nf}}b;AM4w~wdbQFi=1y*|$)FY+N@cq7+Hl^4lApV|+0D5jmM`T0^Fw0h)n zqM+~YER||Sq{m9%aXmm;U3%44L^Th+V|r2}4&CT{Clq#Asy<4~obBQ3{i?8GE)8s# zIk45pr_#x$M9fNVFL_duZev#*Fk`pgTFJ8Asq|DHh@`8=G+QM;x;QNg7M*ocyl|ik zb;*aHf|A7H;q+vBo)v*lmm;~-%h>UL-JBKn7=^?CP96^Vn|-|x*K7`q2pDJP0N&@M z>!@OFx0wXxTU$O#s>WqIuXW0=?ZJ3^UfMd!=+2ST%|r2pmbBgazGwiNk8K}j^j$ds zRN-=i5EamA^eCf!tW%dA23#Db{~v}`q=6|=_TVg+MyFxxDLgY^a62XRfQ)nq_NS;`J=zIS4Jvv(@F| z{obUPwH^B2K^$RS-pNpH);=;KUZAAksg4NI-Ym4&T1ga4`o(MA(Xr3Enk2n9nCDXk z7iiOpIM{Q;a>XX9>0&oAu6KKY;#rt+w^Ekb3ymMHyNZ>1=`W>I=c3RWHAeZ;(!3?F z$WfoN~0aSR0pQg1zy`41uzG2HsDhW^N4I+Z?bZ|$*@u*;J(ZpUjMtnry%iVz#(SL8C@tceS%!)WX$Ux0jwu50Ie z<*SKhA=ZZO%u73Q)=WibQzfAT%2bVwm(A4cinNXw2z}qvpuMXd&ma!$G+J$Z&stO} zpSPaHnJQ(UkK)!+aMquDgYCoXjV%+njC4i7b&C-|yMG*d>L$J}7lQz(bBMdVoES5Myv2 z=Cj!m4ojG$YD6&u?(oT$Tw3SL>6jMe2-*;&iL`uFV|yqi^H@fdA#iH-9tpLwBY`3Q z_I}()dgKUs;65+^AHu$DYf)@j_gN4WL9xLe8^i|e6-9dN1#D16z1dHH0?b@hd+#&O zxmjb49F{By>P__{#ZVgNPrg2DVzsX^WC!xHpx<^0?iL}mGgZARhY7o@9sZy)Gp zY@iU&3@)EHuU7KgoM+TTe{EH(ap3h6i{%tno8=%BPe5Arac8Fyo84y44-?fgw6w~x zHJ-b|Vz}KiW6{F^vGmgR?LN*|WPe|UkBxfwN4c1>DqkMoac*~^a1S2hR?aiE@P2Em zNzUt8`_nT9@b|DYy%mi!~~Yoz$&jCDS)5uE~=1ptxegBc^_b8B^^6;dNio0`&U2v^%h^klP+=o zWoe&vX?TK|`?&$`6<$ayskr$FjaVc41ocU&7|DKgSc`_*na^5@^*W;_$Bp|J@dAjI z`L9ACHcBCm-eUJlN%So@r&~%{6AMW2H!`;O+4xr_2Dyk^vDr|7vqb38E40v zwgoECv+PL2H4HQe0w!ISTp>BJq~L`{`P$~pRa-91X`VUgb154;t*z5mXPwvvW70Pr zGCjmtfupBw2>H4UceQ%Wc0?5%cXwg;s{-H3IWO4qwDvz8cCW zOl$nNcA;%RXzs*?H;*1k(g^8qxI)aj6*>vj&1hpuW9`&|K7E?C_U6nG$?@0;#&hhw z*D{qydqMG)u~uTs)1F_9k&0P)R@T#=y(Y}(XU3C^yx<`^c8|~IQ(bJFv?iow<_tCM zuS>8FCOE9(uRMenVucw0zaDs-09}ISP?a0%!476_c+rUna1oj*~bAd=h znEW%;q&?ib^@4pZ9|wivc4WpRH@!_JyH+z_Y*r+uxZNwSx4x{^IyIw_+!LS7yl-2b zb#}R|SI3Z>`b31>RG$5-Bb#SyJ5WjGh9H1zL#!(`N;^}{q)rg-edt#!hv3-Izx z&e_AfP*?z^R>*g!^$zi;_+BO1sZI4N(&*<y=Vjxu{!KtZd7kEF}zY- z80MqL#$U?cL@~2_V)6cKaR%$#$FoWqG})wM-OJ1EkqNJ`rGsV#EeQ5#y{$4$l=7BR zMJVKMfffeP6<4|>oHD{<{M~Dd(=kW(Mwf05BQ^%SoRFY`5cPA_V+Y_6Jd}kQ*&Qd# zMr@vsz8_1EcP$~;sgvQ7Vm1QZvkpYz!gt20@H<~3)ws}p_+_)()hz@;&HiiG;x0{r zB_?F>GzGn!Vtlfif#A3I8Vn9w^Wt5G;}OcXQ!m7}$IOX^BeIInHkz3a5YA~=C1 zoi+Dwf1uZ8V+(}Z$6EQ(3$|)xP%Zkp|LK9tzaDst8Y!1d1snLBdhBksy7Rv;odEr_ zewIDu`{{6%EN{a)5O&I6n~of>NnR~KQHmN!eEY4(x{!o0YQ{M@Nu)r-h|F@f<3y1Kj6?NgOJxIx6(#X6EFquwV%3_tIfHEG(;! zj>byso4_L7TrQLBR!GioI0PD6f3IyZTOVmDskG%Ud*s;A>N1~sH!`>AJ&!eR9j!LV z*Y;FF$yLx96sp*h` zxi369>4ZFMCAaVcXqM~VCcdb;CHz{}-=i30`BkUpeMGW2Bw`Zq(@N{o)qZC;2B#^n z`j?I$zTH$)9 z9DalCp&OM6fZAm&xtLO#L8-HGh_uZUZ7O*HR(jxoZ7J@>OK85rwI`%coKG@TR|HM( zS5HY7}$RDBd&s}&G)_vbT}Sa#U~jt?QXOuafWYzIj-5MTa6B}Y*K-JehenZ=FMX!P8u*AYBc?r zmGSK;Zpy<1r~(cP_@7fz@0?F_2udOG>}j2uz-}yAgA9Pj<@Y(m*UGF+mwa?s5B*E3 zi;63U_L**-kVFerXy|jvu>0qSwR(II9abmb$~%7!YhGA3-U}xc9oD{>PClY91RO!! z`wvWTgLKbhr6-PFWH?)eF3D;!_NwKlU|u|bVBzlp67=@VLHJ&FAV)YrpwKFZ{NzA?=>m=k)=ikg>Jc7qWMH;J)f+5Ugm)jm^yiyjC)R(_vVAUV@ zK`fmk^#EEc*n#({TtM@QXLezO3laPiW9qFStJlqCkR0rIE9F9iS~e=fld_}3BKXrO z05A=gpuL1CQz{ykWgGS_J>70RQ=c4K@zx<+I0l37@Eqov<<}`0?~at$!IC7}b%7wFj zeNJWLF@j?&SBPb8hw>1Wn^~9or5@n&*+~q?lN=2Fu2v*SF1X>FUCTollqb6O3x8VQ z@wNYbUNRzi6o`^(T$){5a<;dEH0$wxfA;okV%6KVK`L74j9g+nt0uZ2oJ|dc+Hg2w z6EgJ_5AhpgNT=7N{1DMR*Y(a2a0iO=`(kkIbemrc`RDFC*y7^FyPQlSNPG@ilVWd= zq{)x<0%4+@UIesp=vRuIf4*Bni_ zKMa6Lzun^FTLL(+Ha1DwO?vny&DCppjL95{)v8l&Z|DJV-U3CDtk^N&FbMYOc20SV)$&3yoeg;d5`u9iPZ%qp)o~h1}#= zF4Mwp{4h?1_E~N@ZM&lsFfYgW9X%&~2Lro0td&6a4p!$CWLI^RW%u+M5~YsGz0%<4 zJjXl~ja`>lv@?gnNmR%pQbbbu{2+}nT}6i2>w+s zrzcOxP+@`+`#vd?#|_AMS$t>0-P$j`X8({puM{G%l-Q7wj$6HxI$1|zz()frM2(X~ z)Pa_&d39kiAE4tgV&8|dc_gFH8N#kKQ~~xDeya@R%~kyS#e-bW?~+)>)A#_Xp@DL; z!wxhYP#^H@Fk7pvpU-tzyGg?1#rMScLsh1OcP{=Qo2DfS}Y2xGEe1LgWbC=9yWn0+eFHURs5vm9G*S#+9yzhezAl`}EOYJ?A>0Pfl zQOd`&X@w4%cLW4@=R7ci;qz2JZal+Q+SxuZ2U5*q_)B%a?_a?|qz^mFu1$3{W7UVn z?(xpY%1l()J+NaWmVVj|ZJBwgU`6of9SW!R%XIUcE-22qYzqac7T>zZbWuL6Nf_}e z6{~$+LlnUJ`6hTyK4nd~j&9y?5uu)wOg5+B85m19g+%h>WId$W7oog9A6=9pAcrk% zNZ->;E8dNljIfGM_N!MliR8P@qywqZvmfK*&ZfN@HU(M&NmDN+AP;0EC?!S-KA#UTp zH~HZk+JHFxW!_Ebf*F0&VZYqK+K0k<1+BpYgm*A5sqc8>-dMNm^ZRRgT%Subx$Krl z2-TLB{hb#3ZobbY6?>1nPT|^Zl-w|4;f{Hy4*X#`cY>J!tGxi-l~HUJe;y#@kXAz4f=+$uNAEN1_8b{N`B z9FL8a9o!zLQ4M1H80q8QW%E1v4vXLfjvn+5D`kSxQZI|Kl#+)%?Bi|6cAQx=_L4gB z-(d@psKRE2e8XL{Rq!$`HT!t_rl7~c@juXs&^rA3qrcO9V#2Zo$<8Q5e3rxlFZ_dj0%6WH-rIO??N&c)wUAnti^8~5WU0^CfjG%aw;bXprD$k}$tlxo;SFHOR`^I|=a@(%F(x$u z*7XTNTD!{`sXmzIQT&=qk34e6y?(eRwnL3GM{MAb)el%FRs$LVndp1b`CaO)Jek(3 z_xcz#GhUUwxYouC$u(58@D z`q)-|Tfl0`@;_J|pJhg<+v!Z>;%Gd(aME+t1}<6Gb$&WC-f#A-GtF4X+4nbJJf=lBeTEJvn?Y8UQvsOn1I-riOhv zK|p*uUG&G-@5+T2y;HKCR1!=*cIZ5wznW-lSTuryCXo{;rgIRt#+t1X!!e^gGKm{ zQ0x1?yT7I&gJU|3EiNHHmn*f;Uybz&8PxBhstXSEK`!dB!jPh4D5`qOXFjc>RJqp+ zU;dYePDlZuhg_Qm1w0 z;3?Yzx-(dRboI5%=>4QV6$Y#N-p+cn#TjE)2`GL$cxgDb-ojfTP&T`7B5KQz*5H!@ zRaQoV*V}|FAdm+*iNaeIMrmNL;dsa7(4U|63Rm7c%ok6kH zbG>Xf9kI-*?j7VtMC`^Y<1Y&g!nt}x@cXC90J+zIRVNCd^|3j$1+8u@>Uk!Z+W$m7 zPprIot%Y(W+X+>v1h+$cv~m#N%O1$}ui@WFgim7HDl)Bk|2#t>Ap-j()K3D=WU?gW zaB@t=KWhBh79i|LSF3bTBxNR}fZ{>p;|cNgidUbzr;> zgVu(GMu4Rpm{Jl@Bg-$3EXQ>ykK}+&CUS4aSv4>w0LM_2)7v5L61H(C)DAIC!iScs3wl>4nPa{P^*SvH@;e&wN|} zH)UYs-O`)F9`KA5wLneSLxlslaX&=!&IB{;1CYs$pxb;WswqhpyGWfFFGoY7ksq7~ zn=O*6%?7MgqO*2@Ze}n@7KCV!EXkAe4quFJ?GADG8h(YQk;O*-6g$g%0Ue=>DGL;j zdj@XmF#u-Q`-Zo+7~NWE%ip4M;N4zGCXb_EBjBkif`YfU1x(I8M{oIggq8xso|`J<0(s&nFMtTk*LaX^jiCRnxbM_ft1U zhIkeDYL5nVK;L`jS*FU6*dortIVNj27c!0m%(>PffnshZS4pIGA&zlu#FvH>15P91 znZ=@g%`MtcbJ%fRwYIoRf-37D@~u*k>iLMzfW8zqtVs1PSF*_SE#GO6x%A?*A0zP% zFjEFPyuY?^c4fE2>kt&+d68_3?(?@Y*+k*xnqYOH6D_LKW~2VFbC^RU;Y|`8B&cHELfSiF$b8 z$;$hk0CGIHGNCXO{jP?0`IbC-oo=FMK62x8bUK~!+AlF$Wu63C@XSOD*M0ur-d5|j z+MHTuaM;yhlH#x4QecjkB!72{{ThfjIy>T`*AjM`@Cy3?Ahq_lARO;9yyYAG*Fgdo z*Zgl>+@=n~yeSW^zMwH-rt>V!fw84H^$IFdu(Q*i#qFUBn08%FqFa#m-jCNyabnV- zEopt$`8ty;PazEULOiWB5?7nsr!`!R0VXOH5?9})|AC2=Zt{|X>e#)pAWg?WkmZtq z9k&lKW0Wr7;chEo!)q5X;aO~NZny~L;Uvce<`J_pi)wRlNz8W8WgWv7sw>->WHSqb zr!lX=k7Q!+*N~eOqHdnr05yH#+YVxdoEAR<{xi_4jZ*!*;zf@#|hck!4END(3l3AMxMw zGj_Sxzpn0pCK2_3o!m5-TU65U>f$y2i~8n1eX(<-|MKg1e}aL_`!8d$K%xT&!1|1W z&M`2Pj0?^0%RS6YpCg1m*~uEg}V!s>-1xd!B2>dBPD!+H&?{LC{KMRfSd zAmqk8L7M${8#_V^e?2q)xFu^>`jaYRO(WSJw&A@}|sjvvU^j(ra`MUOj^p zn$)~?Z1LTRRZH{0xE-qq^wfp3lkHrK5` zyFC(Q*dr~0B1?BtxqCGU8+O}IX)aFA(dPtv?}&28$vJvnuet*o)}yr8WVIDPGNo@$ z&ZJK(&6H}hm4-;2lLsr})$b$^LZPp8sN&t0_8^r`%bwZ%$jXv><*LXP6V}A)51<`i zY&Z`mZ-c>N3M-WLF?>45h+rLWe!f8qTNsnDitKp%jm+`Qlx`vqFl<7GZ4b#tw*0l8 z0;N&3s=q!_D5_OC{8foJ% zm=lsS`=t^>#IDBxo)JvlEZA&3yc=7m5_`Uq;F0<0!@Q1mDEl>_Om@c`A z&)}W=tuTYgo}Og%ta^`hVO4z(CO~m=7jkW^z?Cb?<8>|`uf$H~7Acv`)7eZuVH-oF z2{eu6(NSZ)Ac8joskcqOg}2WR0pxH4p~TMSUWljos!*M3NOH%olDEnEQp#zk1%MBk z%kROpyU0Fvdl3?WCBF7BSE=IRgDb*1#q7MX=Du14Ygey^d4?8o>V%#2H+n0e^C`Xn z2-n8YZtbtpbun&urW5u)+;5;}CV4ev#^ccg1OV9?{}DD}IZXOiVgi2hqq=F%i&PQo zzKq6wK4w{<$0wV4Ix&s=N45aSQ6a9ON*o3Fi5Mjkv35dL?V`o+G5Z4I+;q-j#gdyy ze>0ua`>|cXr|Y|*E)TZWEqxxrOecdv4JigrZPk3a;#p-Qxtn*&9;;TWH*5eOK9=B3 zSzy`9?wzXFs(BIcCtQnYZ{Wz|AK(FuNu<{-0ft#CN(-?%U%e<6i3Lu&Waw)ZKnnfx zy1i$P2y_xi*(P>;G+N9lG%L&lSI^z2;hW+WV4JB3NFfaxPxug39BJ&M#%5qpvJ|aD z@OMVF2j8lyMg3o>(gglh9hU_}B@UdEI;-PqLZ84<2HZ4+dvf#77pQAcRF8tfWfVO? zGz~UX*bgUNb4bHoJB@1@EL*Q;(JkNn8LY#~vkojX1qqE_vTdc31P)?O18?0)(SoKA6oES`2Uu3`ms;Jc)>)Hdt!a;fchd+#v9e^Sr<_9}RO^k$lW01zZD(&b!YJ9V) zCd={47y0_Pyb?TGIt(tGADiXoRS5&ksCBWxbfZ(QkbO<`&v^ol4gNYJ#Yf_hU!IY7 ziZH!FmM$~BA+sVzpvudMjdP6aNu%_nd_3}P4p!T7^kG`FKo2y`h0Ejd2v`V}RddJB z@(iGlY6o%@f+D!Nq>r_*|Ht+MdeGpF3 zC9*W)*5Zm%jL?(8icZ%#bj5eIdSlw(+aIc6SumlIlS46USEd~jqW%6O8V>O=Eu-RB z=&55`t(se`dto6$2R2TZ4$~Yg? zHU>U^1MciH1DShJ@yv2e{1I8_$9Rfj8$TJgjgolDvMho-FP`C9YFh23)nudx!aU`n zvBGp)?d|%NsT~M@BDFWico0~d$#7Q7z)UB+dkV~J zA>6%y7Dt7qGW=>kF!!%!Fcb}6V5XyokU!30|2JJWte$Ky;NKh%bzA_39?w<*(yPZC zWreN!_O}Q*nto~2fuV}5VvMQgB2E9cwbbiQlkqh&X54ohcV znZ$KnBYPJ6zNCWA0sKQ94W**OnA+9~!089P`=>gBsuIk#*z+G%F!;gZvrH z0ZAfmA6GeT)jE9E+=r>npG>TV)c2Qj?VCIqQgP*%{H8Q5MFY%rM$z{lHDNH6rf)9jT~#@F2nU#c zDwbTqD9wEpfG1Wis7VeRJ6G6P4S&78{obAh{_H|>eBwRZr0RRZgIr`|@JJ!{hVDh4 zr2<|CpR4&M-sV8<*KS<*QXSgtd!@4}UD{V;4$-HkM|wK2hz+W#cVF3{)5i# zum#9=-r@raOGItY%KmJpKp{|IRc?-V;n91gOFDHyGG^CGC3ab&n!LQSKPg&*0itxe zCTKW%)wT^DpZ%YsSHHY}M-M#qTbYF}ee$LWFEc%Ppi==s>$o!Agzlgoy2#haPQDJihimAwcbj?1%h(M&@S*F z67DZvW*juJB7wMy4XXn?g)J6O=qN88HJ{T4Ac@c#&UAv~a7`X^a;cXJTeB4(665A* zQA(CNJa1UVO{Eu2%wpV}z=H7CS^4!j)}~Q;K6;1r|7VKMQ@Xhz8e$`QSWHBzY#ER#;(d2h8n&Ei6f~MEuXbJZ@)V=Fq4S3+vt|)8mjal+p%Nu*G|%kDdhCAfjM-) zcT;|Gdwn3iG&bS{FqdhyB$Sa$JnUZ_ZkAe2JjsR_k`{SRM#nfipuRE(>9E$lx%^8w0-im7Y8xT_AmJM@ z&bxViMKz4l(%Is_s~!gStBXot-AZdJIBDeQBJS6?wnqKGJH}yk2j8*d}t= zbvxbV4c4`J1e)w`(|*!jPbO3m_|{Tdx@Gytr~#U@Q0(}w*F^FW@g%RqdfShTS`eK? zoNc}}Oh&-;+37NM=vs^pXKKP^lna6cxEJg>D8$yM5?cKzoP;m5@Z=PJG~S&05X;Mk z%7Q{{O6=253cbFBIJ}{8qUa&VdTPFyee8HT2D@Ifms&iOI)G{RKU%T>$jJX(NsS7- zw9ey4)epWgBxI(1XhWep+AWQInCYJn8EKFp=?gMf+`DT?;bCLj2mV29*r77?xa9yfV z`wMH!Q%{KC?&AM$SlSpJeMu|(e(t=SEJ$Wc>SquHi$g11ZC^($C<63ZdfL>>cw3GX zij(p(rH;6#UhnDr3ldbht_S;wO%a)QQe(U zYnxf7iAOg(2cd&1#pfG9K4EL|V`61h=)iy{!C(tFq_D%XskkSd7oycT3qzZlzvt3P z9pa&-aUkHGB4k3UZ0EmP`*gaC6)N|+-tg_(1~NO3$UZrqoSgN2R*D4&jME?S?!8%G z7=KS;$}S?r8-r6gllmgz+#-lxMc>ct|D1>0yZgGCI111IIS(7qX@`aYw^Y8;XV#aufF$SPRY59XXd?i`E+x-@RSzC{%i7Jg%!%gsgd zK$Nf&mbzAA7Jh?Vp!jRoPO8sM38=!8Xq;-Rf&Z=eOoPG3F|dR|mdi4&KgJu@@*>^x z0n5eDKe{+XZ;PdGTQ#*-?pF7UtJhEU6_Bb7kG!Ln3r6n(*xw`Gj1}|K`t#b)g6D%w zBWm-?EZ4)tiJJExAU5oZy0XsIP6q0}B_xmHrqvSWU#U06zdz$l`zgOR0^RO}_b(T2 z9l71TUDLOMA-QUOW-&3y9%u^y&jJk7DRx$BpI}e@v(=%R36bf9gDvu>S(a5k`g#1z z)cg;WKtHOT+4G@~C&HFYMrX7IMMQC?(7;uD5Km#RzJrvrP}fTdNy2tDIa}+ES=%Cj zHiLY%)Ou;hw=XNN9s;8ZKBUg*SP^v|wu#K{oZp&S7S|pW5!AcTN0+>QJj(C{}&uS)ZDIGN&|EoEZ z?`Swl^dpSx-@JQ&?k`yE-8V^C>;}7$E;DE|9uJ6);u_FqRJlZYGfEBDbbr!%=Bwyy zyui2jt7B6)cS|QMNNyW|XbauD^tFT*QfoB;Eeuo01Zt{)jXDeog?BX^P_RWba%q~c zaX*2tw8!QrKjNOnlD2&_pHRM4gNEarL$Ie>866d4!Bx^NHmY+kiTMJAy}4_&TU$T8 z*GL$9F=5m%xk?@eT;p`hRZHW?JH46i{t%cDk*inOVmYi|Ly((Rj}ZY^PM`K^73-DH5sq0JFjGG}o`$-jms_l}?B04+ zpjYeMf`vu)O8ZybPg)x_1x)0w9w`$|Y=Nsil}StZo3|1&woX}y6ekCr%y zu3reE%%_77?0`{I4@97+dqN-4t^9LuX-TJs$QFoVX65g#}-h- zo*EM~uOcKdoKF52e(d%Y)-w!GAFb%b3R7c0e=R@0Q7{T@o3g1^#~3dRYW4I}kBvgJUNtg2 zt(0N9`?t+qPfUeAQN41E_`1m*Fg1XSyyD=h0jmSZun5yfw5a{Jsg#kbFO$f>+y4Y; zNdWl(rX%(CD~XHH&Q7Yq;{kDN9I9t-hecOPd%RPF7*F|a99B`{^4J!S-Ql^r{A%d+ z9l$i1yEdh=xZ;C;9vHd&PwWYG{DmL zdd)zH#4Kqh3Z=IMJwN?=P4hW1bwpR!_hRynWtB!Ts{B3MCguBAdhR<_R1rXmTvY#b zXtE(3%P^n?cK8EZErI?qt<9$Z(y7kXhkgSOlf5I7xvwq*Jgr_sno@ z2E#xAx9<1WKcR_+#mI%qLr7!c-M5}j?8Y6q87bv7Y+S^FgZ*|O{N_f6QphuwO;H70 z?M-Zu>dUK8s;`v|6fBF0TjhzA^R@VMpEvfr(;yR|M#1bR`tlOc+|`PDvE3Ut7Y$_6 z#!T`+`}@aX+w#ok{ksEkLbtA2a;CDba0f~_26Fe2fkHt}{nAAYJKUW0r-qRzKArVa z?LS`VKZFJG*BBM*$U{nzIMh1+;`#JIUh0)1vy>JSZ^_^^z!E5zf}kPKI^vKUDPIlC ztOh=oD_Fk73)68en6zxM^X5c+ch%+c)OI1cWaa7Ceq zv%_%wG}u(hpC2FeT?FL!;X;Rd-ortc>pzE1bFAI`lK}J~>|D3QUEpJchAnpauF4Lw zN9sO}hH_Qra>Hb4vS5UJ))7RZ^kvt{mvRk{u7~Df?AI!9svqRzl&oTvE%tAa#7px+w#dSOZS@Rs0 znErKha@~-zthB{E-h(C;T8LJ@cL-I-5UBw4q1za?XCd0KiUiy}9bGSs>vUKNU&-*- zO1wsX1hol%B^;L2!U|*`GhpvdU8Z>ms_%3Va_-rU)y+>BgJb#BZcF*y<@9EtE0vQr z(@n3vyVbGInHU4Irg>NMSEbR}|418HRlPI`i}^U1dj;(qNG0N-gq)>p#s3zeDQLbT zzF+NbkQ~<@zwgAk%YDNyWM*vox&q?)of&R&$mL)h20l=HBPEreW~$y_c0NOye?MiSxykN@Y<;s`Vs zTDO1vd^nRpfJkWF?)6S=r2PE9+O}&5s>i{D?n4U4oA?JfGfPyNRMvX~3|whJU}s6JdE3kPCcUZso}%Ds3lM^7F<-_SNs z5Blzi634IryKbM(@h)|#LsXb;fQ>w?yMM;RI)pBKx|i_Xf;+P~#k7zRI7S?Ms2)lE z%rDvR9f@F^f0UnYb5i7?vd)`PyWo^iRQ##GLJ@9i%d{NuBr6LcDD@x{y%`hl-@62Y z#8gA494m(KcbXW;0Rammp!6;6Mqal;>1FU%Yn&DwSS=5!RN4LOWwS4stjvAis^@oK zvKVWY9K?V0ovE;_#SfNV{Z;QrSF)a<+Ewd`d}$e;Cgf0B0hmmM~%d? zUfpe9z2*%gVfhVVQk9xsRh@Q5U2w63UhLE+>0zqwFXZ<$di)Q*K}Ii~@T*OIyA9Up z5d_D=?8gIcm?5WlZ9>BFDOdkId}2S|7AXlfXUnf?^k{|+Mlx~+4eQ6}HoUd^4;up3 z$7%QDTG*-?E7~!h)iLeTMSznxO!VfJUKLz^+3T}%<8p3Vyp@+~w%49vQ(xG|`R^&e zNTmvwE$|43Z8QaDYR(%X5TJSkSh}YVnUg&?HKG*@My@XPGMF! zJLi~h+zQQ2d03oWRzi07*G8$DI!-p$joc>slKpPVRp8G&3WZw#)(oegDGUOu;Y|MF zK4P<9pjFa0$2?h^>@5IkigN&%+&OxTFfLeY(eW^*`hrT@jwplO%b1`cbu^ZWv9+%KoRunLk?k3pL z_5tf~G^})e-5NBoIg%0y75*6QN5_Yo%$$EdlF*H!MY=aQImIA+w>sd~JspJZs7i&R zHa_#}2+%Suo>&;*1v-%kWNdZvq|Ympfn6_O+W7&t5em4c!?T#}JR7fVYLJmI%S9g} z;#kRk$~qD?EK-o6v#2}8(k*6mPB{dxkd12gw01?=QmOhqozCK*s#e-C|d zXm=||H_yQ`1U&u-lHad_^&Ckxb(ht}LT~z>!3vY`zLl3-cLsf@a=X+);yyU!Ia_|V)F2jfF-7r%m`fyrNTbmUl`UfVkImlsR` zRZ}Oa>e}ML;s2OvyBT$Xmht;(CfHn}2-|uyfP!vsVVv7XAS#{JKQTq{l+c^dK@VXP zECOW!E)sx_LGPDE5R!=PK9j_3u{{#QF2RlFVQ*gq(0~iJ{Ha!i#<8#E+&gC z#74O<#jsV_l*zx>LMquz-07G$Y(e{NJ-^2H_^?QD-hAN7H?%IjJysxPGFAa3Zwy$+ zaXAQ2$h6kW-vBH|x^6JT$a(&AE=+#TH>!TYV@_{=g^FCO=n$Y;{Z(Z3HCz5Fa_5^W zML$-6>C{Gc7EefkO+pghKNhZkiNXs5N%xS%3M8?D8J61-rKQkkBsi(MkR}<4!c|CO zCP8AG?Am@XpLdhS{#^Hj_^WZ z3xVsDhNBmTb4_n_0@d-dJH6JCA^?=N!^xKO#ayb<+3!2v3@~Tb z$uFZUvX{T}Yqtx#m%NccF89NpSUP24&TtZ;kdF9^iyY^$6mhzVq>H1VoHC)ye_}u1 zQ205wv&L#CujpRV}w-KHRmDtCV@TU=brSNC(TGKah%b5eSe z!&8F1gLFOt>~X{DO%PWI6BZl5h;CwkTIAlujZ%;x!cQ;Tsa1Lpjj^r;(@mDxy!B(o zv&#^&e$W{&k~;L5V1YNsllumx%zN;%D%qKJ#=)h$Hv~EYh_13-S@`335sMm_e79#O z!S%FWhh=mb_MxkY)eN_2WHQT9efUq2{cHIXOHpb}n|OnQ;E#`Ykbx^SO1T8aoDHVOtWAd%gs>u+c(G8XVroFstL~?i--(BD`i?9L;ls9qu*xim|^g0#F z8sw<=`}2hLmoC$UB)4%P(+~XgZc;qvn8kFNb0$q7PBIHVu$PBceIgA&Df0-<>?eQO zA73MWXcOridq7WT{SfbXsa`>m4)c6-#$qx09^NB@n-TX{Pw{%4FqUo!0-MRaCn2Dp z4k4gcd{&SU#TumTy;BsD4%R0zNHsB+#~f^jvYA)RFASV6fGUmD!gk#+k{EBN%6=VR zCm>PEZI(ZB`llHbt_O43(n$Y0Is6yb!bngdLphllGkPgykx3W>kY=~kg|UFPqKf1+sh+$rm| zbT@7y?{yva>)e)_sTkVIdLky%bGnmKS}<3zi|x@EHx>oIUVVL(bJwN1sC$$#$Yx3p z+O_2+4Hp{%Hp0vnd5*=a5Rus+AqJy?LBj`Oj_69yv?}ZEa)E!KlL<}nh1}zl{>#rT z>zf4020)Z=@@M@s{i`f+xo0rb*Z_d7H z<{tAe_ZuK($qYwdrn89h9hFj6bHhE#5O6AWpddr+s?i$+zEoV}5W&?w4Y%7(euA-M#`nNX8=`tEw!TP=>rS6z54wQ5nwk=JEC_?sAT z-$7NDr!GUTjlm99SN&@kk1sC0k-66cc#fkuMsRZilMTDJe{qaaYM@ro>1IuKwxHhw zi;FFIX>^o}#&^g8%R}&kR;XKPvg71UEz{ww)#1#o5kEfrDq@??$Mq72ee^q6z~Vxr zxKR2a4um~!4m64{jg^!988REk6kvoW$X5A^_e8Q)&9HE2`nZf8dYNIn{q3XU-ivix z&MNaV0uLOZFcOFq$?KZZ9eBeNF<(%{YCi{_v=KNpfzyW7lE`lP8lQc2?oW@=&=i7N zu(Je-eIq4`-0l^@pub0O9>Sk9O&G;uAb?!eqsemu8vs$3a2vbGiZy(9M@w6v(MvF& zImD~JkYSpN=izL7JanPw!amocHV}56e{CUj@`FT)YKK>OfdIOPCL?d+O5D%@SInHM z{l1dJ|2lTKbP>Be2$U;qVjg5ef+4hcMz)>X;Lu-#j$13Er%jv^_j;hQ*c_>lCzrLF{E20yl z-w{uJ&PZfnsixaO>hJZL@rN0RQMbnd^)XK6bq#v`cxO@?*I@P4CH1%;Qo*8`+p3A> z_db-t>D4puXswwAQu7{j5+Ph#m2QWfj$LR3C$Ydi6C1=AD)~7AE=2- zF3iJ&D!p_apR!wpkg&mw!FxLWJvmL=lnr}amj;SIiDeM)as3fC@eMTsK|uo&7eS$v z01DXw0wjq-#}e|YkE#hREoFu%=om7=V!&L1GTq%6hT^!nGvjRFrGK>y;yoYQK)9Y1^58YjlD11|-%N>FKqC zlwdVoshr#@Ya=)2qbWyM=@vwbxgMHuQczVU&^m=9x)js+~ zUZ3Ukan1I$92ztVyXeWO7Bkai(P``zAJ~`>vI5#RT)4U%%pfXO{wHnl|D_xpKyw== zZVw@J)!`<&oPDmH%1M7Xe4I9k!zGp>2w)~x)B*R;5ExA7t3zIYqNNI(+iBda%`M)^ z*qdH@S||R~g|PmYi(GQ|(}jzbS@Vg*O~jb~gmOdE7XnU4w(&tS8&(;P?Q63LyRq4* zx97g}dF#K{>{P4kke6IRH@W})U2<4^bJ;>9aR_HJhlL71?m|6sDX}@S%>9PJe@QJv z(Gl|}rRCP13!6p=J8ggVFG*|XMo5Pm20pWp4~f^7Z|UEjH;s+P5TEeI-R!9Xyeq5@_V6;V_`0Rtk2KkMqx zfT|t3zdnEZshxTiFz1MGm>AR2$MaLcVY43EpVg(MrN$rr%ceBMI0UDT5T4=W&B z*cWJGtKuGSlVa-X{!DIX0APfbhrWwSG8My-nqNKM2jSlL8{GvJ>6Y#QqL4ri|9s5C zQR#OlP1;xkigtgD`0wFSfesS5A&`par9=wzt-rV-yV>g8#*zx?l-F*RsrJ0 zwR#!=|JS6;pvrzlC@o3Ij=-@Bz4z@DbOTseH`AGA-owIjTGrZfo~;PCJLYck-#8wyn?&|38$ z6w62+)!)H5#u5N@)JowifkD))mk4`buuXT0SwN~WJvZf^%-5vd+g|1%6&cKgrtu7bny{P#g0^_&l3X`s$fRZ8?y9sG>|=S-&wPbWPK$mw=Cx0 z@5$73Id%$_FqWLC{Ko-gUh4F~Z|&8`z1(QSe&N-f;nVNsPY0OIPr(4;WBXg%M@w4A zt4VB|XgiZ(w&=_A_ZDUXR==)^y=5LclPnzv72b)Scb}d%L0BUc&?2dE&;=R7_;Fg^o1lu8P^J+} zZUm?-=)r;e2>j1tvA z|6t1u+#dDS`Y_sCa@*f!h(t8(-m}vA;W?81ZMT|r5HC6rYHWeT{$l%O6TmFJC4aQv z|ATR1!wvWwg%?|<s{l`sNFu zJ(>pwR;6!eSGXFgJ@9R2IX;D`b#RKFs=ZKjosC%Zp~y%Xm?%unZERDQn<}p#VPt*7 z6>?HYWp>Yudh(`f4q{I{l3V8Go$R3W!e#X%A1HwbB}ueYYj^P@9RW@@!v=EKxns(0 z3sn3R7a9fg_iRWO3bl|rgcNO4QHuVVBZfEu1(VJnh`^!az}h>K(s!N&d5j$XOzKky zfMtg)jK5nFP{ud-aaT{4V2dk&WJ*5SJ3mUHm6SVl!Mti*Olh!%UcG^{A8HElO1p3F zcIvt&co^3vMcHfZ%JcrCAhI+wJR|1~8K9MX>}cq%S><`|M*; zwAhq_&iZH17^-!{YUIi8EA1F;$G}80YH@!Hw2-wElG{TlcLTm^R}6`v$Dfs3V|-5l zyRke6Z#}|P-k%r)V0>$H>kd*rSl!G{G=gXxAPSBQ;Ph;+Q&qEWhQzOja=wsZJ#0@I zp}1)~lIJck_PWo8RA9GT!(RDm;=#2b~_i3dwLi`e9jz^f?e6E^*OY`^AvFUp4rD z?w0VUR=KmiqLcO4vS076SauOHLcqDB>PNzHkBO2#-1MoICbKs21SNDLC>t2P%cQhR zj9Ts2<744e8ci&A)J4F64X2*{s|OV8v=I&6C) zeQj0ol}qqLd4Rn2#XW?DWI+XfApOh)vh+bIu6J=>xlYmUcxqo`;99}e+^o%j-W=`C z!$J7wClmiFcR(Bl@FIBL1IK{0s~cRj?)DhEI3WW$#X_(%WEBr+>3Yb#Il+o2g@F#= zaX%ZU^Gd3L3j_?GdGU&aIsv<82I>Kfnh2ru6%!AHSQ2{EMT=m6?m;n*5>o;eAWG8X83aC49H~> zu&=K?o@t^O^S{P-^6!^pW?Q0R#1pk9o_{8_>*j_0`{l%rKg9`0_?|MIb&gR`od^=MJcn|R44r(F5ruqXxLLZOHEY-%Tp8;D4qo*7uv{TUK zYBaM2T+8O`CGP1WklqI*1f#J8T7=pJ=$3;l5LZ_q(6>IhI`uhSLHun5)!X0;BJ3}9 zUdMkwF%O+@`+tMJt$U&)DY?+h;gCsNcEJcHseEaP(Ucm z%A6&#rv|`nSRy?udvy5W;u#il7bxU<<$1?Nmk7P8bTiu?_UNMip(VNk-2y_nR!MA! zb2l1mz^tSKz4`L>5U{fn8YY7@@PFciJ=VYi>bHf;8vodxqFSGziM`AkKo3_iTo~)2@DQka)?FQtmiv6v4{p(GO1)8ACx9{~PX$5u;~-#LLQzHB zGlRhTfSduK_Ua)zt}(cYEfle4LvlN*WyCcbzZt+}HKLJq`)DXc0X?K(Ojm7Y@MZfo z?0=KW&P{y|h+98RXR&NG&A@JNJO$GRr5e3Ikp3I4m0W(atFvfx@HfW~;AtSO;aP?K zAyfxhK4ySqIWC!L<+))xIMAS{42b^MsZEe&pOBqnQz3a~=M}zAz0{Owfunh2Y6H)< zBtRoGsDh~+GJq?t$0G1mm(2HAFm{yse|(SxY+x6M;f#L8?rj#c$0WUDU~UA6qs9Oq zOE*x99VP8F$20g#xZ{a8&?(02(a&pR2-Xm17lluxJi| zgzMjaI9hRiyvko%EdQyyqB-wqVggI|cqt<5c*k6m5;7WxR;0^ku*znWV2o2HU;g1}@3>06z1uNC803DUdw=cJdu79dj(^le+e+(h8yl&7?1 z*xOCjKTq3Zs&QKw^kGv;DFzz0cgaYS)MXy)zl5gnM){vsIE=YtI0J#)CP%>x({hOJG`t$O%e z$vWGzMp9D}W<~YWb7!s!o6+ms1W&o13pu6$#^!a9BwTJ&J1FeFJ*ij5HefU#J1tUc zE(`G#rLe|hy+B?cWqAKkQ=VZyvC*Mm;F~rGbhL2(;J?BN^su-ajw#l}>=*3i%=48j zbm~f%>>?W9Jk7(|G1R=Y{Rm^^S-@ag8-h4PuFa@l=h`A3DFH-9dh59T7{ar723OP2 z_=aGvQZ$SHsZNwRqIpYM0dbQ#Hd0R|${g;thNWlUOw|-rhbKy-T_#lB8Tl>za8;;W zI&L(~6ccf1R{8$*RAUzW2gJe8^AL)BjI?o(1c8$5xN|P#F~gX~`1vO3-7~EMe0@T0 zi-qFkD0jzc`1<{2YuM%9@bzuB%_*g3^z|8xGJpyNU7X$kBN?vuVu7CoJFHMJ*ReIL zdffWS9vOOwg-gzT`3L@%UfL2M1L8mUB~W;Bdf04H+J^W7GO5qb=PW z{v&$|MBelYu$A6mk=~5KUJAZBPps}Pd~TDP!wy@ln?b6x>FOzM18jr& z_ra}(UuA+rF&|2PZtw%J-^-KUt0@M28W`Z3nDRF5sZf@yP;zUUr*kFo2y~$g&`Co4 z64XS^pJ7l4)%qbR)eI&s(LIFTv%<8L$f<10b?40WP;A z_ww(yz4j12_*MoSg_LjGJ{UQydZuCQPOu~-<@}y}*QduN1)ABb+FrbhM$*#yCJo5x z=SEtjzSI04yA0AP@cfH`gB^M#jKG-qU(e61;unxsjGn(SN?rEKDAeCa6_j3=2 z?BASaD;5;f2mslvSj3k7S#%#zHJumwAoFx>p~>?hqhBYzdWR2&Wnz7h(iO2$%F?GZ zF}n;jl12*FD91|xt+-dfV5hgPd&Ot)c+(kZrXx?Ms{)I%@ zQz83KgZS9lfYfns5ItciNPPQyr&7U}z2FVL4*VDdVSV?L5;{t8@Uz5J#P|LsilmdkVPwhQ$2eHQ=h9c+F(OLl8M#wTR6bWnTYr%bZ#=bV9* z5r({Q1c?9E+pk8*V#Q7%gxT2rM1-(ED^~ca-5DfPHkK704Q`VYbm-40KsLMQ@lSsi ze*SvB_gZt6l3<$Hn1gW8=_*FfPi;?w394`W^)zU> zTMve2LoNVrxN{%5nSEK4@pCLhL!&dor;G} zS)tq_ThSfv&e0gPFAG5K?)8LR(FD-l%j=ks4U)4nw_H&6 zr$1%EHiJjYSngAD8-xVDga|5|GrwoRj5keZa^iYW+YHTfG|ZcKtTbAzpXVhQ=5??( z6|d>hjJLS#rf_Ujza=k^1onx3wPq*2o$i%Es5>9@T`_^1sFr5x0W6iDS+KqUeXU5V z{O8V3tEq^c`>SUaxxCTIT*#CBmnH_J<#-dYzv^zo#%@%&XgKoC*g^m!n8$&` zOQY>Q`zlp*P_f=sW>=jBj>A|6-D|n~qt#%lzSG_s9%aJ>Gytvlou8hv@r&=@-itrV zVtng$QDYG+rlfLq|Hc}x<_VTYtLr9RfBF4VGlJ5~ugU3*d<)lO@@S%CuD2XL0R9y( zQ=={D-2w!F^x}CSWX`KjKihiFhF7zfeL*H~qOfT`>Cs;3cl&&Z_o9#fgRzfX^I#E6 z(4Bhk2#${+=WYA$Y3Q+YlpJ`AechnmL{$~ZzDVgJjdRv&@bnSE6ebQUVVs0}$ zjo^%`$eP=VOhRkYY#fX$`_PqFA1IvnjRaDu^EboQV&Fc%Po6`=0DK`9^egOz_>%*s zP)CF+^X1-0MOVf+^RlCnl%{`r&)DjR4$a0Brb#wz& z)A>gaXAc>=T6h7IYPE%P_IWtnR(=g$S?~Fe16YC5f}eG`K9G64ce5B672p}|>O3gNfDS^a^j)x1o9h&*49lwYn$4~t zu;{R~*aBP%^tfGUh`@4XH_$sjfEDCirUUIfE$b%(IRPZFk?T9KvbQxv0jf#P z{Aq$w-9LdWQ9P7nbQCX1zp>}q%)NhsZ~xz}0N8U8-xWml!Lps|)&o{jIS^LBrSfcN7g^}2262^V~!FPj9J-(~jx zqEsoXqCj-ZSE}=wHsS!r^s2xaVY1JMA%t}>zh!pT(+z!5E`-}iO(RjIa9do)*~wfQ z?%9L^TVQbnL2;+N0<7~KWZy(T52K~gGc|2&!yV~qHchG7-yP`%>EPX>AL6G_V@m3|eJEyDzzlDEK+$>YtvGD+M)Ddr;B4GI7L{hJlfBz8 zz@*yh9X>5{nEIgZi)U1$j|_L;Qo(wttL}D{!rQb7LpZ0|2D6J$b#;HsEy8Is6KemI zU}|4o0?RiQyMOY0}?9nesHe}33*p>HV* z5__ZcJX^Ij;H!Q0P6dGVS?yT@k@fv~k0BEPqRdajRqaXbG^T(QUrhkbm!^yIU^*L* z@-PUf?L(f``^9xf5rz>gz5ONNj*?GRQpAO{FzTISwEqRxT5(=GSs;~@?e9OvIDI;2 z$t3dS?$lzM+?AEw5u?vso4_BUbe!gSc+_$&9EQB3d04dP1PWq@15CzeLQ38$`IqR< zFR|rcfau&U9_y{@Lo{s9`=M|hv&qxw%ocoYv4qsl;0Y(e;>Dj`^kNIzUzvC$clxgh$TSk;{h1v; znwR>&bvWLjBC+9;WW>G+U7q3)4~SbizXcYOmt7*zs8=WVYm+*qo28j_%XCvl=0GR* z(S9Zf{a^Nz#-yrIogM$`Yz{N^?Rf`Rb_WVXv%&-BTs>fOd9Jb2(r)|@j)-#?tupkd zy;IyT;Jr&|Z1R0B=i9;Yk%={Ww*ATL-Fu@^eT9ll6=Xhrw$tacv8j~DcX}_^4j&C6 zsY@843h-^Y)sJRD*5-q-tzUsn^yLsCox{%c@zdhP`Gx_|n97g7p;^^VBc55G)BiZN z9Bl>t7CPxA!Nu5HyFb~xhn7hl0H<2(9`+_p9Bprr3qU-F-`tCV_n2Q_OHdDQ_*rk` ziM3X*4){OiX(u%9De`b&q~{__9QZ$`+9eS3dwHJogGa@$oGs;z8uFn`*kerQ=Ib0( zx$xAfLEgsxdg@Un@rvD5=;TtF@IJ4}!Fhtm}?c3p8 zs`EFub`72lw*D>yb4ITm;@`~#wkG1dt{%(l75WYF`yBAxkJqCWxH{{`pqQvrcqMS8UroR=VC@uj|%n`G^J_52sh4i?LA$tg&hp?qNr|^mZQ6NiH-(+?Len?qC2Zmyiw?M?8S%#%J1;TF zom~X{7-$*Ij?{flw*WsqXILj0t?zyv3$In zehrj=2#gv2Q%Qyd59)7Jx{ZH=YC_$e4y~r5H2hXxxu!ls z304|guh}_z0WE&kZyK-$!#xu}17uk=!O#JQPC4sljQ1p`^1|II%o7E162+~PD7_X49feR*FHJ3nD3ZAD2eBv zSgQ{rVvsP#Qyf_hHnHRgJUI9y;?Tti>#%XQ>k!D}Y|ou&*Ly^7;dX_2%o!bS7U)Kd_w`?snFz$EwsRW!l;;`QYjP zR4D-#O=!*Zl8e6|V7JQtrLD5DZj)DuK72m!+;XWXIj|(9bKzGGeq=zp>xT)xGO_%- zD4u#Xu+2&ijvlfP#5VQu3zZkW!n4FIeygOeC%>neYNB3#Hj=4c&(qU=Ytb(eE;jG+ z->P`=w%yuiQ;DO{3y;;kHaMa_@sg!L(-0~=g8^ER zInBFAe~INkdMY8h(6m|g%If_G!I||o>)+oNl9z}X2C;2mPR^wtv44Y9F`qd61ea*H={C6iEt9r43&yX(Ol8)J}%}Vu4f76Q@$RU?U}HCJ55NpD~2)2RQ*>$SXHO9g0FD#Li^-W^r9vR+;t4>nY!d81u0ZJ?qOmFST| zq57d#KJUq-1FOwr%bYa2BR#ov*=r3Kwr3OS{L$`xzjCIgGWYR`ut(~bfn5f=c!7ne z%qvq!qJy_&sGsbnEnjH}a^O_5sbcm}?k z33?2e;O##hp@254_+-AghZv(a{L_99QMZ#5{L@EUKt;A>`^tzMPtCfKdJ1?E!v`}) z2Jf7toaT&?!1tBh;-q*@7@NdEH}xLh2T}rk*e?zBg>)mOuR+&AV7O(<1zQTI{|Bkk zwzCZ#6`*-WYlOa?TPJl$)gJrgpobpZPdYKJ-5vt%Ock|w9%YeSReB_kMspAFh^@LT zr?6siG(vm1YDZdy2HYjo)$IWEa+Y-Ty(q#u#k(Sa9~W^3RNYzr2y-14qlHvURLV7B z6v7nU^(Kyjh022|0un`=u+Bjet=;iu^4%=3GrPU|Ytyn`r{rXwJ$@!fPH5Vv1JNZv z2*cVclPxy^a@$_58%g88gFug=|-BudZT%c5F;2IZYp@djS`tc>F9mpRV+N$n2# zzdUMr+~BiLd}HkQJ#ew~ohMaA>kX>9c!(+6m8zd($988`9G`w4?j~Gq(JeB~-bJEW z`hEtc_brDPJPdtS<;V2enm@*!P0A`Z&k}G!iTJ<;N<$XEtYm;?C33k$6Zt+EzI5%jv{xw6dZGtx^`&OOhvn{A?1@}Q9#x3IAw=FA-U;UnFv|j~}H!kj-Pw)7$zk;U~0vIxuStVX@Ap z_WdHWkb#*Dv{PrqHcK@ZiRl9zWspVItK!mFGDkE{*F54o;)PyA7pD(askZCgO|Qcb zyBWE21H9h+0Sm+K1oY((BiO7OOu2yN=o1&;uwx0$9rYsSa>Y?6xB~+ipht4*R?Icd zL$K92nJb9RBDgtt=~y|vFpp<$OTh&^kn0iiJms^JUc26CF=>(4CHP1c;y2OF&kO4P zv!rZwHOm?^kUY&7CQw3z&kEMXg~4nMKOsw|YxRo=`FTvT)uXoo`3cyKG5eQQ0*$67 zlDuehrTk!p6SIZE0daa12is2iAj~sLKC$ijX>E{y-ik2{s^0q^1T1legv+C6E03__4zCZ!KZ9}?5idyXbHi5>Z0+bTTakw576K=}pzs}mHQ>E(RxKG||GeE^0iv=P3QU;5` zZlf(&Qri3hLHK%ZzrOoAl0V`}PHAV)bpbfC17>5!I-rr)a?y1oc#_3;xf)32YV6%O zf`?Tp#XAkJM2tUcPHQy1SZL@!HMEbl1NwH7gY>$lWZ79Qab+mQ80mTZ=onznK-Kg%1~ErbKSz?NK(0 zugTebN+6X$TtXEvOs<+5IlGVzia{!4ZdH0GbKOdLdNdO)k~Dw6S1$?t9-Fjn6Q3>{ zlRm}Rn@2AP8RYPa3$=JcCkeME1nG|poSNMfrnvbDBowI?y^~v2F&El4R~gu&%?aR734(nb-i7b;X$budQqps&M>gHuk9qox4^oF7>K-kx|Gj+F=n7`4>mFlOI{ zs%K!~M)_{Gb*)>)}5rPT-RI-FP6rHH!&YgxIiSe0oE z4H~z00_bh>%=S;^HApQAg)SKfdGS#$w6GkziZ7*o@z2QdQyEAqGH5iw!D;!%BXz z?PNQj-Zn|gn4E&gPqqkuONcM)yWL5oO_)}sK-A`JkPXtZfd;fN9?^OIP4P7LwjYj= zZVvff!z({YdI_pm+Ke^$0+#XN@~Fc*=hib-mm|vNM3l*tlS$bP1-4_)kJ%o3CqS!$ zu@3C*a`0TStn9QhXBKn}G|XX>_PVkURLwmI`&rOxDzmf~leaJ=Vep}?Ae}5mRT9i?P|CV*x>n^f0NTEHV|7}1HUcrb z@wOC8>cd1m5PLxk!rtyfNoL~(PXqSgU`M^vpbKlZ5cb@QS+Vga7k7!1zeUo5o2lEv zsH{w|%%*yy-^ync0QFGDSI_ZN|q6*`l3F z|C%Xfn;8gpMiyWwaT)XhpD5J*k0NO3<8wc^b~c)wZ&fipmg08X#0Sz^Y)?Vq)P6Rd zM1B>a>Ho~kMJ^X} z_}4j6h8#W4(22tRO42KM@edVHx2kMc-rUjdZ~3@4EFx`c)m zkCDyi=u9}iGC{?e0=fj68QdygI^*^8XogRio^-^Ubrb#-v+$~EpZ)lw<|hctQ#;WZ zxWVIQgQu-eBoqiifjY4_bgGO!GF{LT2D6I6;`$StipYN6gRap2Y17p2=>(jNZzKIv z=dN1c!hC>Lw7t2_3vH7q-+FewJ}BD^)1g$}9xDG=C|ijMe0x7soD94XjXz?YdslR}~hs`X*EK-dh01FuS%q zmz+pT?hpP+Uz?~ry`;R}OML?CdRkF3$VY;fcPK8~M~eOFH~fG!pVeGkE+!BzS8+VF zl@aH~_%*wFppe+d6zsTJyW3%22VkUbO5v|xfS+U(JDXq%Hz~6@IA|wOQXt>hZ-Jfu z0t2jiP--q{_qgRn)7jj6-vf17U8IvhG8A}u^`I1#&6A$Zubb)(q)hA}c7VaxJQ+mC z>(9oSf9U8L)T&~8M+8~ymNJ%z`tiCHU|9w zXJiy8D&2$eErseiPdpD0uYZH_hnfNM$0i-=KK-d2vqYzI-`~xSGQNXRd2&AwoU>t7 zFNHlVnd7O22t}h=fZrcC;I*S{Vbf!X}gL3WtRgaQ$W-aYA%;`G>0r^L80mU-st~u9Xzk90| zGi~n-oZT2VAz@=VkHMU_ur-FgX&Fyuc=``gywdCTxg3BpnpOt5U|vHz4tbHI1aK}7 zXKt*%mmZJ+jBBCaN1NS;9IA`d#T~x--}lO>FFR|xoIw-Q zXBk;7!k3JEmx)eq8outtib$~iL6h@9u=H~zl(M$y1h=c){OOXzu$lwmsa4n|d!@wy z^m=OYN29!z^ZczDpkjq#V`1j*(&c8mw@ipXB_F-HAL^xj8Hv)_`aHB>j==BVlj(v^ z_6PM=YoED(Vna_CuS?c@|~yYC@&J=o+K^bu_+{?y6NU>u=~9hza#n8_f@ zhHq(%LpNPe@f5Jr$jeX1A;xz98paFVDB42-XID_cu&pEU5ZZq=4G>%YeygQqaEb#H zopptHp~nwi_h}5!K>bo~t&CvN`mxdd?OAL)378EV)$9um?q_iGLT(MI$B4uWWY>2x zsA)gl|^%Qf2EraFDXn+QM|g1FJ1< zU#UMAKM5>K$xT9i?qUm&`Rr0t^O|zX8Ew2?q~`;n{m>u5HG9B$*7P>|PT{h(%b_?u zE;beZb%W!Kq-r~$&QF0OgOw0z09rFw-XVibeMnKM9NwJ_tjPfqsS#UVNN&1|$yM@} zn+#tC@w}MRrz&)M|4OORZ`59rd~QAL_kl;oE&fik=_;q*=W&6X%VpB5&FUp9KDaN3X+!G_udB5X5HFOMij^0cEeo*XFV=iKzi~t< zuM+Rqav8^Rj=lGhv*c++{v4EV`NcwlB#05V zx&-*Ld)|`gS}NgGz)xDQOiMB-^zz?p7JRFGp+$2Na(r!1o%JQWWyx12cv|iSerkV% z&wh!l=f&}>vWDR`v3`((S6IC>QCqL{GM!kilw&?+Mb=6VZR(40Y~yS|i30-Hc8`?u&f>Rj9r%;9^9=nX<=ZGcFFsxSg#6v5@@LbB!Ju}X3x-}Sb5$NRoZKu47a zQiDsogOB}Aem^s3w{I>{_NpHn7^OSmZ;2QLTY2vPl?P&n&Ym$V#UiviiHAqt9S7Z+ z!5AK-=t%0SWuI{vpl7qk96exzg(~#SS?#~i;7^BO)~*YAl54k{`{E!PT9u|3wcjUG z%oo$E9DrxjZFKmz^mq(ZusE-5>m>J?VwL<%((>(wl8)5G)iTkFl!;DLPGnOEhjspl#85f}*Uh1yOQ%J-f} zv`^Z$m6KD?<@CGT<*w<>l`E@#3LxLwma8_n*05IcCh;ddJRDbnT%HsLnkFc7FL|qv zhbn}#s-{>rd->;9Y=xJ_xc=y|V zc@BtY5^>B``W+V?^d^_%r-Zp4-YOpAmQKNzP=Nk6oD>Ra{Ck7y#^L70S$b+y)8)ou zp6c_M@&@$T8ZWk3r?oex^$9dUg(eV)%|jZJHAHZ;2H8ThQ_$HDSg zZuuu^(@D6K?R?w5Wy}r8ucbYyar3E#9q)Oij4j4$@LA{SafQZQl%8SEu%IC_bjnry*l79zGAf1V2*NX-W~#Vq+i%y zwTv=pEdKHT>4+ulIo3O{HFS z_bk3}Fi2Y{oiDKAtPfL~p74npJH+_g3{jdQ=A*Y;3_0X#*PTlFnPuIq6G_JKD;MXi z1NX3e%f+aMm1}+DL3X^v*#@@$mIJv*QfDxns*GslLnT&YIIM1#-tX6@kM zkJ9BnAslr%HJHXe=g#5r8a$Pv|6YyEnK!x(W|?o^AiJ-f(SCf6Y3z;7EyABJwqlb> z-BzZno!!Jn(?rxxqq66=Dm(BornKxDZVyfB%bDbBTA$eS#}0CBKL(ou%X53)*=3)i z{23Rv(jDf7R`1pxlLnQU+&WA7#H+bHYg=H3XurROW09BA1jD;M*W&A=a^tjS!F3yb zk!Dd$C}dsv(7J@*oP(OnjThr@MIjalp7c9Oa2z%2S5!o);yCfx8USu=7PX?Bf5xrm z_SJf)Z)F*#f@##NH$+f(`YRb8aWuXDO@^E60`hr#>{K|O8!)%~qlRH(#&Zgv%xQ@o zm1@6s1{p%Wky~ziy>BwUe^d6LOH)0sUf?x@cvYEv;kGfgCxu$c{Q?LUaeJ%pi_*qC zo%u&bOR)D&_xbS36>^=o9KSZqrFkA%Nzd2X!^|)D=(ILgu8C0>ocJE^<^}>f?cdC{ zdG@btrB>fP-YDo>RCxGzXhScAP32A zd|!Jp2N88Bn$TM+FfZTfc!2T9Y*{q7n1GMHc*lNZ2R9G6AFpe`Dp~86rKutz;XAW= zYsHo`kn+6xtkm`B?5kTl)&>({H#|OSi#PCe{TksEWS%mM#ZU2>ELdu30cR^$>}sG` z=f>Fhw_inS`}qXuAJ%z+Hrcvb!TroDRg&rzo^I*D2-wf}CR8x0G`=M#lRLTFoSZ&w zf6aY1e$Kugh%tK>H*K#e*Xo0PyE0u|U)0v$9rZ57Y*TS10H*<3PI{Sr@e+WT)h5wg zoA;Qi_S37*JG%x{#4QQCNBB?=Ymj~&4sW~@IR0?HJ{|jW|%dYPB=S&~i{;tAn0bc7v539n_H)-L7^9;TSZPL z234$mt5nJRz)5uz4s8DV(_;KJNg^>%wRpWRnanX=>g~^ZO-2gxG(%hd9t|fO)IYO; zf0y}}coQINmv*RnU4?&zNVN+H`xP@q4+QFZ+_}ysn%28sPCx zUmu?iyISClKH#eKO~)v&lXs;5`5b>2?ob(_lyb!HFw+38%yQbjH3#J>%<7QTR9)@f z@7kW<3o3+`!zDrpAq{y)h^tuXG2Sq0FY!$}#aaTdGQT~*e|+w!qV!N~W)z6WzyZVi zwsF5CMmcm;bla9PPEL>Kwea!ZUf<%*YL7l2uPP@v=yV$Mmz#e2Dj(N15N?8D$LGi_ z)8z64?PZdN^+vGynXUg_re37yyglxuukq-2;gc8HOgb?m9oB!!IHu3N@Tam&TyF-F zEcuD4vdN>B;!)|oPG&MaU6isGLgJ^VwdRYjs3`-4qqqEN%Cy9QigsbqQP!ILQNo?@i-$9DJ~JFl|=(GXEg` zCM`e5jg2CuUdzd|W_8g#3aVfb4A!&zu<+oT#s8e#!L(2c1R+Mcw|^a

;sCT#nnx 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%0D0H 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�{jtXd+BV}I~7*$S?VVY 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

^?T33n8s9neD8<)QYbM zW9{r9`=s4jtULH>T8V}Wv;VtugEeX4c^rS(weuDG=C#{MgRT{cOMqXmDMv407UEJaP0eB!twD0%6M!QprhpmhuixVcO zRZ6$|>B)zY77C|9Sq35*`)Bn4kW6me0jN>(i$4TVYc#$TYBhj~0=vm98ebF;7v4mG zh|K-j%hvIcvNx*BzqzReg{4}eSM<(X7M|1Msd|;Sh%hVmlw7S^9L;)}E*QMjo(vbh zNF$byc~|+V_cAkSdf6W0fPa$Ox%CGj^c5=QiuQcBXGbzC|7oC^_1kX2a(5L?X z8CuMX{kObn3r#M?%Q8A?gQK}O>}p5Rd&Zfq=c7i{NLmMaHN4LZ#*L4xI^># z*op>&YwFM#M32|K7d;8vo2oS#DGcKtoZ~!Ut=YV1t()y9YVnBo^^OAYk%|U5jK;p05Ay zv?Xk|q-xY-LFBx~Hfs9VU=KPqdcB0UXVW<7jl0%#+SqS-+r_@WPSM|3zUSIpzO3(k(oB0v1N+CX@8G)9la!{7}fi0)1 zWb-Lgh21s|ya4L7@O~|&huZBeoYfmJBFs?DlEQp&HO$)B&=9PmRU&K-NqC)|Hyg;< zeVxu`mY5XN!M?vD}fmxe0Qbecs!IK9R-w)lAWmXHfb7fN1&oL06P>?OwhXvU&v}SX5Q!}qw)vvc+IkNAfA3?(s z5eJD5RZ#cAh^Uwd5WYqZ5RxY$V?l?EOdW6UL8z~i4|P&fn^Hj?ev^%RLh^a9llzrx zDL#|ts@3jInU9YX|J$D6Xr|{~nmE{0%;?dS}0PP#T z=S<|!Pcj0%=W@4MQ+a_2`|+po%#t({6O+QzMiX<~Nx3PwARX53-u9tJ1$DdC`h2Tv zO#)j(fB!JLnHgjbwnv?G=7}3c+DewhVpaGLE4`cU6AT|m z0m{~1yEmAGKrI`^4H0v!b-r1SIBwH9U)(6y?q+hO1wAZmflQn3-Ou}$KV2+M=9%q? zi!)I@4c+~DB+0%S-t9{54N44w<(37aU`Ma1y?JNTp9V?={VxOGZ1m&Xrra#Gnd)_Z zSw+gK&QOi*25P5KJE>FP%E)7={-CguK-o61TMWPaE4{FMKtAU(ViLo!9G<*lBM`l6#j? zrk;VT$Q62kb1X%z9kc&pgc~U%yC0(z0yfTV6dN2UJC$qgaS;E;Pw~~N0Vt|f#4?M4 z2;#@Tc2fqer`t5SscxmqfwV*~>K2hA=n@K4w2jq|_)JZnFXu*8Q2o@ax0=yAe{T1A zyq`h&np`ejw7XlH=`Rn5Q7ty(u3=8Qovu?V&U|kPKb!gQcqW1BKNvg{w1YJaH(AVYg#Lya=pyx z?@CVf<=yIJ3AL2Plg(9|_XwR|Ty|as!iCV8t4Gyw<@yY>#-2YNC^{{bo#G?{$yDFq zW@#PaWBlQkvr9r;?Z0SteG&1X2Q)!pYqv&uwi(v+IuI^$v^CYE-xFNZrCKN|+NWVK zU+PQco8+{aIlB4zB=V>F|NqA8;V9*RdSUSzwTQo$BIntdIen+R{$1})Ew1E8^WD9| z7R#SE%OptHq-~R2bDd%`c~;Y#I<7hi=EaMXfOr3pZ&J2_Y#S z?yae;1+`Qx-H|5x(_SNlaI7jZsY;Q8D=-oPv*p2x! zR~+WAV$Je>` z+yoNU0b>Ip(G4Bm7F9E3$)0ojm)Ek85Pu%+U~soMFs0s)quHVNm~f>_b-k*j)$*ae z5QI``3qJq;6J-`Y)~ZG+a+seR5CK#mYFt@596%)q=a8_x z5ZC=aa_3`rS|ec-E+t_OL=avM3FSR?zC0>TRrL0EBx=*MufLvtRx7#AO*fF|1kq=s z-WOlPpmsx?Gr2~v;M1pSlXqjY{&^5|TOW&G?&tz2J^@7GomtL$zxUmgHYX(0t!?6B zOWI9v^|0BT|1IkH(mL<1Hqneu)l=_W9TST@yPw%twWs01(AvV40Z&e65i*mjWLLJf zmEmpA`){s%*Cf9OD!$3{e?z3vb;!7iy{$u#_)kS}Bm+iR?pQkAxUrRF)J@pWu-dyG z>fLpG=iBu{AdHZlNi`QsT4eUcbM9}Vp*)y}3@F!YtiVyxMwat4R{&h|;L#};KdO4^ zuCYB?!SIrK(Vf+Ih-;ZjyBioJ?`z>ViPzUeC(Mzm&EFC)HJ=-P`aSno z+U>ji*Q`q*ceg|V967wER4T2ki5D;BLaLJIHaUBq>YaP`xmm9do*>BRZ##~3e!rna zCF~@8TD#1)v81f7(NCcq=8$nIrYImSx!N33jVtr+bj^4!xabuVh(WW4wVTs?KA_5*wy#mYb}(`rkM;4&hGXeD%tZvj zrViceLL)nk6x>RB6vC&1!{*y-9Xq7Gmzi&s{9RO)+tCGgG?MwKjFJHHhkuLbjYd7( zKC#IN0qq9;I+y1k!KWe8Dm!^#X0!1uwt$^C`n|6UlZ!1#1dzMh`_O&vsX@wo<#HMt zzKC3stK80ZJL2^_m&&CTeo96SEqolU!r!@NCC2(PuXg#=FIWG8XpLK`45b*JS=R$T z-w!p6i2;&vz;c^M{VhA+rg_a9oqJ`}_)i$r52)U0^<8iy2Q(t?h#ekxjtR0^>2HWL zkw?$zJT*A2u^ps3A4;PoO80+NJ2DgS!Hsk}{wSwbGy1sOf`HEUYIr7srZF5$8m=kDSNG?3YwezxLl$QHC*9Ww99 z>z67OA%MYYIXuH&HR8yiB)rNBtcIs;&!r5%Z(lE-zCrxW8DrbkLsB{_d8J40Ki+z3 z8dYC!hP?D24+#4@20wBQcvkggTRgj&BV@4Q1M8Qh9MH>^y~0w>$|kp{El;iNuOGoW znRxCTAu6PorXu%@Jd(gKI|U?Lf^c{JjZgY5yg~#*qj#@0GqX2rz-l&bfi5lcDKNtn zh1R?Jh6)P0`)$+x#(>ul=R=^itP1Vis8SUB%kiX_$)oG@t&4QDblQMX#tPI;1b8){ zR|bpneZ21vxyKxUmIdG9zk9-NXpZ>r=&N#*0#gc!HBHpDTB_Sxqg54mzBzhroglbkEK!soej;f67uK+tSVJ>BYfjM(g{%UN zBtObVX>On0y(spC5-i9xFyB(?FPPWC$lTJ=sFr>5FQm4)ENZX1GN3<@ZpBSzyFqK) zXPskqcJ0>$dSU)Ofy6P6Q&1{r|ldy71>7ui$mEb}uc*Y<9Em^+Q_J zyk=K-T&GnzCz;tD29k$WGd+xE+w(8goP!>0Txd00j|(zfejr!vIS0aeH>tG*-^B`w zU!aZbh;pKTgSBbIRbV~E;$!%#wyX_Kf>?HaBSGmommJTzZJzy{R;n5A_?~%{i?Q-e z4!oAGiWuffJFs3Z#?YX(;O%*?erv%XZg6!f-P@KYYRC1cA4Qwibg)cWTNx46^mWfx z?*7gpbbErZQmTJR4R`H~mi?SEymOxSx$p7WlD0kNq=Dfm+4sIn%>$e>g!GhLvCNgr zENGK|`pwjvw8!+(ADz8Ab#8avb^H!k>Z0-TaJKXR zdD+fUp4Wuway6fUo4+k|8$c4uG;gidXDNa)EcZ9dRcqQ|-gHmychFy-y~wP|F${i> zBlvBDqux)7G$0cybNq{BM!l*yyMhWmR3ZG*lvrLTTIJB;p-q$-3GfWyZqIySr>uI) zoKH$i<_hefy$4Me@%+DWl1t{(ZClc6gqWNN`!ol|9|pmgL2)4t)Iqg*lgH>qogP;B zdR(7Z94x_jH;7GlTwBNAlGm!N=FIJSJ_&f|TCs&Cyg3v`v#U0>l#ZC6^-|5@CE-5! z+!=lE>Lo*;XxkhWl;s6yIzcYN^=AP2`1=9=0|u2+0k@ey9|gcb=PTWGJ>wdW@!5)5 zDU2@N&xZ6TOGODE2R}U_FE&Aq^L6JWELCS;v`RUoo`|Zxv+m%L{VrB`GN7HuDaGcd zuURotdc@sdfm*Ddo~=^+ar-*-i0|v&^wRs~?sD9fiVbEpeZUQd?(0Q^7MJ9i6nK6& zITRmbLHw&2N&l;|$)W=ACD^Idr9io-+#q`h{>IoWBXws*X7Mrsxdn1Sq*C%4_b`Eb zs`e^0ftA~r)VczKI3k`gLv)J&HPtJ?lZX~`y1Cpn+0^pPn(o)^U({jko*K{Eku$PGuCU9b09G&h&Dq=fD=le#|*69mOyp^!f!;<~rj!p9D!TfxB zy59)279c&_ycl zmS5WK%>ETGM2!(EaE4 zoc=8ih_1IP$~9kIn-7h9_94gV^d#IoI~X719iU>fo&EKrZRc0wlEKow=EVYug=kt2 zT*67NMS+=N2tyYRi3rdlL@=visZxv`7 zwgWv2$_MHOl9C$aT}# zGj>{~I!yV0Vv0f+2&V5lid4-s!EO2hS810Wqz;KX=TH8u@D}t&Gnta*(%q=#cK3c; z*mCtp^#BIA1*A+L3`HKzuPS0P%z1gg4UqZ}*9sWy-34J84Y4OOw4kB2Bsq0ofccp0 z5s%guscCN)qE?6oNHqC{|2LxAu#?fmI`n<1G5{uG%STh-yddOO=l>>5HpZ{}_V+nc z0MIw93vHNHV@?8dhf~#;N}t9}e(_pI;&w-UxWmMre)N_6rH#U}?}pJGv>y>tKo7PbHaC#*``X*G2#!0%O)yGMuVy}+8;t7BbrbB7Q&mal%3oF z>umuy5N2DYyhaqH`)(>6utK>&3fm*^I(F%5_hShf-&tN=6v=}6Xa@_ylERk&m1)l zwZT<83Px^zkwp4fA2kby*Eq}u7*2BcA&?2(=xoNR{6R4a+j+OwH`mRZo6qh0Pq*Z@ z9~tB&403;4x9F!IQ%}s{()o#R%~j!EjA1Uz95UPef$Q0=*${e^m2}#&YaU3uzE!L4 zihztntuhSLaiehy+3{vKnad;ugseo?U>s&Ql8=FKh}qE?C>m-lA|w^EPJ8-9hH81WB_SjFFl%Dvsw}HG?)$JIdixpvQMI!Hg~* z_IIGVvw6(k`i&JhYOZX>h3>~gB~m4qk7p%ZYU#r4cg0G*VEAG%<=c$0+!`{doIk3RAVrA50>}zm_^StR4F6<=-^-HHrqe(l;HIX5eUb(q$A~ zfeDEJx(gC=U2VJHdya{DT&g(x-2a~Y1(o@tol^6#Wb-Y)gO!@0<`uw zQhH}4h{q92y%KKVyT6f=T3}dXFP`U$;isAwcBt{B?Ld6gJG?BF`LQvo;MSw^s*P{d z?Qx9n*!K@IMDMG6h!gL$B74iR-wi*xGpR&`C*)`iHvIwrwGlj?b>KEHzx$#5nH1=*t|0`an}wpqb0#B}77*P=GJWIS%Z+Qf%>*Dz zeqr}aGWAO(U+dTA!m_m9iI)$IcyrDwo#+GORFv{_1+Jefy*->lugm#(;d&o$sMF+e z!FJ-ne8YN{SIzPty4i*)Gc?%7+vss=_PYBmKLkT#Cx4I)abKny%uTEW+kB^Ai>r-f zzEcop&G#j<)+M64?yL400-n$eW|AsOHMo$OITrD1Ijz`hew+EPa_VI- zY$;nXT1Ljn{rh>e{Ybm(hY#RF*s&{!9p-H?5HkT%gbuHBT0aiH!5Gq+GJK97EwJ|> zs}uEp)Io+w`LYo(v&exAz}J5oh^o+-yEQN)sL(jxnjKe~Q}2J$i}_)iZ6rxMgVf9t zvjQJ~^f;mVe3uP69I$ml69HI5YJQ!jYcakaLa)(z9bo%z_680%h4yAP7&7(t^|^zG zOsew(^EC(G(dO^4sBh;MHe6u(8;+8GxA4tIwNkxu=y-^~YQGd?1l14PuERfY(|!%i z^vYLNwqhoYFh1?Sk#vK@)m8cI_fF=t{UzXMyZph(9dlg4l>SmWyjfhr|CLJpfp;o> z!trK*Z`>mY*QUDe)#nbasytdwYL}iYpn>sy#y<<}kUHDhqWH?w;jh zZJUDwKw5$ImZ`Po?dX<0#+oAtz`$ZmjVrz8wbvk(_sFC&5HnQ$ zlOsC-$(fkpjFBVe>K9s_)8z#Vgwm=i6Q;A{DwLgf_gji>WH=n(ss4sTZU9 zd-b?Xk4C=@Bako1;E~l9zGq8+3`71E$m`f%HJK`Zv8tc@ErUJCUVlV(SrCW_jQn18 z{^TNM2run6?S2e{l2f=HG3)e4MUJtgUF5jMBgR8r!O>4bH*O;a9R`e89g1TlSnk~c%vIx!l(XysNTo?P7CL==)w;87 zh*7Akv`FTlm4&(S^kO*?XiYo_BqjLb;%DS+ZFa*Hn~JN2f2FkwVg_r$3O?{Xjxg9R z4A?^*yd$5Cw60_*D~nW@kWQOK{H-;w(4UhHA>@$AKaGrYCDrgIue}RX-1yT{SbOpVWvXx~EZ&h5eP#3uuOVNhgWV-yT_mV!&TDwGGF^} z$fR6d?+vDUSw5un9R!)j1xP^B4hp3d$$<|Uy`ygRC66zp=adQdTOLu$jC)F1x)afN z$eOITyMB!pkd)h9aB^~ia7*8BEDC>~E9>eTJuXMWItCd*&X|*t`adaEF&EtI5 zJT-wFd~o2V_pMkxHTSR#fGYf!gqn(k3o|VdBN*P*PKWTh_my+-p>uffJbrIucie?g znZwb@>aEkE73c_gVL9ksjh`KLeRHjhYMAD>b0R-uBOA5%T9+*zRJ^sx*S4c_Frnpi z{&p%@xu`;wEoqg$LwrpK&Okg(Gj-t`2aDB43NYsED!ZV$ zgKi;qr*$Iz(qkTGH*94JLC&-CVF(%X`q(L^FQOF+iEZ0_9_Q=89>}Lh+E2rc2;JTx zc$Lxj%{k68I&Hx8@mBssWxbufifx^o*5-EruOh(yh|?gC^iN2(Z0%MVbXXV^Cv%!N z<-?u4et>o~#v6r{&Zutg@ z(eemHHtku(>*Zi{<4G{1?#rFo5sVNo`*`C4ODMM`xUL}&{#T;-e7}mwd+)yX03C_mKeN>#isMEWu zSyC_4{I*Ori?i4+_cu^1@}8F>2Ui(4Rm7MTz`-Hz|Lzz#A}&t=5Gj2Jqix~D*HQmL za-ZX6R*v3RSc&X*oH&%BE=JL+)YPqQL2svS=a=-P*c!p*{&lO%*#ciw3yUnAchh^0 zcT~FHFC1I>Q~@G#ic+~dd^egoM-ei1>l@HAgxu*7=(gVC@qFD6CK5*1kj+U*gX*Vy zOS8|g2}+Ph8(z!g+HAxs=<4k!R#veWa(BEKUwOghYE`@+ndPFrE7yV_=Ov>rsM>?> z4h1o3Ud_sC*+|ddn`+9V&Ey0Z^m}^dwpB`jpyuH8wd zHY@Ab5ZyWBzR>zJmY<=+jGvxP{Bgz%zOG<$efX$6CJ^7<7)<{+q-|yN&mnsH?xuws zz)5s|Z%J3jBMw;}&_nHU8`K(FL%Hqy(*-8)ZWo$D`vp@G$-Mw|?QKzQWcjBzBKBt; zAF;9S#iE{2KUco0@a$psNw zgZc{+nASH99~fbd++Pe(O0FCYab-=}_diuYJZ8gxCflxE5G4TH-5Ol{e*NeS%{LPO zvj9m?-=twB&fm-c8WML4+-e1owIBVv1ndUp%M=?jf?kPs-VArS0GC3CASaU zK(f!8A6G15Gnw#4cClt|Ye!bz_)xm1`u#iTfWNl$jVo53uiY!f)4xt_m+30x0^lom3;_7H4WS2 zQ`pTfqqMe@RPY5(me&_(Bp@m3cZKc>3(7gLqW965d`g2e?cj&w5bcr=ta=ua>?lm6 zRSIZ$X?F%LUvKbh8*K(rxQc#@=yK#LUgxJtc9mozG|bOjagZxt+jTN_0K(9W9s_#$ zi0<(T4zLP2uM2}6o-zpi`G=pTG=BLn3mDre6t-I&mqT;OeWUAO z>r7ec3|_7`n0h?%Mc_tOwKqbGhXRL$ zr6aS6SnNzH+j2CxFIO(_8`W$28WMpHk0A8=)jgD;!mJc@lGHC4@S|nN=0pZ8^aHx| z*E(C+-(zmpSyyGV#}EI_GE3j-7uL8oY+-tvor=}V8!oJpHV9I8ofu8lCFRBV{Uj4_ zVkVKP;Tf@B4pKXO3d@_mDGtVR7(YC~CjNEAdge03##LyzP+P+PnrUc9By$eU-7i!h zwkl#Wgb!KE0sC=iLMd^3n0Ii12kmLM`2c>EC`_5k3WuCvx49>Hw&ZvPy z>GR62y-aZzf23viWb;zK9fgILM1!{Kxt+A@vv#S1`jpU3(JRcB4_l-feA19tP%g*A zc!rnrv(yGy3ClM4qagi!=is!#vc=cfOOLq4&d~D^-%9e{v})gA{dEt6$Zt!#Q>W>) zTGeH0e`~Mp!)znmd&rd&#)Ty}n`YvM?o4w4VCQ!clbs`>#_Drmf z$;~kY&5iTSb`2QEIcjp6O>?6PDo-;qJXPOh9`gsI2fDeCEY< z1bdu*UY0u@$h4WJ(=1P;IqirM*{hsic|c0%b`#=ua)#Fu}K?ecWG5kk24ofYklbI;B@}ce;JUH3wKx+ zsU8E!_^mHOs^4&rc^`MYgZbNc(JMvayU7h6zEu97$nn`{(;g4SSY~2EnH+YRbX9&S zr?#(LDqg;&xTEXgh^{|hzfh`Q^D)+J&9d6?J#cQu>viFmCD`sqbJEz%kIwVNE{@}( zBA*c94an}g&Bv2Ft|O@>S{6{qGP~cq_v`Q%t34kqr;EhUa|I~D=EF+r(id?ZRbTTu1+dM>aob)(?b}49ifbSR~JVz+pUt>kuu8w zS@QW#Ry0WetI6uXhz#(^eOz|{;J(Cm95Obx4z$7gQPMG= z)^k#d(_GFd4=JJpP997^&B{cSMLwkILXZmq>DawqvMQl>yU7orR-seD@$@YSIbg1(=lZr$QQs#%5L8e5nSEIV1_85(8N$AYmo822aF0m0Z&HO5N#Qq<$-nHFW_6z&| zEJ@bus0filLWodQ7A+MO-dvyl9_IPKpXb@O=k0Y~b7rcJV;p1b`)}v5Go(F$#$%(7 z#au*Qp7*3K92Qac^Q*48q3)ytp?H#^k2y;q&1zK7-nI7i5uyz@$T^p;DDLxs;Z$~R zf`*x)Ph5K}ag-II36m_XeZsyd#3~1kPcsynJO>d6I7t6ndtBgD&A2dC!lkOIch}ByHQhfHth&D5 zW?GNm{hhbk^O@fkMSa`NcWS0n)oU@Gv7^c;yX0MNBy}bSE2L$ zA4xmIV$a8&FV|=d6g=^PPq`Q>PIgi@Q-L5z(1INi-2oln{<%A>NeJ@&LL2{vT2h;D zVWI9Cm|C>$#T?ikK0k`NjErJ6NK16vnXnJhF^`b6oj9D=Gk(KCli}|(!w%^okXr#V z9?&wWVDot_!=CVciS_M`wxn zmHCN<#OTy>KVE2DrN8s9hP<?BeC$k}r&e0}o(N1k!o zZCldZL$8*rT$~ZIinrmgdyB*4Gdt`T)Z^bASdbMYa8fzU^rKy6tuWJ>5KJY&)fFa{ zV;v+q+FS+(%81wvbb#!}y5PTBE@yEB2x_6!egw}%Qm;Xb^Divnmizhs488@?6IjF zF1j%6qyM;-Ef%yhJ`#~Def%amz0h^v8Rr7$?r>;8PKb(H^Uv*dyBRPK$fj9-Tu++A zR!6A)`mU)NWn0~NaM(4#LSSj)!@GF9?r}%^wNrE;S6pGXR+}n2?cMhKtIcGGtSnYe zbr2CD4t!kbEz!_+v?BxHTS-wt*Jr+1N|n1G&)OWHc2^nRW^s0+1Eyn=?xdp$U#Jym z7<<$SVc7{y>R48n{?Pi)s|qwoE_K}ePMzIxQUDJoo(A`ZC~2w2W~w}^c-F5#3I*&g zpqZRjKgTtF$@_WDMeU#`U%w$tllRenPi%@M=F~G6kEBGT>L?3-^l!M9Z$s0c)oMY- zm|%-FH9LG@{uuc=EOwjMX~)`~t5w;@nM`k*yQh^!@Elkucd#@0=T)DL=D)7w`l7Yx z_SJ7c^u=48dI673GbmPV+_yzWC!t$5AiHPw>@Ugs%evV9$oyN;XGlxUUmq$4%vsc{ zCnChj?hbwVN>;P8fE0(8F3SNvYn&yzG6L-Fb)+^aTU(!+7%Vr*9V%%`185ZmjpP0s zl6~7XLyIS=7n<9KKC{`Ki{KC$54u{3Yb#$~KGlS0arCRwhLb3I*0_TlcBS0=xUUR~ z76dDKYs|%c$<49jDFL=7G9j3c+PSc(v&%bVTbSLBuNB1b(z|(Tw~isc{JOg4$_f_J z8!XqC7sjx=^dqCAj%o+Kp-snk(t1lpBDr*q?1St;enWeNP%ksyT1(4WM0z%eI^u!@ zjcB$&T-S@IfLp=#h?=#zwrHbiFV<>>BVjAOg~hdMf3UhI0ot5(@@3hJ>uB6IZ?;l9 z4ME~TbZ(xMM($e+j=e?MxI;d5Hj?)1+IX}7>k7?n>T}Om>&H#NSv=mUtbIol-*fVW z&=MA1S1m*ZV&627v%NSU6;Q9SJO$EEbe1B1wXJ_czrC(hgOP*7sL%}WHtqQ>v+tAc z>iIFFmPIaPh*rWB>)wg}7Wip+o*eU}l$3f@CLVX20@IG(ZKI9^^>5N&=f64#9Eq`w zs|_KjrG_OI`>bKC0PX$pvpPul9pj{FJuNuHi+HQwXS*z|%}V}>y6q_sc%Av6ore6Y zeFnS%1=aC9r5MuE?-!J8RPWRQz>6wQu)TSumnV5uQ_J13H=S7kibL(^6tX+rJrCk+ zcN|raKb3e*dleKtw%FEqx+Le*%7)Sjs!Qk)ARfP;FxG+S%|jR4(5+UeeYo;N*Nj2_ z7h4g`SyEm}c(HT0D3*h4)T`^|=$E}DWi8GX+xJ`Uio9l!+@fVMdokesC-2@8D|;f| zV1dz$Im-;GXQox;dg=hw1qnud$`68XL_6)-06Yogh$Wmj-M7c&E{8Q=0co4EhqQ{i zo;9AH_4g$XBcr%TPnxOvOs;VoDc^h*cy)Br=PUc|nawMMTGfi~{3Ka+C*P$e@Lm>D z+}U2eew{F#nJKyMwT3-fwwOYSzeCp|C<=4YKB$nhTa?J|>Czq7^&HLY6>;H$sU&Nk zA-7;zi8ptlTJUB@{dmphQ2=yu8Dy4e2cdgehim1-z=XNeau2Lui}MH0dRP5?5vaaC#%-lK}N=Imgus4eouD|JlJE=Z-UucqPQ9Oc`89{ncb!t+Pmf-sqrELylR^1% zd=tlQo9V22NUZ&Tf58i*%>nmRcs@JTv)n+u?&rM~rgCbnfi`-jMG;BIQ=>H~uN$T4 z#{W^w{$wGDK2ANcqJ6f6ZG9716K|e#+9-9X*!H0p&Tg!gcRG}=x z$O@>NDxNtq3!Ck*b&%EDEdb_d)*P3G>GBF)`@&5})>Um6?^{WuxrVf*@1z6?+tVSm zFtch5quUKn?)Ub_-nDH=z|}JlxS+JSayyf2K=b>3>)?y?!FlhzTP|aEhi&z3!T1&Q zvCY8iu8zYAo0dz|=moOh!?;HwpVOe0%7-ItC0v2FK{igGGDEdxqTf%Tm!kwVbL@-m-cro%+loi zx)CCaUY+TZzR|33(gl7!6ZEPG<$D^mXoOp^{dn~f?6#`B^ILHcj@+$86GPj|-0arp zS!1*IuyCbzA$8q#8fEKyUFhvPd`-|tm-9~h7&x$F_+c*u7C8(LLT2JJz2TcCS zo&h;fU@G<^_gG5tKlD5)9})a4VZ)PLztj27ctayV5uf{e8u(1{e(+GEb37_udorCrJzU%2N_BZ~}2>Y|Y^)=#0Lx1o6gW2DEkGJcN_FpP-3PFVzF_+!zV z*76rgdtT+Xc$$27HL0&|3tlb}4&L^&V@@XLPt`5~^`FI@Z;$t5?el}TxKR(aQxFQp zSNim6_xHoB^oBVJ3*316tzG<%++Cj6Iq3Cnl*_6xr8EYF*){&daR zVLm*8TbF<=sx*>eK9}^Qhew=JDMFMMgqCj6fsiWe#pPvjmIYqVU@)s|&BaO#gd=az ze;>R5l$>t68F@(9ZmjoT-PQ7&e8)*wajD(dUe1D>9VOoLXSU6=@Q2g#4%4*zDNcFnxqgW+%0*I2`jKOVtY6d3)4Xm&#w` zvR*+@_k1cT#dE6SKc|S2+Fz`A%D(7_1GJgk_mgnrD9q8%lZVb$j~?e9`U?=5EpVjz z-8{1!;tgVr4Z4Pxrow2`&GIycR7Y@nl-iwIS!#T;S%~`$dLQ-fLCPj*)Ka%W;c#9p zj!!;6YDuL%?4yRP=dbvxj1&n7N_86D39+-Of1wY;rj*jp^R4QDg7WpAtkUW#1pW_^ zsSWs&wj5M4wNv3bTbsx53Oev&aeBnNP^MtQ5H7T-LS)37D;Arfd=I6$+yRa%cuXNHXOZ;-IF-VvkV$B;|mpgkrbC#3MJQgRfC ztbH-i)o6@`)8Hb2v4MJr?!?K!AU8C&pAZsxbMM{klzFCAlDfBQ!%J7e2I+&HtX9JE z33;}I!ms8pvuT8T(AC0}Ivr2*^?Y4R)f3Y_wDwwZStdlj8 zAO{cnqLU^~9g63m@@zkr?~T_de87+Ojl>;Pw1byCI~>DE4+U$4c;gIVH>5(GO75lp zETZ0XOyt^GET6`=40}1b>3~qclGn#iR2t}*(=VG!d|u8; z{>`s2FB_)V{L!hG@w$)U%ftRO;dccPe*^kK&zD_@FY1Vc?Z*k9&B@I`^{qCCkQ>C@ zQE@6SAT4qedH6j)gHq!I9~^>a5vsZbhO5|tGQIpIq|aN{GDOuSMzR} zeqO9J!4tQb9jF2NKBq>zCL@I2gXW2ed6?Hb#57s>4@ z;?mZO=jCr{LWYjo%5qdmn1V77oLtFD?jlEBJNSC3pZwZ-|NCF&T5(tVsgt5;cyaJ1 z_^YxNdfI&?>#NBBN6M*9S;(yFFI&u5v%)UdYxLc7G391W5(c2qc>=h}d`eV3_0MIq8iED2?&L zt4a28l?gjhf1%ySe_Qm_PwAuDdOaIFfib(Zc9}t~ET=2|!=Xp5Cf4~f9+Je3=<1S= z{v~@}`opSUlTuAQQ7Eg%&TPmY#j3Jv6Wvn_>Gqmq>4=;jCUncD%;+e?TrB~{?omJ|WlCBkyCHe6HZBa`Aom8ZJ@wblG4~V9=oMUH$R`WKo zG%j!dP8e9Dxf9Z}!w$1%GYAeJL+qZ>@66dAi3AcGf(i%K@^w=>IiON+>VOrEj&$ZY z#j27^)V4tZzI(VcBk&zo?8;iNFvE+ThA8+$xLTABV+CUTLPWD}z$|PFX{O)3Z{e7m zDsgc42U^`}v9C#%z6xV4zv{o z#QUpSo?G|6u4hKO)#$i!O!di)>0H{xPu^&|HmK6PKiwT@$W+`;rn-KYx(pIGS2-O$ znP+f@CXK#-?6jW~w|$gL2yQ1iWxR2Q^YSA3-XjoWwls;U$JnLlg88& zUvc`E%OZeCLjE595#GkdR=h9MP;bPC&Udb>i6rz2QxE8YOPbyyE^}Sgo*dILlA!8@ zXPHkO&UgL}?XT>0k3nX5aq_OH-M5$@8VlIIr+DRuU?JR`5i^4-sFUybbDa8c( z<}NC-WqyPF-x`_U&%bHame&SWTA)wtrVcuhQ_Rk)?~w{t|BhuLMIc;ejfWDRA(GWU z_2W4vpR3y^vU+CUhg=zU7$fn!Mca(Cw^oxWU*oy}-%~CJ0MMkmD!9uQn&zhTV418t zZk6Uwo3vjR%NEtNmaABQTe3O4b0(^tguSiH6>qe-{R5nh2ZXV zRk-?CA68DnKURqL#&`^75F@-4susk4TQ_)<)vTZ`F0OKvH&!=jj%fIrj*G34W~8c< zDpAVs20d zz2hAC4{B>XE&&Mi6&=8pM+68gRq4tUgVlZ$jWN~OeXG51d=`ySHD51nt5idD|L?$( zGuBrN#;#~`J8wI*yBiDo3Z2zhj+|_F5O`q6mA}{I# z>EO;%qqM$vIyMbp4Op8BrNY5*lUv$gz^+)E7+rv%f(nR!zNz;q9SN&!AEMCn^^LIL zhp%-5C)!YBF12Dj_KU$TIdxB;l6bz3HtkaqM|Ms$*mIz}r%X240n?p11>aNIEIo#^ zY(Xer6@6*gYK;D`D%90O32In7D zL8RQz4f=Vnedss4pzW)B$Gp1ryEP%$dtNH>VkiOMyMyc5UXE;t<8eD%;P>(RX$@FM z$~Yhi>pJaR?;uPp_OC#aI}l63fQrzODGW|ruVr0+FGn*3w8wA?L9Fh>59-P$@g66i zX2W!ie;gnW`_D__8Qa|!AqM4frNW}4d~)`!T`NWj5F-xBMWi=Or*w4l455=*Am zYp2=-<75_vHE0fzR(e@2aGgFn!P@uvql?ko(kIK4E)z&ISY|q_n<68&55Kq&@`88 zOul5(HL6rR?suhZxkzPO@vmYrXvdI18Uhg&+`+gPujL{UX+sb5u8XozSlrVM9ZH_n z0L~#SD)eu3wr9+qcMZ?`a|3-f@j&I_bTJxH`^CJu1H{@fy;zPWpz#0mF%>ucd&>AG z^Gm92K;)77slr+{8{UU&*T^X~wRO6?%h(_{D~WDOY5Dm1#3rhjo8nJ#pkY9>bhOoU z_zrKaHGcp!?R_@j1hee&n17xYMXKkb;o7B>x!T*rgP)Kag)xiP)M8IWxBm`Hz&RVV zD}yOS78=*ZEcLjr4--l#@MR2QY0Pj)x|M0Z{8t(=om+kU%lMjs0S>>NkCjq(ZEWYy zGmnie{Go>#W<&98w+Ox?k=$2bw)X?VJI(rwGj)` z0^03;I6eksQgNejW#~*dkAYjGX47Qx%>B7xhwrNv4a4HbfDU#(iPC+Dr1@3*TU@|P zvb?{%VbDHKqJ5{$ck%j#t?wotV2CyZJ5G;~pU!j-U_F{UMYV^*ZyA+k!^FQy&&Nun zTI*>hN9t@B&$WV#Ngj7z;o2V(eK-`8DWH1Z8(|rVJBlCN&W5-|q`!{1x={I}ZYp*z zS}wG8Rfay0s^3nZ6SfB@dOhXC#(+sHkitYrcR3G;7i;EDa=3Kp(JcLmVYzxA3ur^F zr|nY3s1_qom5L$^>92UXKaqQBf6priQA1eaX zb25E~^x%z9PfgxhbLx4HxR1Tj!du<7RVW&)@1c9Ew1zMJ5^wZnc8GD4=#n}bof@?t ze#bR$iXp)A6WcrtfY`kydhi7;oLuvDOyPyImu^P^u2NMV_+|azIL$a4OtsJ(oLNHp z;Nxy%0#@<4`A%|kP*FvL?5dNJKwC2&O$u~s3C)tS_=wi4lqL84I&6`NfwyQRb8b}n z61>HF+VSm4@mHmfv2U86_d4fHL*0(t$a;;e!Fsb*phWg4tRmJ`s>wnzEIDYeNSg#2swH6UQEgET-40 zMMgS8KgAf-5Q~Xl&tG$W%^ljUg(U;yk!kLfX;hTmxXP+=u{Mg|nfoW5+FEMvA9ka# zX>-$U^Z3YQunEMW!_9+uvHk@V5>ELcRti_h#GHr+6XA8p|LjQ@7kH8!dRevqi5W(+ z!(sF0ut#`GZx4HgsX)$!+)HYMQga~TK3U77&DuZ3&rMvwxG69CKNS#j?mwpueB*j6 ztM%l-P7!Te@+z5F%6P*i=V`?~ds`NuU_nZJo?7JKg9d(5fPopt!pJ6e_GQg?U#G)u zkjk}ac`8$K<+(nQy;fyFfOs^xUJ(vCrm}##)JDVp+3qIp=#L? z5B9ge*YSEaJgb_Gs(@iYA6!)tp;*h>bqIcIxzkC(>|IV@ac5R)*%g1Vc4o?ptdnj&bwIVs zQuwwhZTTU_1&AW8!*x-BX+$BO-JN9QJa}Pgg*k4J6QokSa!&Sfy*?gz^4LvL{SD5i zfgqe?CA!(u5i9bI%o+;Cc}#Shjx?_i-b_y|&bVVJQ**pUjDakk6J&0(Lyz6>OzRfv zZ$JDS#wQi6I|Bgc-tL_r%~8VBev}{L#TF21^AOZWA8C|McfDtE(MoBzw&VZy80XKY zu}c_xtv6mDuHP#DI3&YxIX>stP-|4z>+rmrou_%kC_0~d0b-xK>7Qm0_RY(1a=%@6 zL}4h6(dicNWC7%tKYvlI;c?m|D>r0z%x^KaqS}f~9pMP-)1=K!?dbOl@CDDw6-60M zAjK-oli@X0nM_uv#}vAJftSkRMG>-*tE_)_jwrH379Z9wZ1|>{th~QY+Oa*JHm9DdIRb;;(#Kb?h01i?95^y|*q;8HLA60MQ~pgaun zf3m#-8HF$&(|#b|qJ=GY(v*wEujGQ+cIsiIp1t>CFzg6| z5RK5z;q zZTK!1W$#{Wg-ENn?kmH37CD`Ch{Z%Jg$vi3kI7L%NDo&!C9jXmwC7Oe^L9e}iqh6e zb)0#E=4l3P426JRT)wZ?n43^D_V}i?JV!$Fx?<3-npZ(JHqFTWJQ`-7l1m~76Rns{xGV+jcNwsr}pbDwJ(WD;TNgxP!FkG9*U zQc@VZ`T}goc!Q-uK8GOkPt)CatSZ%sg9uZQ7i1#IXb@R(2Fb0fY<9BQ{=diNJc;8X z7>I_&zdyCq-Yk|u+4=;(qus4`%%j_WGksGkEglFa0fJ`#roid} zu)Sj+3chEp{m4F&X}2-?u!;QP(U2}kC#%3b#pz-+95+7CJ4MI2EOhMXD;4o)3!2&J zYzNM)VIcYWNOunxt^;V_k3;U&_SP-nuSd#982p6YGyNr{NzHAp$K9H@Mk93ln<=y( z(b=JJIJ(N@T`U`&)P}hvrD&DPmKV7tRJJgMDqYhtZp?@b+A2z3hYlMp;HhhG;&7<~ z<@Q-AwWjY;yuM-cHE_sq`ZSoZ%f+f$IxZLL=~*ghaCm~v9IggN)pA;YHWv{@y#Pa! zAHP5mt1;k0T3|1D?QNFjWKpef?AW;VqpZx9H?*Evtl(Xp|DDrni$Y$U8_F~LaI$_v zh@*}^G~Roa0>&vSP90@*gB=!KfaQ24{>>2Dqk{`f@YGp{=L&%Im-+PA_2s?kKF(r| zVOkHy8}#0$=vt>c-O5tV9U7!`XG3KfmTxVAp#$>fJHrk#K0BY!rZy@K+dEpwoLbsc zgR@w71V&VH!%E4xn_ni=o|@c^I9e^ba9wQmUU376In;`7p&~x|&a&nDSpMbXOtv0$9isfZ4x- zT*Jsj92C!sJ;(s9I)wCH3!vP|e6zx0Sp}6KQte)%+yz?po1!;Ryf0|Gvg0&20e^me zw;z?Q`}jW5eB1bQn@=CQC%5-Ifc1_cIR<=eZoWIk=TyFPr8>kpwed}@UCn|o#;+y#%0Y&P!n(U3wikt z!UIrp->123sz*LM?D19GsU=)cVpBC!2KS9tZkt|1dc!Rg8bdb3>W<1U=C7?lOj^_s z1DqC!rD7=5f$%CZy=KnJZCt$xv!i=~lZmOZF=hp%L{0l}9!@*%;`6XKt8}5}Nmwxc zOS^e8i_EMh7t8h4O6v6(z75F9(083tXZ}j$@@L$dP9WDxC#G{%Pp889>C^#T!{{Y* zzv~X5TV?JZM}(n|sVhaJR3@mM+IV5PE*e}^Oi@pQ7P^^jyuF_?@B`?J@2uUhe!1k6 zM@fBSi+kMO<9fLdW{nvTj7o8-Iye+0c~sli2#@w&$A5#t7J#UoXXC1MmY2-ILg80D zctE=Zi+4HmbK_wd>l^uZ;ksCMZE?%|i8iNIh}47A1eyl}%sL3xlWez_>or07YAwr! zF^8Noxn7R4`{SMlF|3J(V|FE1GYwLnH462iu_q659J)n-p4+`z77Up!R;!E~hWA9r zvx-ru{~ddGLN0BCnVwA)^FVU9&x3$-1(@Sa;74s&6S{dXuVTwlVgnGB;}jS}<@+`0 zT9DFGd52%fG4jfQL8yGXKhTrx z=&>EXx6uVcXpK6!;(n=qKGLeV>B7BxYtID)ywsoaoDjW*+z&ka`o9u8?3~YUNSa%K z7!(#{aN&cPdNv1zO?JE(l}3BDs6WSg;l2M%%u;Z6dFBFKH(KAK!^Mqv*+e@t%x$uO zJdW&oF4Q~p-*IWw$v{Gy*_xK2E72J0R43FPkFZH~5n~Gi`s#1sUt`KJhD%s*Jomv^ zia}lv4X4awa+!PZs=@gn({2=;>a4hX66OB)@+jrh5l_#HqEY9{5ZG3bCdgYL- zM%AiiI6Y825mWY2Z5!Xw5LTU=izx14d+z6MaEz1?2!Bh_IS@&|skFVzwCyozpOD7l zkxtJ`ddl#(Yj)XOACp`aMMf|gisk>GB1G98^L46H)9#cDZ4H1b`U0q=|4B(*vT*DV zFvrfF8q-&?>WNQS-_Z|tw>x8_A(LCk|Hc1aoMH9aFsf~uedcTR{;D3YtXzGVQ+k=` zq~javwu1(>&(}vvXKa1$xb6Tq=vXp#@G)C(-8S;OZu)Q2JeZ`?=X!V@SjQzJ*t#Zg zG+vyS+&h8%#83IsQ&{Eo>-6;blSi%*aLW`R&Gjey(FtJNUB_4*pEio>Xe2LyRKPgP z=4zkcJKH{tJMKFFlJm==_piQ6Pta1wqQFP=RfKed$D6{T5VRdg_hr1z66nqb}h zWdcAfxpWJ&f+}1yhl{7>NTk^*t+oP-QdAztIC=o{z|m&e-@xS>>T|40CUdg8s++y~+Irc3{r0znQ#Z{RTNhS2 z&NErB8Zh{{$q`DfkdxKcVZD$V8~DiRmsqc5{wG1N*{%s;X)%&=67N%u1~Bwe?vukx zyOQHQJ*^K_W^|Yr@2hQ1%>vG*U!l>I0h4EoS)U(Eravnv&CThkl82}^H~}3rq|bX8 zTlKt~$z~Sk{oR~Popj1XL=N z_qg8@#&iNGhkpPsi1Xs5tCTq--RI*ONaco~Hzse2zXEE?Q1$y}UXRa6jb@&Q9e1se zbkirS}pC_sX4wBS2~EZ@%o8lCKEkhZKT3? zt$aL4#-RD9rCdkOd>GxzCs|Mqcu>iSqv9Q!3%N<6F%RIwxiDqs>x4OQtvfasT3J}$JPMfW8DGY zQ>w05qdD4|;5a>reG@mm`u4fi_MK^=Zv*lrwYFPEb}( zSDrP*o-F+)Z{)*sQd$c(&pd0b>!6>pFJ7Q|YP!1rbTVV(EB{Yk57bs!pBAi!(x-~` z^JcySkQ>{`&Y$r#USs8&GwZgv_5pb|Ok;N;NpAhUpVRvM-CPlUe3lz>BWtw50`+AP zLz=Uy`9H-M)C?+M8anOVvswC-)u49iZ~2XVnt~awMV4!C{S-P3_t&zRw9Cc?;&avp z82X6=+`)do)gKfTyRoP6*;SfRpx(5-q>wS9)#!6FJEtKDTl6t$|GP6HID~4GHf=l} z@F1BJSP;vL5;yJl3*iHQ)oh&O7j6AUnhmQK9{Vm-x#8g7I}<7_3VIg0J~?HHh`xMg zUHt1`l6q6wHYYNa8oNHdmG2#!d*mXMNdveZbX#T%Qqk+JRkPy{!`*{Ms ztWvu6RBK%}(v|8?tnMl8wdgvfAd7!E`hjGs5Hpv4YcCL~9>Mv0TWZ9;(%3{ZYDvv{ zK^bAlIbA=>_{6>pMrdYp$|fY86lH)tz-~}1*lpoL_M|u^K`Q#GmAwu2K{2wzig~{J z)-0)?CZ*RU7D(x+@2+@DdaVR~*7vQvXoIN+EP3ylm;*c2D=576Mh57Zf9(k>P{Q$? z(FlPYdMz+113K(?$+gl=G5z5ONTBD!aPt(h{A{>QR}G+8pC_|$SV~1n%ti9`PZ zdRUA7Fo?xrwO1{eWFgmAN+_dH2DB5}cBNZEZ;o6lLS6;zR=eMB1yY-n$-?9dz#!eR zEB~akx?SebAvLI!3S0xUiCYkV^|pf{@xuDc5&VJBa)6W41a-Fp3vD(z-%~I0cFqntZ3O+;OlZTnuT=QIbws0X^{WpH#&`)fKR?o~JP1lAJ&!(V3c%Ryr<5Y%BAZs5SG<|Tb zgJ7vKVVU2?ZqYD7ruToF$g-Ak@zGJczFtf{#di(CvugSqXZzryTB2?##Q(j1T_O9u z$xIDbgW_`m!Pi^RSmiW`_sSgV^a92Rg`RYR(40wz_V@FqG)gr_r!HOU>5yNrP92VY zB~?iJq9kkz2`Hdbw-M;GfL3{|m3kWfk{$dj-j zHEIb}^<0Be_utQIt?7D(zxJosMIY$hA<&xZowJ|7{P8Ei$d_U|)_3a5xn4|V>rCJ4 zaq6!v_h%A`B7w{t&+2v}VN+vWU35Nd-#8U4ss|_kfwV8f@x#WDWn?bPQD%}2TP`rQ zy?)2a4Z7%e{xb<920E)0H&&j^fM0d|OjiygKlHe$BHq-%#K(z5%je427)^po2<`i8 zoq!A~y|G0$9&vmY({V{!ri-W3O%M88y**rXR-tig`OkQ6k?tvSqddGBRO$!fO+k-m z%1WQ~^$H01!CgP>w0U_tuN2rBf9BAuw`VWX{^$vv&+6}O%mZesnN`h4R=cJ75RB1} zv;pm%WLT5!_Tm5WcJ?959k;0Csr?B5n?n7T?Eg;m^-6M#-_^qw^u?{x3Swo*21MFzt~U~5!huGk zG{thY2)%$a1c9E~_KU3e2KB#96JrmcW!t`%@L^OM(xDvkp559fN!{tMUbS1@nU~Z>Sj};cDOuiRBE9Wf za<0`flw3`jQDYCet||tRK%LS+-CxFXQK_=`O*XYz&dBowr|cfI&q1rv)ka?1-`vlQ z-sQJwkt;I-1@Vd8ARe?WdaI>!i+aauPnlPVamp^%Aw_%}+qXrNzW9%Zf^NV*CZt=U z`D=Va+Aa+Jrg&g`-`!^KMAf+EK8fX)>;4wRhR#i##&_W$n(H4Elnkt zVfOns409Y*_Cc`Cja`)|%;m+Aw}>`hm4}c^%MJi~neFsS{YqzrR@2Fi=7?+fUg2#8+CDu^vR^A4J^krp0j2tJ$p4)>8LCBN~neZ`s3AbciJGiP-9^RooAkZ05v8w z6CKi3^oqA*9Xurk-E_V9GV9|>-?#P37SQW_E(kcVpI((U%%TEskh>$r7!kAQic(V! z36ck1*)GZ0%$$e0{i+RPPiD=gCQ1VC*kULWS9U%fRK-koE?e>~y}SJ#v_XGTqi*dk ziFK^c2Z~+70!Yo}u3WkICYRNmn6D6wK$R4#F4vE__;u^R$y-J;Wh=c6dRSpG?6LVx zO998uR$8XWYNc5_j+|T;TH_h3R`Q-DjqPy_a|v;$aJNz=%^bNoexGlVhZr}vBOfMV z4%x|C66(NQNo@>1#D$e6ZH((+n-+i-wP%_QN<$K!@nYAp37GqeK+;+c@^HT5|5Ph< ze|Np#mHv4+s^KZWM^+#Lt{B@1CLE?Dpg6W&IW@NWUp4S(hJHq6^pcljT~oA;B8izHU1YCXpu?$E*lh9v+LOE}~YQ z<*zzS-A7nI-|Q4rS|h9GEEhy>Y4r;Boucp(mi4xtZ-5%$vpqTF`Z?Gk^@7FUZo@$l zd`crEBlP@h>5fJ7Wj|Y9$DN^b(f4t1bF-cEa@u=Uqj5>-oZIgjp@L7MxVDP<)PIn~ zkP$asMDA3XjWQUvm!md8gB^{2AKFb&;I3zYTI%F0twUuZqff?oa_G7H`@HiW9J(LM zRzI0ovRU-kl&lwX%T)ff-;76ba2Q}+w@`fl*v7ptKMn)6#?!g=FGxc4WdLS$I;X96 zYm6V!0I|lEy8w7RRH%0oZ=%Tya@D%)tFVGQ2%gGSHjTx)wUO?S2zI`mhOfu1+wJ46 zC*?IfV&C*PJzC8x0>1gK!QL#DD-c8i5AO_bX9%_M0R7OQ$`$nFoIa@V5G^m}eLbpn zLFJYQN!vb?q6VYK>LVK<%PHOKIUELjt!DEMD0oLmXM97W#r4-b&AaLVG8qOc;4HwK zMwOn`!_YngxS!d5ewTdz#jU`)24U>a^w(<<*0Nj8ZR_v0Ipu@qt=Ur^M>*Li8I}9F zX;9LA&43QpdmrMSG)$)xZ;RJ1MCnGAr*QPTS9cRLCrvZ>b#0OL;En8Wf5%h<73WgD zuQ)&wb+>~?l0&Pr+-yOfq@vT>G-Bcl>bFm?k-=m*IBehMwtA5dX_i%gcMyd~;rWTU zF5g>(r;p*ziWbe3?(iRD8|CvFOI zXj}emnLKy=>GP=DR$;qKm)%xe3qf>n9Pf%A>9pTb^FP5qUb~CU<;Dp2rVK1b^9d&E z0i7YWw)A*J&b7o?#T9ef(XRmLsXlaqF%kHFa)JV1Nze+nxqC?((lEcQuE63#6`sq( zpouBQVcI050&~bCZ)%wZsgmtMXw+?0ne6q>1n9`cZ8{ut76sUV!u!?UIzm)o{Tf)> zVB_{D`gRiT9>f(=pxDc*0M}iC;m%99tsjI&-?PoTkqqdK3);wVtL_tqstOj@qstw z|^9PhP@zv{|jfMfODsqd$MP`2y=cMlqXu@I-WGB$of5+fZjQ| zIsyh-?RQ)v94UY%E%l<$kOtIE!9!Fnb$ao=a<2{M6#*0Y-9BRm?NECiPf&8?PFbY- z`Su>~adxyk-S_{2$sDNr@;vW9KmUiT^IB6C+P3wx5ER9Pf>|*jVn7rD6$y$0BB)@x zsi!}^YOb~S{!U%bnx%v>M(@3~zqw`M(mL4~a+I_hA?LBZQRH#$J`5KlyGa*0N!6C+ z(`;R5?a_le0cCIe*A;QGi1UN0ajuCMdcaL!a-pso(PA-831Cp31|mdU3kQM)=Q3E- zugk&&Db9n-O2;#^3`8=jL>pX(kq+Xy7d7dh2mzN#8kSOF&ty>mmhm_ zO^)>fFw=S#S);(d8h1fLgB$qJT1UBDda$X*loaSn_X~B@l#p9WO#Dv7e0eFbTK8Av zsgav8=oKzrgrD)Fasa~mD(;oA!w&rDQ-a>Pwe>VLQPOf3QXv4O@&d4=>IU@5u0SSK znF_D;HhowXJIV5L%|G%0BNdWd@iGfbxzQ(c37nBaeq?3&x>;ExU2%2r9sT>(pJ?UV zEKkIVye{NtCvsQWrW>%Dl0$*y@@`EpK^DSRoYbb3$>aKbhYnQ!S+4@)3*&IFdF5k1 z-?ZG{CEFV<@zk8PYU6Dahr}1KPxQ~3C%P&qc8UfIc6M5Y^(dnizw~&O{QxvFkx3!u zPtxbI?vZ%<7qiS$tIjA~P2+jfT# z>wRB-3hxmvuITBy3?61LyArcKt0n#msut8uWekzSH z(2+lrGJuvvHbp^xE`J{RBXBP#(I*F4^L&_t$fFGH$S45k#I$XU*r?^1)B{ot{BnKM zt9f6f*lHuIb!8yS>xaN%?{{*Nu?1LQbYSALiYA~Pi*DH+2)oEW^`m_filI{a%b33Y z{$Xb?)njW}@q#gQd>!e4$lytuShQ zsab=AVTn?zCJi_}w`z~4R;!~YzS3;Xa`80>A&roPxP>S{`40#1sj6m{n;0{dAe2et z+8?$tUSqM?yD&S-aqV6O82HKTfp;&pIeAy)W04}*nSHJIZSWZNXVJCXv2&BkUbdJ) zc~%EpQv$hd#E2%#PpY*CI5;qC_G*qgN`J%Tf~;zDPZ$!Fx?jzl%XF&$6mFT1^&r}m z#7yqbt25fG+{M&&So!3=XJ*aL_j~#Bx%a7r-qzpmj8IAP z=vbEGw|radd&$(T5|yZo{4S{=GU}&LaaS$$F3%i{vopcbuZ`z2)v4Hg8y~2UtWd3_ zOIxO10OM;m5w-zDr4BXW^@Q~T=Yc)*T-J>t!`-)u)@}ry!F=v&ooBx-Jov9hs}}6PJdr(lgb?O2DQno`|hE;#NrW5 zBg#g9UKjz1WbyFTiy2?3e527z7R&J%ZfmJTlx| z>p72iB8z#R-Oo7EjVY*ITBQwp8-XlK+U|d)y}M1czC;8u*?+ry@ehrG)eAOJ>vay? z!c4}3ak=K`Zmg4%Hjl)BoNkYe{CB6@&A;M8d z&{Xwb>j|s9v*>=It;&CO>UAlSm)V{+O4&7vu=TkFV&Ims0`C@^0u=PdFVPqfsKcz| z`kChJ!u>m*{(8=S$S03LM>pGtM}jT4Jx+Ay}d)W39|Bg+O&810I+Pp z`p#t8w>na?F8$sn0$20lK-ur4A!WB-GM;Osv+bmiVl9x11=JdMa~XOp=SLaS+7hwP zZ0+iP(;7)&!<>3cM?l%q)&nnl4p)zGI8L8isqoz9K&)LROl9*>PeJC>yDREJ&OW3= zcFK?-om^eEb?lpZ3`EhnGU}<=00+1Zuyxt0Kv;!z^hGt_Yr|SUY4&&L$Q?fEKI9SA zqHfUSy0{(GB2|*srEA7=v=)NN?ME^9t?*fj+iWK{N~gPAb|F>LH)+pL$7Q^izKeYA zG#ctH2#h8DGT=>U^vm91vX)S4l*y^wyK|1jMMkYQw|#1wy|iJ?Wsi$w<3Qx>zauu( z#MiR;c_xzue)!!$5xFMbLFtaXtgGR;_ao$)xv$HrW^Ud@Cx?Nubl;i>mEz;%$ii1J zKRf8^L;y7H)+b&#PBf1q1@btj(b@NEhrQDJOe?ObomcgZ-U)#cXyST?ugP@D5(S zj?pb&UZd6NMq=Ll_4};7`h+-I1_=%+_RHXarO%zsMsBQmak3B}S-x{y^57bKmPOeR zLr=f&ngoCb``Z%eG2Jmx`wGR6&>c5e>{YML{Is{{VJAE1q!m1f`OzafT=-qPqNUdx z3VcRD5ACO$89n&jx4*bTVEFOHw9@%izWXcj!8llu5ywj|%{8c)lWc^0$(5JZ(!O0> z7m^ZgrYh}EKUGduGu-_=Y0l{+Oh_VQ?UtZnrr{oy=g8x6oXUr#4gp{wADLM3f}Q(? zov>`ch%OY+#vBB6Z$L8rdk*k%_R{iFLi;&6wVyMB2Q)Q;B}tHxPnGTPw`Ss|L{DY) z{aopx-@?1Dt*y)XvBccD<*bl2!&gJYJtH7nz&@tF+3uZv zWC9YL<`rl|q7ChD^suj~uiKzu2gp1DhvphV0ZI!anB#z|3x&V;t)i8(sRKryD!pDS zK-_ll_}g^{P44uV4-t5(?s~@mLIR9Fp?UjLwibi)p{i=JRew1O+b0pc2m*o zj_!Z5v=vm4C`=!^+ez{nji|;;n^!Q>uOGryxt#x8FRJSav-u32#txN(3*}db9$T`9}X}$-lw-TGg7aAlVXNol*k3i6HKW`ta-jMcHuji|vx`rM1fQTyG5&~qhYCqo`kf&(Ce9`E2;j7xCDPYICr&WWm?>5pv8 z&1j37VnQ+0>9`%Beu?ETy$@gX2vcBo!9X%Z#hW6~1RvLew+*%8tQ?@~WXQavw}!#u z!oct&m5}z+0Oa>Ifs`dRF9lND(OIlPfls^q|2%m$1 z8=jrsM#c1Lh~qmfI~j)-9-yh@TKY;qJK~PvTdt*?K-ozC^A5av!|8nse^dAVqP%4hSSP! zwa=x?+;69i;dOt%HM%3G6hVSP=7}!`EqyMe-_87v(|qT)fOHoPqGj;ZI;buj?D=Sd z{Wkpf&fAA;^;)J!mSnIzDbij>r)4mn~rp&M;TYIEmVXL1+=u|LAOG6@)4h~wakIU*xhlhSVs z18KHDofFHM^O|wrL~Ya32D=T9`eQ?U;E)6{VE=-R zz-D)WM85i_yZWZ6tX;Lb`Oer=J5Htecic{bGsag(r<>AA(0jf2`O=-EafBR(KiopC z-1ArJVUdVEq!*_xv{m=EO3bpmikxd4A!>MEjlMM2Ns;}Vn0)$Jr(dCeq!bva4!*E! z0^KO<&N`5FBMGh1wV7{>4gCSwaCc@ko5;SQpMYdTH2ztxs|yN$x;KyP{+;blk{SI4LU4&oA?e(F+;S`F+6+Kl>AKQCWGIMg1oCk% zo7}(GA++_Zx_wb{7Z6tlQ7AsE(Dwcv8~_{8f1XO_z;hT#x&gq1iB?B;Jhtb8O*OUg zVBVK`rZ7|`O*G$)dfYiSKfgB!>I2n!r92_^)?eP$zx46@sNf~@%2&9JSJO=kYB#{E z0~xHwzBh$O^!1W$^)%{NQ+o@V8B{GzHXv)A<&MyIA{Xb-?4z6gDVGOfoK{^0~$tldIBU3CwZ-H1XHVa54>q zAlmJ3E_}f>Es=L-kecX1RsnyINY3o`ow4`AUCJRCtLPO2HM@Cyz-P!Vbmu-Po-NVu zI+zsy^Sjm~J+za>(A~{x0`j4EL~Nr)b%gX+Z!Vje_-*^@omoc$^;i1P`nJkAy*aO} zcisF>XAn!9c3nsbdKZR6G>cEt#Ng_HeUbCir1Zwd(8NE!7HN|4yWDP3U#5dCeyDWb z(aOEGc*wMUo6-6SFp$HvyEQk(7ed4bFNFCIYJFjrEGmne608awDkt4~3gjaaWdHGt zm-S|xur#2VO-SM0SLKwmQ_uFRgWxEXsjHWkZqZAt^zIj1W^dhGs-5WhkWf3Be~UN) zd4K2cj&qO#@xs*GvzKW!hdxzW7R8$e`+R!1eMF9@2eX^M21>r>ETEl5_$rph-DWi^ z)y#;P2-e0?(Mg+FcYxgf`PT+Zk?+UK;L~O8nYoX<^~QhQ0W>))ftj zz|29L+%1ftj*7)ycIZT@A(UWvmYKXES$132lpG{|qe&+ro;kwbLO_@0yTfcq+2)*U z7{hp5fx#qt$o<}^l1E^TijMW#F`4`Bvt1~elIyDu-KLHlD!-zbDe@}dAQER$%CA>a z_dih}QfvzL>Qt|3+I3S0S#0_9PU7qi{PhbSkO4TM z9k10vtDu##8DUCa9eFl?jn+uD^0oEFoA0+b^Oxq?9X9Ydv}(hN1`Zmlqh>@X^E`F? zYEZXf`}&A`{Hg$iO%(s~!WiE^77G%EUu#b0aL zj;px&-Ku^m|Mg}2>7dnk)~}L#EBgDiOYmo@o@p%3_YWruW1du-cv^o_%Q#cIrjqIT z)gTAd;_q1BoRsS1JFVaI6w(1qsi(gH-JgIgel_5WyXzB+#%}nW$!Tm+PK>ej1-*NI zJ}nor+CS6W6=9N_`W`P~aL(-Zmuk&{VdrZ3Iz@SW!YU5TkS&|xWT#O|PR8V~Hpt)ITn7(H-aq7aOH6&dQ23i^v zn!r!W?6vF_xrA^vYBhaW_^ozu zzqQGIvS<%C_Z9-MAaDa-D{}r4dhahY&hUMxPNnnc?VIY~UHVJ-evwY=%?6*wY}PW& zyO{_@CB|hw@4XuHMLwD{&5^QG-#ovPo10^`4|ZF8^aS!bn{*Dd_G$ItF4N2Mv}o6J zv+w7&836&){j8~TEgD9snV-E07IUA!%PHVwvV0ISw)zp-8`xVPrnxp=N@%6$l`hdy z!w+V6*N$p>Le@Yy^m-lFSrF(d%RE0Tkc9qp4me{B-gzU?;}8~-i@Rf zy@q>lvKp?kooaek2ODedsdM-c%5jjlR=MnS$!Qz?b*SBe_tjk)!5g7d9}I%Bx%2HX z(=8fU4eiVIo$YlS%I*G*^k=?NPW?)%{63&Gx@%8~m#b3C)H+t4t9^ItjHcWfdda|y zX-`L))a%^3H4Dt-3i&sGk|3x`{m5ML-)eDL>b4joQ+0PmaO=NRs`@tCHm8iLV_}I2 zSfvlrAA|TAJdeFVuA3vpNxJ>pz5c1=qzVz)M`7IkCI;6Y&7AoqTK7^@pBU^v%Wl3` z#BW*4oxxYdw(tC_blAzI*zROQuT@su`x0hH^TYp^wGIlLaV7&nH3cCo%(}JYJ@cfg zA^!dc^%UOghMUP(q=^?=3I`LrTE7$>mP+9lCd(EEZF;8M3ulOH41=h;9BA*lN zP@9-V<;wkj_3wllX85~KO$Vz7YX;dFyhxJ=EuCiM;nFhuo_ivXxh3{{f8@o>RytcM z-|zjmzExB0N7gL|6|O8l&+GGkB^HTWFa78yFVFTU#RbJS;$`B8+oj^qmB>EXs8$jNN=EHUIDI9(t)pCc?I0tbdq z&TA`JJq4=Qm--+)0XlzyIq&w(PI+*bqj`T~tuuWs#+N__&aIt=2dJCpA!qx~;}qNF zn%T$m1}owrQ6^@^(fl(SK^|4c>TJ_SxhOk59RxW9OxB*Cwa~qy4fa*SQ_#wdsREtD z1lt<|3Avpm`$~W?YN|HRB@mT)7NM)O=wlE+dl&BWTdwTKBL7-j^&{dYO1r|WzvDfL7bpVn3o`3Fwvref$+4{7e3HAHZOH{1-s6+v_78&Ug%u`3li zYh0{$me0d5!$Nf4@2ob*;MAOXP6`y;p;{$3*#4s{^U?8AP{_ma7&g5m?a0*ab-=~p zyMD8xyREF7EW2O5ZqSN^W}A9#b$dNdN(N1}H?r`GRA{_zbD`JLTvO!ow&qdNcr%*c)O*u>7S>E%I6lnTllmP(-J_^6$Udv1 zYcxDhNf~;mZLM~sgY&y^n&c!A&tq~JC?Vv*?7khYOH!Be6fA<+cqtej0mjPhq>Iv% z(Wm|Unu+iH5d{{I@;*M3M#gL^g_{p>8*l_cVA1vBJgHv`iigdcfOQ)` zb_i3I%_+LJbAx{S_Rik7i>cuX;Ce=ii$@d3Sq35n<^9kY;Ppz?gQ?^pn259SoPa*r zt4yLe&FxrY-2oS1WFY;T8eN|yUtsl-bx zo=jjqq=Zs?8tu&E*G~VuQ{!n;X`R29muC=R#u1>{84gO*Mj|kKzhG{uNzNYUy5n2d z&sc8<Rx*YWR!!4wN%g~O6bLr_=e9fFk3y5#Uf#iuCuPk>Ygk(08w>4L-f) z6<2NsOfvW^;v>du7vimHhoJ~#vQXxuhAUv<{S6+u_@~-}{vGQ^wew-I*^V<$K38Mc z-;)xVnm?#E_Ugdh^x8!H$$#d&t0c)bTBd*fA6Q%{TjOl}QfW4Lc8_~^q7CGoy4dMw zZtSkc<4H9S-MxN;H;rG8Ld1tDPGmAaf>Be!Ad$C3N zR0c1AN|v=#H+4SXPw>cUd&0?yEu^~gaCm1`4nq523}(U0LXG)J;ITDifED48_HV{r z9k~sJNE+KUb|Df-b9`g}{arV?dHCreolnP5Nl44s3c+vj_uf@AKAI*>J7s5!@lnp=HXUqv=g@aEmCEm# z?H6w+X}PCZ%@nW3lx_1Lb`za@MrsWD-IVoFbD3AwhzGwQ9EboJIHtI|@2}OHeLErT z`~4Uhhgzd~`aHsl^3JuVj|o<&IrYXfYfb%P>he(fyt=w&xbR%N#%U^seg?>s04WqB z`h3NTnmRk}?9y5D0(^UVx4V^oJ0zAL)JvuOUGUVJ!h>7Xnaex- z!bmSQpZT`K&UTnS`W;+gb#Xfi2~dCFn*YvxZVw`H*v>PYHgo&4{;XA97zEZfRQCmu zI|+D#92|HMwEw#ilvj{pof$(r@TJYV_i{0F*M{|;S_*(6O3muj(j~P4&{K#PaTE5p zMNb5A241c_;w~_dvhHv~^_kmc2({H>T0A@6yHm}x$wQMZ<*JvgVDn{?IX&iq}5 z5e90)>2a)!`&%u0)V){1u$9@2e#i#%H5VlmjIq~|*3A%kuC*Vfz|v&2e-A@f0jiRD z&5G-x!`z@e{IBToS=T4au82qFaIf~tADw2-KKoL)HWi=A7Y*kx{3^-~076rRVmVd@ zg*?A)H>BdDO_nr_M`u9GViwoS)=Fy2TKcHRd|}08(l|8uh>PX)Q8f9BUFm!eGVQi8 zwSO*SVajXtb4~&OAbSvnSGp;BS%kUn7)XGsF$NY1g06ukr4_UCDfs;M_cL%pIjyZ| z3IGG+;0`v%fO%(>{NZ6|>1n=m?w^wODEvrlg-HJbXRn6|HFCVp>idKwbR+se_r37w zTaC@@_$CJ+$HC`RGcLC(!JGG5`n8(_4GO&UIk#>OWD2}MYJ_?3*Q3^j`|??A<#O?y z%}u-hp({LmfN3w7ZV)?t+<1Je*`GdHTkRYgw;}%7c5m3BJdHKTH{qsoOYdw&_dq}) zT*DTthW8pRdLQ^A8TH+&rY)$NQ5%CYC!Ro_EyEe}YOhs6cBiF#>zFEtE4Rts25Zf= z!WI`Hz>d32%stFZ`jKI+MHjNE;YXf=(z)wSyfp<1F~z8&`Eve$D%h(Vz0pPH+gNy~ z?!e^4kvT@v=`| zv;GaxOQrAVudRZVzY0R@sUzlC2|@){8JPT^58c0psyFUjO6A+39!NKVZ?Kuf$&T~- zm;F_bBIty4<#(w_40`WUmSVKY_cSQugX&NK1=gT7udCUI33)QdK;F+oi7#ab3s5cC z|2d}5@=ce_d#k*@s@#gJOSh0KELdQDFB!}v^9o!OphpZx14s1o#n$z_%Hlx_QjWSZijP>l^gFrBe{}s?{UW+q$}}H5{`5TeD9@NagPqNdjEpcnFl&@OKNR6 zEuqf>#End*k^ay`d_qBEge*sSe-y~^70gGD%pep zy&lOe)~ib6_zlK;CDl0mK1GO3WOjf^;Ut%?HvlwtjVr>w_`q~%+gj3L5T#T6;r#(z zwf#kgFG)NUYfD&l6?ZjFiHx3jKML;3|9DN~ zRy%e6wdr!rZ&cf$%r?ceUyDPyw3`2%d-7p7+kSfTI-ocuYtn^kwR7~3uV=59Kske2 zn;7TnDt1S?V(Z3ZzdohY&$p;Xycd znTE6Z!9{z*4xDMNf(&4Z|EfdeB=fxIi0kGy=6)a%-pJ7A)*2o;e0J}Tj<*Q7-*XHK3YG@FoZGtouep*Xma6Rke@aeRx&3AoH^qYjdj-ey} z$gW3Bq}Dh(&TjWhf?l=7ktbaz zWiceeCj~Tyc_~n$sA>y;ow8-CCjtqlL)G{8x^pSaAj!(vaI4k4D?Y;(pPhexE2B&i zo_@B&G;Lzr6$^iDR3%>zkAYBh7-06{%CyKR>|$5#clruU5D=#4oAu-3tha~M&~dFW~aD=`e@vb_du<{;WutpaKzXmHBP$5wo5IC9w#S}tNyt*x|XNbLV<2luKr+M+h^q>h% zjfLn44~JqQ2fw{s#O>$rYK_%tsuA<6Y`S`Hma2#D#8%_aRJoTZJ>7T*p^x-Fkgs%$ zLHj_CPOpQT*b_VHv>>YF@V8YsQ(bab4Mc%}a6yy{fiVo4dx;D}T2de`LP7zLj`zap4k zaoftI#>BL{*=nis_CLd+h4f<1fT?A%g`mRdsOl&E6LT!JX!7sjZBpEQS~U}cJat}d z`qOM;&X&}_ugPWRtl2)=ahm&Eea4*hIO_J7pv3y2H0E7jhO zGuXaOiZ;j|Hpw06!C)&i+!>1bacDxc00^ufph!4wVMdD^8hvU~IeGHjSsyeIf9J7k zsXpN$ixzJOuEZ1lZ=(hEd(;MMfo`HcJgl-8I zRrgc~ZXhMf8TlKWj`0N!9TH&b`NbutwerjGVsc zHD?XDHK})gLh&sqN17A42Ju7hBvTcgKdDE2QKq?6HM_Pqc{6B22k$<8@n5u(Qx_oO ztCef#cpbK#)$Dymb9R=?HPK{!2I+VHc}q+`DmKA6F5}|XVHfmU{Nu&WdiI!piI;x+ z*Wh4QU65dE@!Bp<^tj?uu!gIrNdyaKW)Na)-1>_kdt~gn@D!a4}0Y? z33lgkwVXsXuaC^#P;CBYXg9}|ThIqr>G9qiA5yTT;|qd(HQjQLo2LsqnEaO--AZ%# zrz`FhI4yU(Sn9?f+H?(X$4y6U>eqa;u@i#Bp4x+CSA9w@lD3!9W-vY89;5E}d|qmc z4rq-QH=$d^h@k!0Z2O}GO1OxwQDvd4`~V`PDgm+|Iin6_Rra49${CM)BukYi&b}?8 zasVkv8MFF1<({WmKPHXkWAeMppn2qufRX!-KZoqG%0HLa{0_#Hla6#fVUqnUm=0!j zlX%^D!h_*V{X`hIsrjGC>sJT#=J@=1tNFuo!NtdNzXmYANDskic9g#*qfRs1Z%330 zmFWVQ^Zc?ALp*nuREJTx_D2@C!zxKU8*bH3t{D2Pkm;Q7R8w@Hf^w-%Ps2l2xi>PK zd3|zy*DKp?Bmdf5e{uNrnu#2Eo_|ttdSE&GgxW>Z{t%im-ux0~rZ2pppK*^`ABH ze5KeXPK{NjUTp6;tfLk4TlPSMD*6nh$=qkxs#i+ojoit^Yvt6T|H9$!>EvkfR?Q_V z@?ni1)yya1D_S0SbFH|L?dB4zS4f9sGc(uKA%xZdg1oxo?xMxqi0~GTYGv2=xrT<< zRCAf(^y@lhYYyb1QS|uPVj=t^-#hdOs|}ua;{?}aF&nJD=X!czrz3BolBLemdpdf@ z@yl7GyB@TJJ3)A(o(J0H{@~m%CCvg-Rno3#(UtOF@v8U@)Xdx-^C%pSw=>^?8g&k@ zG*Bx6U9hc}p#GT4@q|;&>i!D3Yv52bwr)D_R;SnsAMR&vKbHYX6$n7oFjo&M1o#E? z;`v&<nGC@THV_#s9)VjUW`8iW4#5Dnp^)s<5~>*WPx8J98p%DiCnxJ7G!i%gy#V8s5q%~e@>K4xS`iJDTU zE%|q%Pw~uEyZ8EIW7iCNPBKUGpbNExh1 zS~dRcYLl&>wAU|XSjUg;7#;ACutR%eK7$!ve~T-=X+T>aLHt|`46XoEVJ`WEl#;ur z$eFI@d|zh(Iu05mzEGfv)qA6y#1+!Qsi-E>?EFvTwE-&osiUmM98jJ%w2L&^Du3L; zMrw$$S@DV^zv0nevT9)rF@+Q8bMu<4 zx+b~6PM4YDV9Dax8czGOd$H|gD$d(z&d-O=>jiRu@J}k_t9$Ofgfqw77)Eg(cPD*w zd{Ws5%+lXF)2B!>!}@?HS2RUvi5*)d?_GcqJAypPRxsNb2eLn35{uIY&X1}VM_~3e zGTbB1E$F?m8beHg*EvoXJp>w#o9`V%k?d;NJ12!cS8!iKAapS0br~Hq)Ikfy-Xu%I zJhs^#=dOx=jUvd}=GcVAxUX)(#CaTI=wqTL)RI^5QCIZ2;IO>*T9GdpzlZyi zV>C(vzW}(V9&%a1hcIdICDpa}g6|I|x4AhN>hmlK3h&dDxS*FuN2Ve#?y{G{FTH}A zr*YuwY65S!HZ7^Ow7U(Esz7?wR}~MJ%JpfDH;-JdP^swM?btTj za{wkO5Y)|rrt?2X?X{%|e}&;}!j<5)z13``)twGfu<=C&X`G)RfIHCQ-kAav9)g0h zZT(6K<|9D2w%IRNYek@3O5|z&Ae-?N8~2A1_v$GA4FY(ThvoXXSW>qA zXC@@0r1+audGYuS=#UuZ(X{vDLiz{ilZzdhuYuL6Fx!i@XA1x!JX=z2_v{@e0%LuN zadwgnqJ~o@!ZlNMU5tB4<&_%;`Wjm1{0V>4Mlx#_`BcXz!Gb`o_)I7 z@XJ{4@qxo=W|lq5y)$?Iri{(kEq~uYJ_;6V$90cp959hPmA-{u%&ZZ z)-MhTb7DTOoR~E#Xl?Zqnt{QLN5|#ybnugt)Oxj|Mnc*8Xbsw#)E!t?kOjf}gOfgl zQ=7ul2DIe`8fnrNLOinjyh5hbLZ_h=P>@?`|K2P9`~=QVd3A+w!a(k%GH!9A-8XYt z;1yV85%dVY3ursjAJyG@i)8nVJJnNhSVgtVaFeOoo>ObXsBienxQtgxhhu=L(c<)J zeYX4aPcbDG6b(J~?@xJf&0dbUG2AgvBF@x3hZId>kBcK@}GXPhZW%8&^zZB zv#<%2V{7-h%>Uwz>9BZwPUn`jy)L`OUD>b)LHxvVS|U+0oj;)sPy;vXy=|s6%O;{!|A5 z{&YREQk|x(syJxIe2$&S+~%M{KphT!5C2~uxwh%=p#IzG)^2y%<}#VQb9fNjFIMh486@)_+~Dul6@Q)$U&~igKV6T9!?J;#i!xvazboqr z;j|}V#uuLPp!ShxAG;5a1UAq~XFeNJ&GNdgT<`)P_fp9vual4d zmsUOFgtnK?hlCWg>$AJOD+v>`C-02XE4^5QyrausTF(%d57jJn>Y-A%!)zB2tpMH# zG;FpCkVH}y8?E+meSWX)9VvI)qjnYf6pRs~664{-PhU)3DjZ7c;SY-PZN)hKq$XWz zqb;0C*Aw;0pn!b1=Ri^yfla1fr>rz7b~}$tOnE{DYZEJd&-~n zo}PNMk^B3yt-z*>i-y$Jdd$Kt+dc5qHs>9PKl;6;Uri`{@?)S9Ao%Qx+CkBMEu6w+ zT8mn@u3&ygQD?>lJm7Jq{h^MeHg?{srjWlA>}-?GMf6X?GGGLSozUc^3k*i(8dCQE3Ir zjKh`0D}PHTLP|9W6o*c)H=`s(c9=;tflwLlZ<}t2a?l(hF4|_!%6_8hqejlx&*biZ zjt7$CRI`UWdv$|luud%6vK~iIAmFU7sXR|AZdQ@|hH^JBBCs4}7_4JEnbV%D`GJ4E?Ipv}->X4RAirN&I|j8o=fd#<>TF@SC$ZOht0dT3C@_3Lwy$JcwXa{ldT zt~Bf;gvOaq7&jm2JATMCN4t%1`g!zWfe_{T`?I<6S`yX|@9O33qWvs%f7=EoaIYfR zZ@hn}#hlib$2=c=eJWGh|GW`K^rNdxJD?IsKUc#xMIUX;sb?Uoiw_&0_2cff-tFe_ zV5BtVmJa{b)-9vVtl8>|G2R~vXh4TxJ)Vth;cwT@k7HqW%7>Xvt9u_f2+5;yycfW6&vy|*3)-ZN_tCcZtcO(6mA z0o2g!;>Lr{+h;ZT>TOb8rw;JQ%NV}kxN`ok+b#PjKQ{CCf~w{RXxI{pCye|n$&qSC zcVT`m&s<)qk-o~zCd_Lalw=@z;6X+cBjY=bY2QAKo@3MZZbWFu7%!Lm-2Pf3tO8pC!ft30^rtUU$2(7)5+W>N@YtQO)EIDG+FsK z*t|E3{=5su2T<077pgc>w)NpG{U0;78Ps7{0!E`~%Z2eRS*M%c8^7J=r^5OrSqbH0g^eym5|k2>)>kjFH>j{R$&>p2^;qccr|J59e_ zz87mo0spQrNqQ@>ZBD2Ed0mB_qBbhm<1+m^O~S{eKX3sBEiBC9^+%aKb;_^K7faEn zxsbLtQdN$Bud~@aw;Q~yW-nj;TjLhv@o<59Yz7J4!l6zgQz}=F2B%(k(U~dl3qp1* zt-PB+>lwfd-EcUR@B2M0BHuzS=xr#YF!80_ZJquWz3gRBHl`_k)OLoC+4-0Lx|#Im zm&VVMN5}Kpg*dN{%VKqETr=g9Vm@E@99Vz(E4qwvSh#AAxGvL*alhZY<82Z__b#;> ztb#!~((cxwY5;iw+nevq!-&&iYpK3i0PmJr31ZegL+PaKu7u^<@4vMTvH?d2hSwOQ zDfOeGq3i|hF4c8nQ`NvpbQ)WcLMu$&x_4&U2y#bl8@vbP; z_|pnRWuiA6<*`GH-uM5p_nu)*bZy%xpa>$0Vgn11&_f`FR9YZ(LP=<$g%Cmlp(OMw zMHFd@iUkp+2%@4Oh>fZsSZONKq^O911(1#id;@yl&;7p7bA0>Vdw;+7mg7)nhFNRY zta7bc>pZXX3X#?F_O;ZI^;3X?fKjHa55%8>*yawfAhAh^Rq%X@c0fM`S zune%Wa%8fb0g>cO*9-DwvJKF3c0>~=-vEje+#jc|;HnHY!l`-ylLa`EqYOmxQ~=vL zbJ!Sdy#Qbfm*wba1w;~Nxw(0Ap%@|v=4XjFv)4z!JPB+$vWt!{ptPXm$pO5AS0Bt) zAwsOp{T*}y$POVCMV_jGCC__pKg^4!K!gCQ7zAY&@KtK&#bp7~dm0d3Jw>P|#}epX zN^Zs|D_LzH4~DI!C!D0p40I>@kc{mu9c!GU1G7=V)vF?5%O!VJt@$g43<_0>3ln1Q;2 zsTD-DH}^sb_3xJd@S{~m4ZVYY<(zh zj8!3i1QDwb=;|l}D;GmU3u`$KKMw*sFqnv^`RZCh-93X`xRy4$0s7n!ZyyYa%3DRV4i@v#!!G5 zc4#|yV?QScPgh?BYx5AGykK*TMqrRW&5vqKQZhAWFz|pV7g^818UrV?0*&xkh&9?; zmBTQE>lo?;DEk2p+d8Hcyq-d&t1HNesQ{+%Y;^pUAqH3nQwJk^Wsa}4E`rKI$$@;0 zum}o-t{jB)Q8MH52&$H*NJ}t4jFt8B*Ov{E!vn%;K!6f|xO$KS+Ls5>L3k5`Yyhm@ zTsF{(>Hu_WO$MrZb%S-TFN4Afh}ybkD`T_n-T+}Wak;^ukUSVM5Vz3 zeB6;>qB9^W(97HEEwV@V%tIK{{5RzgIP*hahZ58D3zCrRNq6H`*hy%pGbyM{LlueWZ zSf<)qXkCJh0g>aR3WS^RwUZC_QS@`i>q7#$Agm|aQqfVF21r<$Qss=fFg+>=7HLJY z&{xJVl!!jo25cWW9^l*yanXfnagaEg994m>K?Q6iM^%AGP*YMhw{gQ_jh$3v9qAz? zrY7K{=7?6;_Hw41+Y;g01U&_o1Je_Rv;u~qp}R$}AK8He4pv7&ogFpYT#Z>Ybya1q zg`yH$nXUDY?_PkPWQ5QUGDa&K0OVAlkJ>`DX-E{H@2|GJ$ z*0!eh1gs?(9O7YwRiit>?A)v)%>W-|OO!c*q?>c`}oq-txbq#8vZ~>c2raacB_C$=2XLAfTSI)O*bSULm)sbGXk9g_AzkQ zVIe%dfC*UNo~CbXt7rir=z!X(lfH8#8fmZQU<<&m7z9Ma32z)gw9p5lh6ZwVHRx7; zvib-*1O(UDmGjU;Ky0ASBnJf*byYczg*ge(TrjeuvjLxDXEjuamWQ2}JlI3t#Yx|Y z3wU6HyfHw~4h<)sEJxK0aPR>mJXMsTh6H_iF9*0hly2dnrsWa|1Y#$dC@6qUUD3fx zE^2^YG7_h1Of*n54pe}ex)6McUQ8Pp1*46?5$FL_3Xf^6X-Y70Q&m<07UV#LYgDka zo*66f?<_BoYIJC1D&b0jB~k+?qflsv>mg zY8GTalDxaFvV$kt*W7~vQuG1rzHgwvi;}-ChoB3C`S|i20=QroM|(pBoHvy$hxf+% z__;CQP1z2NNH>W_RPEOu*I?ce<-U)E>1`|mzcdk3Z)YnN(Syq7v zFh&%RENGysD$$+-le4BFk$5W}pp*$H+X4tbU`s67))-fkD+wUq_@h)vKJY+m17D~% zgyvxXe)2ONDWsE(ZQSs(;?eCK}>zMobBbv^7@+c z@>=rpZd4ByFDnww)I7u>kP-kw(a3lfHj-)zv2Zs6mQaCSrW93wBP?CZSl!%L4u=8) zJsESED1^O>3QJZ42$TaTd|Eo{V_{AV7YJD0oN5S6PEZ3c16eC2b+)#rijyfI^=EY8|2Ed(DV(#6C9iXvNS}Upzp);wnjN<$a%8dEt%RbAhfc&jl8Li z7Kf`?ljCmh$7MLFkSWeS0Obk_cq7T`V$@-1MS_L49KpgnCdokZ=Gg2MFfu?BxpZUu8K2R{+ushG0f&~^a#>`+j(OFw8>OWA8*Dw&0qV{>o>Yp9(S&`oeC4JWR#{HoBeye3B*4 zz_Ni$Hc{j;oj_=)G6PGcB1i!gmMzwlhGv*JFjai?BmI5ofC@ISYve?C47Bj`0*7EQ za&AC~K`VC*lmr64^{tIb=wLU5n~jsbD+UMfgJgjjUz4aRyQZo}<^eip*tLzD0IrV( zRo+I$8ygVhLDg5MxtThET**duevU*%Sq%%WJx$p{7wot;H3R^Ta)8a0#jDa-{yJ=) z2a!Y%BooL;K+(Zk701D|;qrRGkhchNb+UFh#2``1hCTrM#UB)Gu1&|dTNr^AoYyv* zJ-vV=)YVAJL1ZjhXO(T^Z{e${8RX}zWMCEyIJGOwGuc{n9uT`1&}2gU8hdz{7|Ll9 z-2Y>(y;IU>JrYaf^+z`{%Ph+MF zl?o_5f&k`?fgTBP__nsu0K#(w@L(ohA!_mjXD_lBAeZ70qzksS_k{ofsFm&QXv+F7 zbU-{pThmkpP=qt|;gJHJbiKF=fle_xEUrgTa3G7}CjuPt+`UCm7#XVcM)2#m-H z4kEJx^?82&JT8kHEQ6DQ>GOPn@U#95CQAlpzzXFsS;m2kh}B~z&x0l73?xU$&}3jn z9N%D8AaD-Tv(X1OOwhk?fEoERf?2;$fcpSqho@Jtj5-<)WNyyldIo#RV31g(jEIa3 z%qGM=IKm%zlBplV6ZmDjcH&?K1j%9s%fP4{58!Sj3I`X_(GmImBF64uRSl4qAzT)u z=88Z%VC-Z=BLDpSsewN=@c-8u08BeYfdo+oTyuaQy&x2xuAyb9E)Q6VKmYxygFpS? z&oKC73jSEaKbG*1CH!Lv|5(C5mhg`y{9_6KSi(P+@Q)?@V+sFQ!atVq|9h4YE$@Z% zTO$X`2buz!Y*36E&EWsV{wP<~$=%)6&GJ7%D%gKQD!f3CHCO}=T!gOzCHg#WFo3ND z$tWqWq9{SZ0A_-KBY{6!%fK8Qon%l*Aj}9JgO|bJQEFH$9Puli^(q9idMjEDg-2m! z0GT>9KrIg;gHXq)VX%0t3>FTgT)hH3a#oWu3`3rNx9a(Th4(8*um4-UxsZ}5i^ z#4-ZPwhGbshO&Y=OopnJyDujo1OQ?Txd7V3;d-uOFkC%u5a(YPEE#^l4Myt!c;+f> zgZS&At9kzOnfQM^Q%`0UcCwLy*;$+Zb-x}nn8V}#x}@Y49PA&Y0fRBU7#wa8iy0Eg z364->^8A#6EM;KCf2-Z!wS>f>)R1U(br}RuH|i)1T1FiOS3_d(>M}^I8fxvz|Fo9S zs}1*8Ev;2y5YP@l3#_&nYV~!j|4X|f5v$b-G^Pd1gTo;5!hyS2vq0m~YJheuMh1sM zsNryU+-fQ^BXAXHLn6?>(pa;Ccz|~;D`>4|*4ksW{ej%bfjp)SYqbl&$OcA0ZLz|E zwps(qh`)d9|Nd>dn)6?%*=h|N@dEuEc-_FZicYhEw_ z_5tNx1@mkJ8QitkSo_m2D31)ldaF18?ttjk)PGei@po0J0hI;(Q^%^|&}g7{t09p1 z)ff7WF#Z=naqZ34vIhFwZ!{5!LjxuGyB^WMht}UCV)fmA74V* z2Kd&RIcjyj)SfB4X^BeZyW+O2@DnLjn~L9o=jSZkPZhq^&%1aStIq59I5N0Cjrl0d zavL-;&cBHy+jjJ7hdlX&@%3{v$s=u8vDWx^nTn<(gp&fxOG`qo<5D*>a$Zd6^goz^u@iEAQ$ZPfcR~!BVEk>*V>bHjB!NxXg-2!OM zwVu4ztJk_E0wV*A2jJpb7a=o(0SK8ZgGT+j??3i<+CRI`>LdPow7(}1%fZuqZG8OB z=r>?(ZA_H;FUeN(|DEigxvduOKV1GV2>IVe(#*u#oz`|IcMxDVR;%Nm1^wp) z1JK%64G1t-08l7%=s>M)(>BAT>CX55~g$2Tbe@#1UsZeX9-fyk0U|2A@~7k*6v|5hWW!3vjsTYefKuV?Krt*beo>8_mQ#mu`z#%4H6P;H!mYyr-G`z zlmdMNy*;L;oPv|-W9g|eGm9~E%K=u*_+a>Qu@&WcPf{c_A=%hyPK zAnY(7gzL|jB*rJcLvUlVz(GD&8S+OMSX^G@#JZ|SH~2sI^6iup_#EFlxqkFxLVR~m zMgNWIPFURL`Yn9PNx}ikTZEqCh!nW61V34*sz+}>&;D@Sdp?@n`VAp7Kd2&@08J`Y zks>7}CML!$&EJF-XQ@1E**Fnb(Ze5{t42Pp*}iAux?kLeu5CZUf_mqneA=4lUP!Ca z3XWYDk`NT^m3Rji-tj(g`^O`tM=tSUzpM*B^M>!dCAmS`;|@XiLLA|q5Z{LlOBcsl zN9f6JNdt9m!s|yz)*FS_ZN1C4S7M(?n=N@TbS8LcecZN=2T5-CH%%%?ecf`TuFf8N zc%bnRzX-C2Z^GhuZ0Lx9Ufl&3+I^ba1f4ed(=4d`<)rWzAS#iuQvDmj6>4-Ke8-N! z@+nuXSn0BL0;30YR}T5P3HxNxiyf-)-$!KKTDW9Z1;G)k@H5u;E}0s~w^ew@1`&bW z7QU;B4Qa!j;7C`?wgJmF*p!8|j+bJ=_J^MecU~21+7LNaXIvH6m&Zpvy|-OxDJLyf z_P|zQx6Oy{3*5-P3tzuX=;k$%vj`K%M96TfIOPeW!!mpmol|o;3&&b+oD{y_NthlG zYRax4~d@LQ`ML+Gbw#)e6wIx^o|LOgbz2p!ughDTr9+qKvOw3eG zQRV|$TNdKST>7N2@T(u4)!#*g1s*Ex=-RaMDVKMGk8g9Oq`A-4VV>%`@cY|LIPlQZ zpH%0-1bv9uOTL=*eMz+_%XPW_!MKQ0`*xAKoPGMlhWM}7etYCma@M$sF8JIY#aBsP7G>2Paf8c zs$`q=j5^mZ+4Zio56#qfG5y$BIr4G-`}580Nnuzyy^2?O@>21RX9d_|%@oBN9d^=C zgtth+7u$Oy-v-i#i1(ClTe@5w0-fqF?D@V8emC1EZ}Eupr$_H@&fhI*FL2?$Nm4#= zQn2`oUiCF?U%R>-&&h@ZN&#KOXt|>uU$eSK2AkWzz!}*pP0?LF^(ms%!11DoC3Bms z69s9f6Rz&h%4B85OT60WA@P{`A_iWdbw1$s<6CDJ_AyGfI$}@ftlJY+sif=T*m24& z#%B-dC4Y-gMtiSZH5sx;chP0R`Sg*TX+g47C+~Tmf#*>_l^d_}Wn?X4wWHeRCde9{ z!Fsuk2cK#j7%e9xLfs11Gv4V{?cJg9@=V_V(Il|_;rIi!<1Z@WqxWHn8;6fgifCBh ze{{iS>SQ9fpsIAS1{CEy{Web5)s{dh@g7VCeG^SUPj)#zj?%=P()_tT{Dq=xUL6to zO)*8|U>4`9fGFz0c%H>x=N*@l5Jl10k3tqIH%;p)PIFnC4AqWJUAfzOqwm187S~$+ ztB)E+a-K7twaC5;CuVzVWhM&_Oq@Lpvbulxryb{cvYnej-|pe-BG+i~-N~h|#T$0^ zHw{~wK0fuzJVN72U9R=Bac&A8?9+Knn;{<; z+A0q&8*jeZ#B5D1RWk#f?O9f^g|4r;dHZYYg-}By-eTY2i^pQula#DK^Cr$ls*asb zpUsY)D4)Mz80{k*m<@K5$rla3hvQWgosrQ)CG@Iq7EW~oCyknJ(bQ~=S_b*>u6QU) zj-&3Nbdwvgc8nWu!ucir^Y%Sy0iUi}3e0@@^yb*?V0xIo#c|ImlLN4a!L*K|M*{+n zxtW`XDxqegXD^q`-7=2ZeONMP;X>m}60-Hv_|XdOjDm_4kMl3$80Ci&JG!3M+qclG zkN?=&xnOlXr)nQ%V(ETSN$^IhluCUigQ=W}98OCz@6F+LzCgKFWPKRkxvd-0Fy9uchRhuxUegQmCK^xq`B2(Z5SynV>Ts{>0@VDQnq9DwSsJEu;{%s=Yix1^(`rTqNY!}x4m?TO%hc9f}`c7 z)E?hL0x#@;)^<&PD#LH`o#p9M+Z#Wd*2rDg%g$GqjxyU%Ix);2Te7N6r zXR`b;30fu_t@%)F3=`Q}e=+9zr25wpsS@@dsUkpmh$6_cl)}3mm>iO7e0TR z6KS^j{F!<2eS*`(2T6q4EedopBCX|&(FnP3NR%5@Uwg=-#iZuK^X2tloU4-rMLfSl zF}8jabp;)X7LR+oZa021&v%|Pzqzoj^86*0{PghozIB(g)ASeeW+=jsr*>xq^ETBG zBM;?fY(U+-c0ov5;oIakm%agl8>z*1ZeODMsD${;{>*JtX1>p-l&>91UL?An>{h@B zEcbqH+^QvYMx*%sEh?q-Dg5YXj@ZX>;oqD0tz z34{9*G@|l`l{AtfrClEEXwZ3g(wER2lKL($!-u&cu{opk;j^fyP=!L};EEvVaYyOf zXWQiq3zjdmR%8Q89{~`@U;%U zN9mWQ>}n76r^1ibOG|HenB4u%@^$-lw zyglTG)z9TUAXKIY?4JH!Hl>vp;1n8YF8S!#!;pi#tE?}dB?UCjK9~4r_fhL>;OsGs z?C49O9IYDhpuMCU)a)K)vmyOzO(jG&QTn6GNySLd)V*w}8rRFb#_lIlkZ)TyD{Jl3 z$dPJ$SN~w@DpXc7PfFbddQC{b*&Hbh9ml;~AN^dj+Dl9QE8hK9$*ZuP5`8D1<;>*` zygrK5Oqud~w#HT_EopNL#4Z z!xvrO5cBIt1sZlGrpSeIJ?Or9!-?|G^qa59HJa6=YjeBJou`yDicfQ@DHM+x`scbY z9`Zltc;ygE=_xH7L?y`X>~p-wF+%sPzPl-pgf4fA3u~!#gQR*|qvF2PBu}^L2rh55 ztEjIb#(H}`yC@y*iZ4)wT}gD8mXynlc=%NO=lzRUPLD^vYPe*Dd96M7BywcCU*E{5 zV43&nFZIrSzUO{nqm!mj!V&Ci_lvaR=%||1D%eFSJ=-JOQ~EF2+^Tweh#8T%;dP`t z%A=y{^$V-WbOL9;*W|%^@$ng$f3>-ar`m1tnRLgU&=i&E3z-#{Pbuz^rV|Tw4h8Po zmZ&a|@=3Xq{fuhR8lW{+RM4&Hd(l~|4*clA6IA?k&E_{YY_qrYhwcW_&k|3BBO)f@ zLMFDR1}i3K><1Pqh9r-E*-x3cV|Vy?SHcUKoi=s;J9mH1?Cr(lbe&6#x~|xPnz6U{ zMmMJ_+LYlR>>Av$WVdh(%jiuw)_nFd10Dsg;9v68OYuxOcDh3wbI-z9D)!Au%kL`w zHnHZnZd6AN*R=Y@)OQpXUhjUwPYn{pb=AMe6h$yT9~&8)stb?oOwl3(u4`Qb4p)AaC= z<962VQ$M}K$L;i5oDMF#xEbijryGzANBWx*-L{WiPTwu2$+ zmzC~aDpvk=Po}}g9r6-3-Pu#~Qb+5au~%ILZBudklG?bWrW8f@+2?(iZbsdZ(T13w zMKC=)gQDJh+uaWMAg6OZ(5%a8V^K5e>-YT|KSuj(^6tS064+Lgw>wUpX=E=8|9t7> zZEw(UBE0Ht-a~5?YRutQyZ^%E^bLz+ipNX_oqzgnc@?R`rCn4x(7!Q0>eElrC^_T8 zTeZwL&Bt}cm~6jD>9!{ig|6s8KYdU;wROa;%53TEE}5`>InAW=SsA>N$(cG_w5^}f zC%I;`&3c0GUJX9Kl=`atxUSvj3g`_P@l-xVZwVv=fDsOJxRND??Tuah>jWcUNo~iGxNelP<8*)lOfsl($nXDT)DR4M2BW#y<*OHTzv7h zX->d~97Kqj+4<$c!^d)b6W>3dd30c}m8DeCnDOpYM~xm|s@eiAd-D_%`pJAMYX%{& z8sKysY@b(<8|KMLe)jdli(=WMg}6N^wZeTm`?Ywlr7bIpYWFO3eS)}yFFLSUTx#gE+JzJBt)}QVNv%7QZt?XcMrNNcuijm0 zJNq@4&PDCcG*M?vZYPi3JZE@U)Rt`AvTxffHhl807^6mH6k4DFp&3rbi)`;*vDtqtQ zZ55-33pJS&W*?lAS48M-X(z6|{3z-J61$>rZc8x#Jn5 z66Bb9VRxvNpUy%%f1lCk8X+QmJo=c*#N}jgUU;{}8M`NtjaSDN%873`qBVR1MbrC& zl8%*>Xo+OQf9%5#iG|+T+M(u-K0v%(ds=YZnmtm0Og-ZRHak9kapoTM`*V_q?9>oe zRiA|mp=RzCd)uL=Q4GF^##=|17zwE}h8PBO-bx4B^b?Ou$^D?@cc3TdNofaw7(S7{ zKQ7hl?%w*XI+Yg=RC{NlvL>@I0JmOuZ^(B8nsWOhV4%V?S`}l*5QC9p) zmPJvgcE0#@D#NHX?~oLC&$!?v%2HoarW?1SIqqg(g!TTA^m8|>u*&HhMzz^q#)+Zd z0_ty67al(xffh5}h)X$pKqE0JP1|6=^WIrH&$Q)s*qfrW>tBDcasPh(&CZ4sLM|2N zl+ezzcDpee9LcKil%qGQ*)i z=Eh#zkga-=yP<=QErQYa)Hr*pqjQ$7e9BID9Nw^O|K$E%Q;K84)S-*J&7N-6ku5v6 zsifpb+!^7~LYr(}zE9tf&;4Zsd4u~OxzPv9z#eor&~7qPYRiqDLvR7_d>fweauomRO#>y`jK|^z-w1%+jY*mYFtqV z!VI2D*1!{N;Lo%oo;S1%SKYnHO8FduiP<>y-YDg+amy@Xy7kiG(PJ+8 zKE)xQuckbCYq#I7YTLW@I`0J~UHihAd-#dvVq;HHm&13aWXw)=iC*)16?!N}WLDYw zf%c|Tmd+2Fad+mKi;F&O;))d9&eAup@3T+VM%lm2J$h4Q5On6|P;Nv*8@X(7EH@s{ zti60g6{8ftYc8Sqc3B?6FfEbx-pbzUt#Q44a+YK3BfCA0u?m=ZhR#>rp8L z|8;Qw+{b+ov%#Q}9_Y5RoeTczflWU@f1G-%g$h2~@Xq0M%A$E`=3_tKG{<8~xz{CS zl1bE&=j7}6rT5C2k9Y>jTWrY{NmW?y)k{my-&aUE>?jKY&UH(wMccW3H#h!4CP z(^2>89^{_`N!?qrYZ^Y)eu?jLg?H(Pld;&o^Yiydw9QKbZfNoMkhbjC3{Nn>XcU3F zU7lCfbjsMSz5HC}K5SRh$JYxWy&sB-sYX)|QzmXET;C#=R&-996;*2_wOis!@wbMS zRLq^@P^oT@DvDRF&I9V_-8%ydX4k(x6`mR=Tk{StCqUX(viGt1wGDD;3Qci3&EeYv zFBN;F;|ucWvHhv!{yPEOFW^b!&{naj(UGm4`)LEk^u>vjpUaaP%tp*6PbNNiqT*pT z(HAU#?f|gY#AUx{=No1pg$#VV)G`#6rQR8{G#EDaDU|$X(z1l_P&Gk#YPzs9P7N&|vgA1x94+g)Bcfzya1X62y>7Y?6XMMoH24lG`MPd<^V^I^ zwk`J*Zgp+vz7I|xSAkx)Yjp{W`9cEXv7Qz8Pc>8CBq(#!@r=l8tIDT6%}d^C))1@j z(yD#nD|vI4Z+OCIM0@Y;Sd=0}lr`hu^enJF+E3=Bz)_hX%TF3-;Yl|t!$EYAL&f`L zzW8amrQw~#VDs8<#qVkobEqbs?z=ZrI=xHwoycKNA&fJ)t(d8cA(c^&67G*YDSVjP z`*_cI(m# z{TFAW?UL_Q+@vG!RSSexgi>o29&|0tw`@9f*ycz`V?dq0Zj?+z8);jX=9$_po;i^N zOKw-Jn$I10_p(;acc`O2`$RA|>+aQHk_7pt^ZeVbVbn)Rmm}u`s83IX8_CPaKYMgK zPW^-P*8=0m8aJI|NYU=E9QtpdHbAaVjam9QcP={DX#t@TTE;57JVQksn~ZNoZEy9e zr3dzHS_-C~LQvD`RZ8dPj(+Zn(Q~9+sn{6Hd0Td5jC+)wns#=o%^OAP+!AGKA)e#n zcQ-6?KsN7*yd2nHbG8I8_>wvB75`Dsa;qY%_`KQ9C}Pdm#XZj@*z-iUM>D53H}!@1 z{D@T4=V?CCZ!lT-8E3Mr|NL$1SE9wB(w-NTDr5@>_`JM#N!o4Ejl2?`bcofyS54mk zN4_L&+$XGyOnIbZkal77ynjF0c89xSNXH8c)g9x}Gn8j`Z?kls%x*kz-89wa{Ef>? z;Gu-6t(%@^R9hw;P}1QH+myyaWH)>pHFjG#7F*LZdJZ$rs`(*YroNi9%iJ*^La!h+&!>f;WDXGrv-tM#IWK5Wy$&USH zJO?ks^WI6Zty#-X&jRXR?~4scq~-DLxAIRT7C4$UM`H7Rw`V9V%VijY#_2*;Entb zG4E_v*?6nB@31yIf_A;i&zZT%$P=rUaLbf0XO!>~gq6yQKbrkKDfbgyUL3t&Yv=G| zy=OmIr5BInIwkeI2T$2*21lRo_Nz-;cS^?K?(N|l{494{mu{zB&slE@(r)TyIkypf zp-h!}>giWfJKq>Ieugz(donu7l`-_R@qKBVXg4A`E>G`1fGE~Rq!P_!BS)W1Fhx{? zPGE@n+3LNSRpxO0T;uq=H&Z&a;vZc*R;op!^n5TJ?kbI$eyTU~;?U*eFfHX!>=!3nx$*5*`yse_y5*!S2~`RYa?QvdY3=P^?>p0xxwBTjrf+( z2wj^R&lgWZl8WRmfLxEhKnC1^rEERj5z71w*`K<<;q=C)$2fGst;mZFnW;-+k1>J&Dqk6r+9-jf|C23~S!y9GxNVRx|S)_VV2#@vzxr#r}m3 z$jgs)!^Iu#i&5XcT{`hI=z5W4ZQ+>I+nbJrMm6=BO9nTi_E+wjIduLq?@g~{5&G^? z*|4)*$*LbFj&7rmV0oYYK|_@RwB9|l35GTgccV()zqJkiaP(Xu`$GSo)|0~4n5TnX zsMMUj2s>vx@o?WsrtBd-sTG|NrRa{_{;09ixv`rb6MS!+&m2kg7(PtC@@;cY7enm* zSm`_6S9Qt#jhSx`CvcYyCzatwVtB2uk1i*8y!E=LvkCLi>XzWQ!$VgM#uPv+$C0+@ za-P{7KTm5IMxS)pb^T+}TY}~MF+XMfomZ$yDEU40S`7s?74N$$+%J7SFZ}!}7Q8&; z6dc0gZnNFi=FHV&6 z8ixxfg9VshXc-K^69RZ%2!t9!2CJ^FrvCf7x;k79AWk6gYA6{jMqLdLfbpwW0DK;g z1z0n`Z~K+WDk<#mD{II9CBwDkzcL4sBjD;dAiLiWTYDrR`KX2j*i!%vM+S|?0fqSM z7@+6?Tr|w@|AEwKBpRS|tdHtY?H3NVKwRQ&*y_yt>TfMrPbnTUX zUBqE9z-#|b{C9$1=jv;vK_OQw1_cK=UI4Ma3c-q4;CXBF15}Z)JLL_U(hr$EQtRx1176Eh)UZ$sm6tV#A1azd(`Te$2>u z?cL;xda$f)AB4KIXG^B>Y`(~QjgkE#P|vX+{SS^!xL`K#6pL_reCwzUf-#|XO<1Sk zATq_Xb|@BGAKC>ylG?mg1g5%IGytq|=gp(-Q7?Db%(lfm+%YlC8+bo6w$MW#8|cZX z_BigxR&h_b?zpoxzPx|Krzw25bJWjGo!-Z9|6BiGtJlAHJpZ5&VSZD${zW0e{H6x2 zQb&F<mf)W3w5EX{z0l)%j~b${vT0@Okkcc zFBs?l40#A(NC7SO>lJ^IhyJZD{?Cwy)*kfNd;V|9Lue#Gfch7CNF5+7{Y@T11MT|z z?BB^lVYmz5$~)vQb&imPFMa!5Gv^vV;^<9g^`7qpoKH?c zP&`9GfS;(F`;o3IXC{&>FdN^!E?AJy$z*-|#UvZa(a)*zc#Q~`oas)LxV`o3_)N`G zrj{&)lqz-C*h6iCQ8+zXiPb85hzYQVW+s-X8@ZB-Vq#QfJv|e->=J{evg!shymS|(GU+D<|d+6CJd+39R(R}^ZG(MqK z_7L^;wuy=F@7Bd_n|%=Hc3*Q+L;9;Az#azEQ+LE>by*Aj~%)VtW zYJtkb_mMlU5}NoUmy8dG3PtJhODS1hCHx?)Te+L0D-)+J`hmankVgGhgB9Bcq5Aum zOK&>H#V_zd4|d9}=Z||36Z^oCKGh_Z9JjcxN`D(vcIl?+=|zHq)jaP+l=D&`zt#{#NG|u znLg1edPLy<3aEM~d53UCOx*Io?Ujp@v1LotGb=*xSE>}zR=A(o_(j;$6=EFC&3}M^ z=-f9ueZS+7+N9IYq?IG*^;S-liHmbr1`rlv6L%mxwxvxxpl%GJ9$L`-w%JW;>lK2@ zRN}zLjF0c&>)ZK8w=B+N@=qL!d$^K9`yx6y^26psOmou!Xyne(<>&&4K>Y2M%UBQv z!6%U*uqaNWPv4Oj(LGd>z!95SXlBLb@(GLvTokx3)gt{a&S2Y8rCk6hF8494lecO8 zjt#;Z%ZbVYTZ4ay+3b)JUi4eOE|jDw5>otZz`TMx0b86C;uD!UNK9fEp4d9Nb-41x zS!s02(f*&;A0g*8M+s8G-^F&n?VT8*?7v4dL$y51expXs8r^WM8+UJ1xnk+*#T*^o zo9*S8`ILQAbqa69zY_)vsA`YNiGwCx9tqh zyDC)OHUFYaxfF(8a39wl*k_0hGH$m~y_x%lmIfCSu^ZJ<*txCV`*j8JRciIHt8?aQ zFU=50g6d*+isS&c`&EL@k4uh5GyCV}cybf@RqYyt>@PdOHZRmK98PxBY!z+OAKah1 zZ9@G0H`4xyu-*-vJ9B2a#}>B|B6-Gs6IB&NPOQ^`G5^?Zp)W0dy-lX}l@?v_9J@4- zmj1?&v!pW_A#TUtHcAe-9oveKen|bRq6@HKw>6zby8_x&{Zpu6IOyst6&M`^$^v zrtFId)xCl|)}@rPHZXtU7Fusv60)OftgnFQ*=aVF9i3a)iy;ZLGj#JO~*(r3sJYiOGGyC&G`WI@kcctO<%l+%mrbna1 zzt6mhm<%NZ`#SGvbiRExHP=G+QJAyYjqNSZ{aaK(caK=9j^L7oNTL_4Xkqe-y!GkA z!`lqo4Kg^Q_vW7wG6F1G56B~bShDL1E7V0ROtz!aX@b>ul>645rWc@REWe%Fc-XLf z`xCJO!wbtY2PQSbctYi;jOJzYktT=KW%T`uULV*f8C;V2^pSG*oOWQsjGo);V^QZa zn0|AgHYp3V)R{-m_6+76D)cXYEYkbnX_`IDR}|ov?L3rO6Pxiu)1>U_Qv2q`MPku% z?WAIi&DmlpJ)U&+lk$l6EYRH4p+ zcVq17mFY@!>&w?6%B5#MRov$+DJo>{#QKGNC(Nn4KUVrxp?uchK)(ve+ zTH)l})ic^Lslj-{$yc2mnl}Y&Z<1NJ@ye^qw+v&#lO@h6Ua;6dukb8Bs&~P<=Xi+i z^WLZ=@zmtH(%D59e;=A*r|!lxneju4~ni^Vf!)2wcA_L(y9*oMk2>C)uE zoz8Yvx*yG-;+((Q%7%Uylx*HzTz2;Gl{1c3-rt`wtK>b_={?zKR>!{q1YuiH_dK(1 z-C5vpWrQgT?rNqLPd3fiEa4x;^NNI2X*z^;33kZbBELDSNeq1nFF`4yr|XUOFQoai&?kSwC_RJfd2-*SnHzRD>I8bP#0~R1M{=cb5)@_ z4BpFY1ZqsFtbXW&39+4IfhYzLX`g=XE9>TCgLrl<64w?oz=gC`obzb|2%BtVWlT`@>b*tJj zHq+n>WQUDsm_;fBt>5&ZWrL;*(wl#_IC*2b*=6hc>cHz7hfF8?75CDw?|E?3fj>Im z?5$)XHiA;t_cO>tff;5kDV_+q9AIf@`!Ty{cKK;jcf*!I5JId-ET>-CzORURzUf1M z44MMf&wPI-tuJ`&mTvwFo6qXWTk-~ku&~65qv2QZu^*nu*A9MpdA|9}gbq0Th)3(Q zdgR4xZU?8Nb~`H;D$8pR4!gG$JC51D8VdKVH28FQuYLY0?U4SPVZ7Wiat%{s#i z3x2z`nD!<$QW9tPPzz?3`&@6cpf9ISk4)NJTfUL1np%FsZ}5hsdUbpKmgyFgZ?m`x ziCX#IiQL7IrUKVcr){Y_e|+g98Nv{;v5Gm{H+1f#W=t!`dkKWi7g}b2k5<=h4cvLX z{A|iSo8{$0#S-l4I%nyD)A9C4mp0lw_WE?r;GjNkR^rrK?0uA1xKdYIQIQsEXiBUi-SY1K2XE!Y58m37Y$(kPefnf} zQ+H{tcYJ!4XB{EWDPw)c$i2P`f~m4r-cLmB?8frL6^xQ4yBzTP;~|^uMd-~AU82fE z;X4n!6{ccPX+Et2>1qUAQroi2!IhIQpSczfH7btw*S6%<)WNDwmV|+v2A{EpMy9Km z$J*BCmAz~_7_KdUaQVjPQwML~+aUC#J~wGc`g{5Qr%eE=qNz-8M3>!acpinvzcos* zHZ_%i^x;ou6^LHYuAtE?r)=*ia1YM8(p4YnaE9wMw(o;{&G@?gY24nn#q(b?7fHAq zBeHMcBJ@ycy)>C_jqdwANs&rT!o6tX#-Eo`3BhI}r>ds1*lyP|i?``Uzv_uHHkP;^ zVv;E8z2oqc(7}?!!aIHL?&=(*4eEH+PRLqlh52{gMQ<81&BL%_6pf}FYR+`TJbZs- zsG&y8NM!V2V|L}7l07PnS3CItMC|GA2wgZZZkV!CGp#6*Wv-uX-BdAlIB7(y01RPD zti0G@Ia=%UsnT^Y8M_nQ*z!4`*6Ycb$@9k%Ps1#a@P(h%3|n|C!0SdJ`vM)VIR-8$lHY%$;8`-QyeAgf9JZs!lH7b~EWn;|(Dlx9x_eA{Cm`P@EiCm@V)QP+M>J%Y`Q+7D)>JuxR*f@uZsiJ(e zQhQAv2?1+Z(=6`Vxa~SrD4wXw1ZR{6b?=Yn%lG9Gqxh-mL8TgwnL>DZ$_>tHh3lJ! zlc)tJb8Vt?i2bMma_V$=o)y_>(rZBUu3_?9X#^eiMYCoeHo+q|bgD*EOM{r2%|n($ zLQ>G~a;hIeFGXQ>`U;7vbtKM>oitjNVh!G$Vj~rqiPej~7`4$JK0-i&BiMXa93t9- zoF&{3?&9LEST??!>=YFvL#p!DcRb{ciBK|}No9;0El+kUg#;;LzaH7lxN*(8GMjTp zaT|`H@K~KZB3NRpR9DyP4!(V2L06}?~*qN<^-rZUY@FghlBo+9Y}>!xE!QX-|c z;*Db`FLBd#sdQo8b_dGA8r) zQ=V!8WKf&8h%1{!CTVxWQmV0=!{PICV6Z`GBlF^GAcin;4D=%YxWW|IOLmewuW*+q z#wY&5uIWYNNHFSh@4?;iaVqg+9E*=VuCN(%G_(zQ_S*DDbXiVHgc$E5_R`L=tkngj zDq2Ij?&$K43yx3~9~mEw41>aqtD!PGi7M6g{kJtLw|5Va(YlKKm zx-(vl{CrRUUwN=59x-T5MTBeO46W#FA*!8e#G6#K+U&Vw#PK#3F{uvuTcc8nkiNaY$I$yC?FFB~a$J1y!4tX0 zS=&s%r_QPvjZqb&Aa!VehG-yPrsp)=)OSmhz|q0hpL!sZ-lmpZ<+&(pW#2BmI3w=N zi_;6EV7pkeRpARn%0EcYj!9n*%!E{WLa5pb)u=w>D2D`%S7bI~j@(pu`c=kt(*|;Z z81_x4Irbjb6>*u3#xHT6BCuV)7X+168<&PSV@OPpEos_mxj15INvtN0SxV0PnO;c$xbotW${UbR(pRM`?f<6um!{=^kTdOgF*L^NXI~U@Bw7WLexLc2u znfz@uJsrH{dfhK0bdF_L&H>xV&z#k`=6km-@Wh}1j7>oj#J4R};cT5HtbWXy5Krf! zD2iWRiGKS^=R;cra_E2;z1RlWX_1g?lhat$UN2Eq)*n zE6}-y%01^FQt^;nJ&IiAl09Vu|>-Lceyh50w z{vF|svt>i4L;5NKMnIVNoDC)8M~j)7+J$qCeaa>Z#e{MQKApJ+LQVp`$woSkxq24 zI?_~WZ^9#DGKw0SEH$H}{SYOsKo*<}oax$)-$Hkxc&}0NpY2Zrlo>Pj>Cp)(oMfAn zPU$P8Z;+XV(D$_`1S|cD0(B-l!USRecBPnD986BXEciHTo~sJ#0~AEixKsi z#J;$pqLU>M>78i|A#v&*5B$dFgl5x%>@+(yhB;(yQo&}f#$>>cmXz+;z)eU_aZ#Bz zRRVOC^0w@{MbU13=m#HePP63O?ByJefQ?@yZN~tWx-H*)p!Hx^jb>H6Ua6Q>bwUn=Ao^!>-NgboRvpR44(L$ba$X5BU~pcRwb- zCMV+Xu2t=%4kc6DcI;u(i!~SS8J)`=%nZkZMx=qSQM8JcgKERW`U+!!xZ{M>)JDQ5F*>I)K?W3@iLW?uV6{$Q zr@X0>xniM*Ne)+h5vK-w-fnID)-8&Ynt4)lQ%p6%{50#yelO-wEdTQ@qJW;3`Ak&g z`(&_ZdJ0R~6C@#q2(~p=4__H@LH*RmkNa?KOzz9lscO~91q{iQu!S`z+tU+*+lIZw z(5uViSh}pWB`a;8_n?c+f;00c^Oe{~g-awu_oE6**c5YoGe0}&W92fb+`PuC$l49m zxQdpt?(O6BCMfqKj6h>$KRoX$A`ozbW1GwunmH$1(F#vOHC@L{1_s@=lV-M*@A;;c z4uzm2F^m^ul)XS5rei5u8=P`o0y+K0xjwu4856LN2Glb2)ZR4rohxC|?`ukzhq-$T z&TwvWlS_A{wg5L_68Vg$1<|Fe?A6<(9S`8s>qeU9V8&fA@kzEVTV5K+RbmW>o6p7^ z9y*eetszsfK}9u$pr*UCV~Xi+yOtGqT;x(cXl*IbY^W>ESj(dT#{YD2Z#WOXY^%ZL53zGhIcl7vKn7HjJny7CObodq>EZ-bW;E!(KcO$0c+u zp}2)Am6C~U?wDvSYYmtH`-iw2{#7Jeq`L*jUEo&l18OCo541yoFLNUfa;%$kg(x zvK0WGC39YDkGPi3oU><(N_gPrHhyyNkwN-+NaD8e%Lvg^E1n`-uepu;)hg$KIep`j z4E9Uo`UP7U%-X=hF2jq1GbKn-FYz~LS5|l1&Lb^KoGnRKCn!_Nu5UZ&9-lV^u2p`N zGPaC;-4|zFafN%5w;fHqA%3_B;&P}fOU;fS#naDow-c;Af;*2Kl-^DXWvrMl))HzO zOHt=uV`b*_p?tA%xw6B75grczcv^_EhO=e2EHT^qo-aHB-ob4_Mgi>*$AgWMgD!K^ z=9#~(cQlxBSq!sj&9J4Ab8^Hj<8#3L8xktV8mR~;99r#MyJNnrXbG5l>u}wC%ATWU z7D7)uanAbVIi_>7Gcf7^vIuwRZe~!9W+pwSdA`dFPsQRR&`G=gksp|NOMnbb2pWCm zJ(KbQf+`JU9Y(3$WuW%xxpJt2yf#Bz)aw1#*ViO=5J-wpb=!L%o64OpUtDrWTQM&0 zj*#Hp1KLA-SdYd^loI*Y45Q(}bB$xSqFvM^yKn@5z2fEe=Uo<2K*>s991B^#j;Z4+ zXIgYN>D~SNm_hc_TF%J<{o2IIqoD7(gWb`BASgvPwGyy)pS$HDd?zoA9{lb~I&bPF zgBPRJVct0m2)vL+-IFF*AL-_IYB2k-RXp4nHhP&fmxdk3x`AL;dXe3AIL+A*4}5kH znQL%js%!WC$^%WEaWc9++1{9EPds>!g;i#=mNoJ4Z>Lzj-AXMtZ-cnKyoaM}g)1BP zGg=&v{HuM+0@>*;a>QI|7b}({MrbIk9u{|eE3U`<#P*9I&cPgKzXEe*6(vK?d~w^f z#*H=}4SeVW)9(i(-b~(3R)Zz;@yiv`B66qhgaQFhf;8W9tK7h85U7`KKftC@2UnjT zMvese{QNWf%rvHF+GA5hC-``FHRQtgqlIp@)5xdP5CvZX$^bfzct()Pv-{e8Xxlyn z?+&Dx;C@L_D|0Xa8r=TG(mQ*{TH^?qZgtg5yFpEBj!9NatM9B-G7(Fl?9~s}Xld7G zcEZG!$5{f;%h;Xqd~6io7A3)MerJ@)o3bwIK^J}kQY49F9=a^_H5 z`Ji7WdjpWAoJPhR0-QoYY2=;|+;GLPnea78bv-(syE>D$TWbGufpbP*PdNbZ`>nRZ zexp_ga#FH#j1X+eIG||X$&{AQ%!i2u|A|E^B5q#Q98{BemogOsg=In;6Z?A^ z2FjVF0~&+MN!wl%QR;W#7A-BEzdqj}G-I6uXspE751}!jCiAXT;!mL(kd@0gqd^;K z%SKgX`o(e47s+uP>+VdB%Ve|IsRy_-oE%J2e|xcc?SIm4m!7H+^dd3yezV7ew9>!5 zDy9SiEu-4ukncU_ytuZ1d~5uCOTEz9RX|()?XCE2{6n;SeZ5~Bzr4mDfvCSlalS;zpP&1` zik3f@_-pC^T(tao{J-z^FHq#q)B7(r|4TyWUqp+Vx}2)0`hP82{ymDr^7kIlKSc}O z*N6V`IR3YyZD zBY*GxEpKtGvm^mL`(t1W&&>hGo@DnJY23gS*4&Phq zk$G@f7sp=Bio0x`AMgq25J_=yTc4S5iuQiF+p-Wy#n3&hJ(`uu0sIKO1}G?SeNS&? zAkpQ>V7ug0gnRq@L=bj%=z_V}`)<#E+O`qMQxFGm0v^3*eqF!8PWUu+ePBr-wm>!< zeztug(hIZcw*N5wb~WPXE1)O(SVjn6zy$-_u8wP3UIIMl5{UN+aQX(g1^&^(#asR3 zsmk8R_>&w&A?W=2{3*!W#2zDrm(|;+Vs~z*SAByP|G{HL&J2b=xZA(I{G)HUAU(&)Pe=; zuYKMgJ*6XZ79r?{SL3q^XIt<$)S?-9L9lI&Lo1cYBd0>$y~!=|4U7~F$d8b3hc^Wi z;Q-bN>7`+a3s+_7^2by1t2rD`ImHO-ku4L{82kl5+Y3PT1%k{s1pf!$XT8twPfefz z{(cX(q+q7t4gmy!J}O=IeOO&7)0VXaENL=)%ulHxAA%NfK@-UA*Q!E^5&KxeR z&iR~A+QVBsB&QElAo|m#x_dDNSf4Ma5Ap^SA=pptj_yyJl~3vCuBDH%kx&1t&-M5; zaG-0g`G-Po8Dbo=6t&^N8O1d9tolpZ)9`t4V*?t`Kv%{c`j%1nOqI6-Jm4^Cc(Huz~@7^6&3bkt1$uzY<(t8Y3 zZ)4SILj!5GYao;95(&E=!3;I4AbF&cYSk7fL(gr9^?Oa?|8%$wX~~3r?oA`q&z8h&`X3jSWPF!1{kd!3#^O%O`{|Js z9{hi4`Ju%BX(t1nua^~hC0_M*H}_hm<`w<;i0VLj*q@d;d`5uA4SGce3@#BvtR1o1 zvu&TLrjsT{26rL^tgHn2oO}gu0B(lZE63dfZqCA5TgC%H2kVUL*uk)G6-f;RgCy*O zH#%+Yx?+=3*~pL0FfLvzMB7LtE?Muohm3~bv+P`8jZ&W#HZj-}#=YTUx*y7qRx8|K z75t%Vir5&!BYN)c^@}>k<)!;NwqwwsG~UD}w$=9g0eI;Xv*pS7r9w96n2)#OhKp2> zA}PQ0eIDcU5H)G-L*1%SU@!Cao#~Sub@2AwY3`L6mXHmZIOiog{NFx4qLq{ntf zSJ$0ZDkjJEY(YY31Z_mM;_JciG3;Pp}=}wti24*L!++ww~?y6&>ob-W~a3!k# z>zU!XjglX(gdEr57X?@G26-fx3@(l0jd3AS3gW;_thhzRuuUnGxj>VQ25xLbwanR6 zwQ0|b+Fc4aja}Chj?@FY3=j%M9S_}#TUShFA6SoWEuFOq^Eexan*-awvU!Yd?q$br zK$^F$CkRh2H!=}u_0(+8^%+^}>=Yqm&I^6WY+q(i51DmP^jR898KmF{3`NvAbSrZd z4;FAz`JU=b8ze6ai->*ZMzq=G| z(>`m(Bq+gCBcVY>0p^8Uw_m^Vbavh?N67_E3mTa72X6852tUgr#*jJtz#FhXuimeS zps~oVZ&9Hkf)+HnS%k^zZo6`pR|PfbTq_S}j$5_eRwxIWN2)2E8P^qkT@Dl2sk;}(22Yg7ao%vHh;`>xTd{(;g_COY<=e){9TImZu zgfdV{A={j|`@I<3CUpGu#^(44-h>XRXMfkS4$39m^LwDqgMKV1A8V&MHzkrMlhL zVa)gCAWV<%nfOgl(q41Uo>*RI63oPYY&S6sg|FBd3VJGw`!_;pgJsN-FLZeeZDC3|{cieNRA3I7K#7(3)w*SM0T6xW;oc zvPVZ0nwV}LHonQR;D7iHvr$!d&|fgYVw9pqP}7bggmEfC13Qci*xH%o-gb8wI4^{! zCX=1TfQtGQ@Bq>`A@xmry5)XddJPS)v$>&dFg_vol(S2aF*l7JUj@PU!V0 z1}=N4ly2Oa_vg7_(9(wd_jY7d)MtSCsNJ~;rZrx4q}y&5y*}6H(o-c#0W(g_OR!rb zG6l|fJBvw8V^c>r==R|1p1Zb~)Zjl3 zQ!MTXuKJEy3x_fsu}N~iv3fOu0LPW}w@%@+Td=$JbP8|%YsO~_izAVM_I5xL0o&TE zuZIHe$6{`j%1f7;=;B#B-rmw1A~2QVmDIz10kHuWAxxQ+yB@!~9|GwYg4M~jrI*`` zPbX7rJa9X_knAzU&ysOiYbOP>kK5Zx^M%j$&16ZQjEB>VvD(#*t?`xyE@{r8psAKF zc`~0$NdTIAl#;Rn=Dg@+DShChvak#gHP-BnAU;ysd=_l=SG>Bdb)0YsnClA@SWXAq z^Kp%wmR5vX)!&Qd8ZhusYK)I?FXe3@Yzw7ZU&8=8p#z7U8e@WAs?_}tbJ^F#9W(fI zZd1nf`bqb(=3;%L@QsaaRg5cNgQ*6r-oa4@Ft~2kan#N*>xK`h>Pvd%R$D^l? z9)P|SZI!`Rr<1MtfLU6)CU6V)fXGl?V;{6;hD?syf>l?2tTb^o_FRMEyg0?$N(L*~ zs@sTO$)zMoJ91=LrTpp6|0}<)(51-4`!$sUUD4u4XEj86#{4`Q62aPL^2$5Ej9N8C z#al7Qaehx$Cj={}-`*(JX5E~96cC3QZlTe2@6rs%_wSg(Ei-m@M2fBPrYvM?AReCa zosy%W=0bNco0${hL0U6Osm}zQ6ifdXJ3D3ND9Nsx{!HxhULc8~j*)_Dc5C zJn|?`P2Se7!3ZzH2R;_DNO&&jv|>F<3|hniZX`@jcV0%6i+JZu#j+^Jn^B!8#PX%8 z*;;Pf81GWI4K!wFss4RI6=o1NKn;+$Bz9>cC$RMo>u`%y;Fq2Z@{$jek5e52HN`Dn zyP2C#ax+Q4e}}7ZR3X1z5b}`Kh%xO^k+RKVqoAe-p@|fD;-%;eP%Jq~X-${0-Q@8T zFR5{jpjm8uq*c44wGR(peTzN#8E=Ar#cfS!E4Rz$kX#e62Ft#&nyU8Wifd1BTkJ^- z+io}+5>_n{fPkO!bQMOblkxJt{^gZSW@Q?qHIZfsRH=@&nfYkDDWvGSL}s{WUd@8_ zB{?IY;sVoHjZW;NVyOxENx!H>@eobUY#1;t*nBgG<{~n}r`|cq5V5x+ihfnxovr$o zp2vge^FvbB(5pR2(?MuyV(%3Jx+gHM$nx?Y7(nJbzK~{FXbxE!`~i6jQRK$&%#u*r z%@7Y9!?RR0T{3pGDpr&!J(KjCAW{3GAI-mzs!Q{oTG8IelJ;P4I|evQx5&~vT4HKN zB#U0$S;zE4mBwG;SZypb#D-hYkK?BqjJ{VY&`+hWr;$u3*SIKIZ(nsklLs#KA zd2?&E7o&22(?aT^QqWD(z3v&49+(|0rIKGPu@0?g3m}0a>9S=%(^cYnD`X-36j0Aq ziRfQTmA>!D)y9XXE`KVoiR2rxF1jp;|N2F-p&QupDpN)_T4M278h3L;C!0$Rud+4QjV3E^d%{>l?5!Le3;NSQU9ZFpk}rG*9>k0<2KzEsWBCD4c3 zkRL~n;h-R+d;KeJ)bhXNftBEPYXis}>~G5(I%^_nMdk}kBIqcUkW~OCJE$$$%{n|n z8&EG!$lXAj(lAP}(wAleiFKX7V!8VjOy2A@H|EsN-ADL&4;At%$RVgnhSx(ycJf*GMLG^(U0>7r;F z8d`WJ6=~`Hw3N-KV~8b236sJ%Rp#HTC) z#0BOe!QJJYn^(WO`|Uj0A6Ffi(0yNh|eNX0{W7 zMq4!)H^s22%IzZi2)Af>Fc+BspMLQkyeXF{VI9Kfhc zylW-aZVSQJ|4f0u&6kHg&7@;Yd>(-Bh>!eJNW^Gndqyn7N~mk7j*p#1kf zDmyTN=$zNw$0dW8>L#Vi7|}cWECMctUlUZuz;L-?Iuv$MR4YD)bBo5luK=vmj~A$e zlpN;~!w;qScDIuizSF%J))o#PV)qkEfl<*!k=7LuG~2!(lS~AL`NwX7l;j>x!8wL) z%#gCF+N)QSXH}%v8(4NG9$)xxd&NbTuNmLIE<)su2RY{jk8yNRn`18Q8R-LTs2y-X zSxVT3FFlz*Zh2g8b8`K*r-{Z%K`t(@vl&IW^|8V4h-sTc=l}@ehz;}Acp|{oHE4!M zrNKFd3Pj@OT2S?o-z+v^gD+6_dS$Ot_^=P=IV*aEPKmi@E z3o0H{w|3LjLQlkA5Xt`c=k=o}QDqb!++yz94h%C^M$_wNOcHc=k0IZ$G6sSIDLrxYRGE0oKNm7M7RV z4L;vI)R(v_5~(`b4@<)0Y%I?`c0P&>gGm3hHK4f`O6M`5ypKdb8b09nVd9)EMv+Yc zJj~Y)f}e2+fCu!eob6Uezc2E0kl~QcD^Y~NiqxO$l@*SaScC_C|W;7Q0Im6d5T6i@MDF3qJdh69~fWm4c& z71U9IM(mQ5I)h#Ov$m~Y2-cflazS97Z zouFu_*qt+$^&J0ElT*uwgyi$ocmqB>nc#YY-&)>2t_`WgC@3q6b>HX-Snp!{o+5J1 z7c%H>bwq0x>2Mc9d=%DOUjL@qa<(7VJinU6F{xDYwBE7Gs{wFtjE9W>vb83L6Zvyz zI7Kq{?ST5#A`wh7(rY9pNoBe=wsH1{DGINGdIn#_+(`!sxe|eN_`!0Ynr9LU zrBV7YI>v^b_$M}IIGo7my=rE2UvQJME>YzS1iNf_liNL~!(x4_SgI)MgjMTcqt^l| z()$4Qs@E59acdljdfE~d?k$Q6JNR|LxU4M=%G1(;-Qt|#AJ-2j!;}@hF;Yrn4pq<0 zC|>RpV4zaFIr@kKZkWG8runA2b%-yM_q)m|kK`>@` zEmilzF&en*bhwmlw&>ONK)8$uS)d;VgUyc*B1SVLr-Dp5U4`+ zjvEM75igb0rkKWh7dmhzy^zF#!C9}J`shk#*abdR#7(HA%w@Pz*5=xFom{xvaRrq| zed9vRRj@;NtXfXfJDtq9gcp%OoTIgYN8-&N2NI(`K#~0lBHw}?rcQiGsp?h4yay!#5DHY^#<{WU-k+=lVdSi99E9a0|t|Ldpa+P0^PHwH#x7< zOUw^qw6Ta%^&B%9nRGR6I;Z1bG>0`0<)Jh1jn?AQf`PrY;^{l9oeR9vWmCubKVO~! zGFKa>B{!ZdK(Oc9%){Ajh-o-}x$k18v7+>I=EaTn`W8!ANB*qOmo8}C4mEt?6i_j)&cZH@GsyT2!xp9eMAwc+7fLS|xvh-&)DwSLEdDRhu!jVeG-mv`Nx zin%2`+yaqtm9MB1O54BABJCTFwUfQbgZF{In=!raY_081yJw}#smKHb0Nh@&8fs5* z-mK4PaenX>>^GtB5>1u{Sp=*XEu_y<&(0sPG|Lg;_9Szma^bR_UfkL!i7_5l+0fk> zYANniTy9jQsA>Le)^Ru2me02Gay|8z=&9=O*rPurGJay3n-js-p$%+F(oObWbg(H? z8O(U0SgfS;JYHFg3ecA>%dOXUU_H`K-c#(-HF?M956yDY=uPmHr=Vq<9*s-ku_rek zqwrE%BB*Mi#Zb4?^+(}uJBOnTF;i) zk%kOJ=?!gi5SDtmvRi;Lr{PG+ZWIr1*q8fhmkuylPQuT3_9TMbcu_O! z?Ipxy!^33Ge)VqVrKeXM-zygVqeC#`asV7QBA3RYaaXHfI3KVJU&&!r?M9)=Tl&i2 zL>=iHeU=;I z0m~E9xS_dJSK~z3U0MmX+gk-da9$SX>4zBHBL!6whz7VqtHUt;={4D8#hDsnV)e?1 zVpEO?j&abzcfo7zV=F3my!@HCrOXgkqEtNLmr1k-Pk9~AhFm$xa^m>z#neZEguayM z7;SK&G8!^sN#%8W3+teSkOmef9k6REpxK>mkM6DzQX{ zz2EYTtTkob0#~}}dhWGP;<%r9aTm3Uy~P)rBI-2ySD1RdMN!2IX!Xl1a3qVL?N8H$ z=hY;aB5R83k*Tj{6{!x#Pw3#V{Ti*-ESi6P72A`#yf`bvLOd&kqf$+FUyIkt*MGQS zq+^U!jm~rNEofo9c?l|Wg!=OH4Xb*Ppq&hjH-vaCuKZdA%LY;p z;pv3+M1brvHjc0NqE@D_1{pW_1Gie5_kLP*L-E<_>|8G{ajFjju5+NWv8hOMrHPwfOr za~|UnBt%u2u(j2|ey1;09=RHFPZY0kYKy}B4JB52)d69MEPLIws z|5Ji!q{FXYgz*D!QwvO8%xji%4^}xpI#J9uTYq4dNAsq9bV53?&i5uEJ&Sf5GlCeA z9?H;Fnr}f9_}m@|7`3izAr~=YcX~}UVzPx9&%AUBzs4xMrf1|qu@P=+WkN_lSngTu zShB$+J+?*fCfEJ`1%|ijG6N2(eLDoWRj;TExao))^=sOm+*zRxPba@5nleyOp&(6z zfZ)wIs|b+C{+z_v%ND@IM9M-S_kxFK2xI4|J<;H_S~-mMs=KX|Fns&N*g?ud<4PoL zC!dwXKUQe1Wc5i%d9Vc`X+ts%ZV+zMMr-jEr8JY?aDHXn%rsRIG5fpBK8=FdwO}NK z4pm7$kcpXL-eQw2GP$f$?Zt0)`c;mmS#WYCY-d9FBDgMiu{&o93pKYLpF|QP;Nb^d zsPK&kXxXxqclg{U_OAgn|Jx*?Ki}#9@mgEs|A!r$KRD5!Wt@K)AeO(J(|=)r=)bBz zw5%jaMC&)1Bm|BU~z{xvo1FaGoA!Tvg<|A_xc(%8_r z{LjFjuYq%aemVbyKmT?o{%7FNp9}qUmH#dLq5GQ6_jla$2mJZxUthDZ=)QtU|0npP z;-onHz6u7?FBU8cPguy@D$&~3M+X+Mq}%TcDH`tR5-jc?LMlp7l92ObMl@8kseQJ{ zd-Brj({*xUx%1IW$Hj|#@t(^BtNcayEeuZ7BZVAkdmJ7OKn(Em_}h1~RDV8R1W{E0 z5F{X5+i!gx>$;zU09e0h6VS0jV_x9LxKLnrBJarr+G}L734w}_cfmlQfqV4jb?wLy zAV@)g#6Ca;^9p=N5OkV>WkmprLj}9r0k9rpa6U+zpa(%?83*4B z7zCvM3LME;@8^y~+mEYz&I91t%@Yj{fd=sTx0ub=SIh>16y^8=kngDvQJPnz855?s zsFZ5!{BRsR;0KmBAfC0hs3mtlI*^P1k^x{>a4>)pDy%QUS(K0U5vWiPS9=FY`<8WA zBg&f+n8y@3M6idbi_5p(Rp=`VZX;GGuz{@huB}a#voPK~#1tP(5!T+G>qT-nZk(UQ z5Y0vKk^;{%C<~xZgNGouU|~Obe?Z23LpXp8c;?Fl^N+7@Ih`$Uyx>>3_(6bNMv z-W0Sq4gm(_{pIc5(0@M_7-{?96aMW{2>Pqfd`wYx`_XZ?sYgUqfQ!&a0|#~k9s1K( zH>QA4Ruc#W=FNr33*v23?1MlBcsm3{{DY46q2L2!nX>8m_)6K# zjBj@tU~C|if2C}st$lEb)+A6bz#XQ7Jp?j8e~jaL$Lqv7_!H>9h|)Y&EyBd$!T`Oi zz?3l*Q#>f`1R5P&BEV4O`!p%?@y5b+-4PR#g9YTsBKQD2taEq))pUj;JwP9E|GuYg1v^6{0hA)5ma>-{b%MFvIE}7V_~DbNH~ZTML6a zYUwPOu98Nwvlh{0{98-(Yf9oFW#k9G$QxzpsWjz7sDg?}Gp;=CV1%WD;W(?1@v-=r zyo_uI>x(4a9yZG!c7}T->ky7DlcSF~pMyM)66#`v>Aclxu}4~&SGe5F-D+I!BgL!t zuF|ysswSw!1*G0+&BFBe)@4g|daEK|Lr1D-{!QIaN+Ea!6UmTYHb*D$527|usK?&I zMzN<2zv>1G&_QK9n`+7E9?TV?G~hZCXFsYO3d7&;1RY?qT`(>YiLMP=mD;#7Ezk*O zDQYtUL)BU*?&$h{cHcJ;L}b{sFT&&1ES$CfL@#&F}!JLyd9 zCt5kS*ho7g8{{G_a45(?WY%?%h^nf-OE$DTe3u!Yml|U0$k)=ZQ72E>&i$S2x~?1O z5Cf%m6;V~Vjq>R1$kocovTB&G%XqZKn>%DKS3jFLhG+b21yO}$n+K&AX8eSRL$#`N z)fu0#Q>{d3C=eK9gZfG8wv&`D+wSv}djk8=`+M6BMUx4i8a9I5c zPUQE!(nhj#CM}CM5>0O9joLY7LFEscx;Ct-;5P6N-Xd}HR8guu(8yt2!11n3fY_=(=L3-OWOu_47TUy5Nt4L?? z3KE-XuZ^~OQUWNi#QNR#>5r#@Cp&s<23qK2;X(nU;I}IvI#w4iVy~-o!rO-YO7s~( z%6xgWBRz2}VfSDaF$8CUXoJ%!S^70*IY=oJzM#Nh1EaaZH?*^;qWk(P^?X{0Pd9<@z6Y$Dpp_XzoPr5}-O6+tC3i zfW{oK(ut<><0{+OEl2%hQkJ7;7OQC_L!p5qO?Zb)3QsyT0J8;ne$CM%-&gPXERwfx zsMuo+DSbN-%;xyv*~@KDVQR9nL*ePXWz9*L2FWv5B6k=yG1_33f|TteQn#n{G44{T zNUev6zsZ0bOw9Qpy`QCEAa(vpii(NlMH1bfhc2SNgUaU zu~NLD#O&a*Pg|r((yslfZf6=NPc@I3Is#)#kwPAFd2sB7$#}9HM|PMXHgQ{!GCAoO z%GBegWCj}&poRRY;k~P2(!@LPQPUWyw0`mb_=N)zsjRf@9QA; zq~%W?ou zVROy(R`*B4X686PxUBG6Ix%sdU-3n{_LkXBU|o!oKU+j5VLE;@)d)~nc?A)qgs z=s+?uq|=^{&MXV$I9GP!j%lnTU5lB-D>+Ty@Z{Cty z%d@cL2P|XUCf>?Vt5DKTEnlW=7!34rIfT(BO^sS9v~>?H!7R0D_U+`AL^xBgF>Hnz z^qzHH-^x&!m)Wa!T=p|(+g55hs|lPp>f}jhqRIX+NOE~Hj4TP)h4(3U|3O~+rh9gF z8$!*q1!f{;4aQGm*DgQtEh;V37)@*WItSiI!P9ob*__#R=wM2Bhl;%=U$qutsJ;x{ z#%qp?w{`j{N2_pJiq?4c2kU&tJir!TrrjkZpeH<3?D-{rG|WadAPrwb3yK*V2p_uG zX3-kA95sI?geh6v0m(xFI(XNz+}d=Tj9dvfAAn`Up?~Sf z9y4MxJ1jitsh(71Q;_F2pvuT~6a9pgti(Wq9qVY_hO7Dd!t-kRESu@3$YKvLVMuJYA2UX` z@1_+ri6GhEGO!gjvb~coPA@Swu+?9#m;8}S*|J{Bf#TI@r;uX_8JW|#Qi{t6X=4;T zI8&~LeRO2LVPy*6c*j24Ia7JGbR=}`q%GC$owT{Rtp*LqlaVPYve%Kyh<#^ioP*;! zu2e{D4RG6Nd%wT)DA2l<7<5%^rdcA6;S;1afNebM5aL7|%M~XKK z37VOiz3Pn^09aFq+e#QX-0>t;XR9>60HNpo9os9P0H$H3pzp;As@H%AsWPCFV!?{4 zoWz~bbIKKiH_h|nsM*Q{ttVdCYUEBi;=oURl`5D|1ivh_4HpKL-e1DY@j|}b^sV`t zK1MX4zO3W@X82;ZzPl|IzRa=?)^((8!iiW=@GvKlPVEvhiTP^ek+XKuDLrahF@(|M z6g)65lSg=ld7`h&VG6uuzj-$y<)%yg`CDrq&LE>MA+?8p(xJ3BAcSSa)7u&2OIBlw zpqjb{A6It_Lf;L(j6chu4MovB;i(>txKB>!oOik_T=q)@)|zT{Xuit; z-_m)5m3B-5;##EJ7v6ABl5*1fo*n-^&#vGFFwOMM?jSO<45aRdwL$x{wD9tiwVK(s zd&#h;s-;C~tEGw7+{IZLgoNLIsYrR%9X~+ykwG9AMwLJv^99X-7YxA7I%(keaPxMp^`d! zCe7<>8;4Pf7A+_$+YFU#65gnb_mOC~_Y}9RVRCc^rGYKbJM`X*uq&uIEIRe6mJ^>; z@LR3`_K9Ro(N^lS*>(;2|JZx$u)30M?H6}yh} zq)ml=;o7$5^UZ^<#S$6mh_B0D_@Jb78CL!jU|vZ_MG5qw#M$pCs)Dwt5?epq`IuBx zf$Nx>HViCJjbtrBGU3o|bfsNK9h)DLD=W(Mk{jGzAD+~gu)A)TOL5ECZyi9jAQ;W+ z$FE@)&0ApeGSljyzEA~gxzJT@^ZyWl&gE*_RzFCg>lzY@IOHzIP`N}gQxpz?7aRs$fdqI>i730eGjv#$MW3#_Lb!Mn2s>|>Jj+==+8SC5U67|3o z-TbXXYUr`EvP^InpmViJm~N$Z$6J|ARgZP;V8 zYUt)cy1yba_Qj7n-=!FV-gDf<9W*=fU`I3V9CNdq(}xtXL+knAsAzIz*}o2JTinE3 zD88C`UEGVl0I|$8RHO-9vQK1&IDqBsBg7dpNJCBmI)0M{((AdL(by?UrD=_HoxK`*m zjK|%f?FK*y8!|sV|DFoouQW@{T=nC$OE>80p8b*y2RH5D{%07Ah!%$JmH7EdHER1L zNkB|yiU#(WdS#^6IJ073&iteF$-d;FUY6fG#|N|~9N!bPN!+os56kyS=%mY)K~55@ z%7uIKNVngrPpUGn4cb~v4R#R0vc=qD5!nH`P4EF-s!f0EKTy|T+cKW#IJjL_M&vi? zK1P2CJzQW`zfA2)Eg|W3f5BF1?$~3rJmBs`=e@yPvF$TyakB5_L$KO>?%l`XKSGoh zmgpB#Oxa2pD;>DIL2XXZ*t5goH~_2#QGCEOx^9!{(Uzx7R`C*g>?FS`VN>+^G$DyD-uv>$BTevRlWlu9 z;LUM}y96bAXna=jtf%8xoLtRULGJ`=dPAAJteq-}@3F_`Xq8Bxz`-=DoTjMq)9)=uxSjc=%uO4g7QhxCM?#Wgd;8` zA_`(~xhd4z;LGelJIROADz9GiFh$Jl*s~L_Y|9i^udGN3PV-04c67@% zlhlXSvbXa}G5g@udM)iHyKQA4frA4(7hJCMoYmSNmUAa!j>%VF4DlDXz<5XvN*#om z&0^hff0>$DiUISy8}AM(s#`omaQ8O-fE7N6jj|TFCy^L(;0CtI0Xy z%EAxBFMW?vY?DvUG`7$1*c+RfnmKFL$n*Wu_VVUCwvw<4b(SfIOdxNNcbQ5$c2AZ< z3{sYlgP1AhnYk~|=S+aJxL`%*jqWmo&4QWTW@l=~sNHBac((etuDHtvMFt}=rnbz+ zn?A8J7MW_2F8Mizu=m?e70P;*O+5A$zlWC&%ZNqg=S=iJiHQq%lLG#Vc%k`$GARpn zr@~4-cy~O-W2tBnyc``rhaDn#uN=|-pcEn4U9ZSf{N*@griF8VuWjG*e0W+-Gp`R% zHBdxq9wT3DE;=Xr*X-+*RI^zM*u!I>L1gAcPZo}nL-IEf<5<(O3>0`S4ZJ%+bH@3n z`5r0lB6?#Yd~_ECN3Pap*T$+fpmSWSrjV7{8sG>Ouua+&?R=vi81MPN;pMtuF@J2L zKUW9`OZrBtTTRssUYS#?m0?(GYXetS>73mwCoer%8Jm&H*tKvrG7tQE6d*Fko>tY8 zjJRc0Q5FJ!kPhx6_fDb!WBx18OIXE2K2+(DC~$sO54lmv_kkOvi1o)`?qlpw&$r|G zgL>CXnz6-dS0^A8G%ao3L42zCAaGov+^9V^jVF~FK7I3LcYaxJ+pdQe6@PA#)`+$F zG|%L5+G?ROwx(2}6gy-W$qAd8W!*H2Tbn$kuAi#Xt8}D6T|qi|S7Q}sVCF#OZq+u-Otqz5pqyU3kb%0ILmkH+xO&V+M^ei z#!2W*FLO%}#gV;ibEp<&6VAK5f(kg}+}R-)wv;Mp6@I%px~MWZpHI#*mC(S!a8YzA zy#D3QSYSDdT(^G?)@z@_672q1Ke22itoN*I@7S<=Wn1ka|J$zFXE>jFDV}hXhg=I0eOC4xpM=J;OipDV-c%W*YCd(6v^JDEu^6$JWbj%r4_YHlVeaa zD=CG@Z`@W=bCR;8s0ib-%8?y;F=|9dZ@V@wiBXR;Ea^me%^KF5?=wP z+eo^d8LE>-FjvU#(9^?HzbPE4IqZJa#uG^ArC?X_IS>gg-b zu{B7+WD07tGh>FQzh)cF?s;QQJFvqW$L(+y^V<5x-ve6qdA?z^`$k{(7b%M3SFbORUL&@0s5?lS+)2Zv-yBW z!b5=#j}i`%ermxa8Bw#nBY(&!W##FwJ0jqcO2`CX7Q@}4gP9ndwZ?kPAVhZ@Q(ErL zM?KJ{?+-dPJjX_G`-;>V<01VXgueBeZ!M@IHJ9XL>|oxM>@tqOLMv=^@gpv3tk#Xn z`n+Uu>zeAf*6 zSJ6@aIy4F(=?}G_|3oSXkobpM&`(t%Mn)nIR#t#~((kYZ=W{%X-%eJAz6at^B4SWc zd`ZOc9EXC4LCDC-!p`=`B`R|#CwoV31_mQ@0}ES66C-B_3nzDaBRd=F?=d@s?Hr6v z9Eh|4w|xGi^GsYAIRV}?a{?rb*jQMInAqvrSy%z*uq>>M^vvvBEJVy4^sL{n0KWbE z8Musvt<~=Zu(h>w`t@l3S3E@M`9nuQ4&R?c@i|Koz-haQE#M+E(@)6bm5H&1!OvJS zfMT(6vC%Vgu(1LnB5LQ$tQu zMlOJ46YK9M>pv10ni{gRa~ZQ3bFwjWahjNLnzFGnad9%6niv`}aRLg>`m=P;9LsYK z_Qs|Tu6k?$>8Zc`01y!4yKdBXEt+3buyYXs{{B4G|Mdd^mGd3Ud5+HVy|ESToD7^y z0AB@kG6rP_16#-ENBsWLkDenBsF7dW?N>JB4|YPt4$z4JorXco*2vD-!q$w4LCwNe z$kx%~j|)Gfa>M~*G{5(de+rWFOHN$Ro$t3k{YOrcCe|({P8LQ6bi#Jl#&U+%7O$NF z9sG}M{;PcdrQFc3`94?m@A>{i@FGrnCYJw5iVHA-{Q@p={p4nTuZVvOT&kA+F2#jT zut-eof)nmJ9eV5*_4ybc>(kOma8EEm5Nh!zc;FrMlf5I zhW+D&J#4qKw}?Gg*MS;!$p@AbSmYy9Tnu3^`l4k-k3GEo{(^ZbNL1xqpgtg!ms_D; zAmA2z$y&jxI7PfBP%mvhG%|krh>k|ft(&8Dkt&pr_VQ&wuuN_LMM9@Zu;NR7TGnXn zM3U&Y#dTzZQubJ-S$c%?QBHWa;gQn>VnrF+3WrcN{lbEJ6tf*wPx6 zkf4uu){NGEU>osWV2!Eyr}g4{1^?EFzaUJ2sp|Wv_yb|$`hAT4qRGYe8=&(?1S77W zyw86d!RUJyzh(XJBTV1#|1;UIz45tte{uP_fWKw($KcSA&=gYn>j*{wDY}0|n7DrG zSp9-9{mVh}eU4DGFm^I`B;xu$z5H6xKP^Fy06x+QFb4q!yNH3kq=|)@`EPH{4IGqA zoQS9xRKLeBvIT%l4h)VAPOf(UJ2(?yCHM!N>7Q!le+Os!p3!e@@Go&DEqt+|}#*b=PCUYpyZJQTJJo*#)b^$BL0rL;c~(UB9}(_r%D$5Bk9G(Bw5{ z=hXN3vB1FmgTMueh=F)op@(4+9v4CNIo{;*8b}6B32+WlQGNQlPzZ9`Qn*ioEJ$a^ ziSz;vEaIhqPk|s&HWC!sEg=aCIk>X_%||@H77#%?GLU^}eKqlx`y74QQQcYE#~sXm zlQDQO>1TN^1W1||Aq7THD3D^cis7cdad=TF$%w;@AVd%JIfv$H>1 zf#R7Win-}Rx|8B%Q>|Uz2^zh^&u-^IJB8HPK?ET@89M|H0RQM)aL;n5MuN5O%J^x8 zU~d~i=%y11Kr{j5xu%}wxy~-vL4}bJIzo|}D9^YF)BT zdZQSF0yipE@!FQbkBRjOR^$PZj5KB`KCZG;7Guhz0pKvdPk!=FMARer7@-%&+XA3}@ z82$ojx^bldg*q6MdO`-MbBThGf1|qbD0)(=8_SYfyK|Ip^8Yqk5Ync~h}M7ExK=Kh zOLYGg^X41f{VMUdqy=A-TVJTB4lBj;;BH@*rYDhGJ-AR@xT9;z9nJ0)-P!Z(h_+0a zPrgbc*}iW9@?1wJtNgDR`Y)BKNYdAQZODg};J0C&8U5VndA<>+3LISfMB?EE`{5Ar z8FW-&6A55}-7&0Qrq8$P>XB#84n7dM|q`fP~LJANTdH|Y*^o|tu z$2z&x=N9z#wZf*N@)PvX2?451g9YLyQSj3%q9KMrV|_Hai~Kk%g&On(8V&+(;M;9w z0ZsA*TshG1D~Oy;v`=`#J#)_Brqhv)d53t_fm3xYo>KvN*En3!dXM6`pOV4AMZ`#koDm%YtglM4(}h1{K%h}xPL z@w~EU+x*8yiB>6JA4H>WQl@7zk`GZRDx#_c$c>_~)hQ)$s$fPY;*;|;b6Tq4u#6;`x^ov~}ZZRcN*UZJCOU3LN)Fkcw zzT344Vo}vfMe=hFyEIv6o=K!So23k?Hp+{pHLff%gp)Zl{O%P>K{B2(doN`AmwZh9 zu<>lPLIVWQSK6myu0NH>sorOUtuR(!7m4v$h_sl6=YtlXHj{uFA6K^4*)vgS?Xl~3 zd#TMqoWhY$rO>PB&I-02#T!S3yIY1&w$#^9*9S3~G}AD9%a+~s#&?sy?-yzgL4fV)k3S?&sAP8W>~FA6Z90bZQZ8(u8T`DfyjKJugg5Z zif13ag^cp+iujZLTBpBo&Q`8@8f}=!u%as?fO#qdF$gdGhKxbJm?khKF5@yshSHWJ z^|3L)HJ()*9y9zJmPfg@M^*k>_MJglW!-TkwrgJ;CUK5%F>$Q$;dUQsFqh@m_%O#| zQJxzm9+DuA#OOzo?w)y`=;=t|#u|BYb*z|=pnV7xZlc(ZUOUmDc20-F7}Oye-eY6W zqFvS*1l(?ris|AV;=5h>*fuJs1Xab2A4WVj6;WKZ`QFmnQDBjldT6A)w#LL+Jn7sx zD|kTcY91(#gZZ=-nQ}zHwY@Q@tpaiVrAn$<#qJUWfIOM9EgLS(v4cnt=+cA*E$n-_ zx1!>wQHY&V&W2d?Kwin5&OZ{8@z^MGtRQ<1I?1 z->p16fl?;urwA<+{f0bsU&mT{)S7$%I}F7YAK5}LyP_VS-3_lY%}oEe1WeGBF>OtLyvvgd2iA-07e!gdD57r%7}@fpGAYaIY$D!|7#S z_KK>b3(ERCR(8syrV5tfeZ%q)sg+^m(~7s4r>viy;~#=>=vLo*fKiwT@>h4tvKXRg zC5?Nn;Muuv)G^y@4l8yI~jbz93O0-`VW@zJ^u0{WsV|7!*PS49Cb z)57{qe5XVvgPh7EIH=oP%l52B8zY@+q zLKr3CJ@v{^e9%JwWqxz>-QW?GGd-WVeckTgYYMFNh!Hh`Ul%3)sKreG)Q*}-NP;yMmQ|XCtF%j z`IrhIPR_6`EW=;p4T7&^CYbI@cK5&unGI%^YImyVh0m;z?L^>KM<^38yK5KUojg(p zHfjw6&{I7v$KwqY4c#!Jx0eN~y<4_Gcl@e%EsVa2tSrQ->!-XBJHVIcA4YJF~dx7;x6n& zt^S_j-f(FBh4mt*=O7$Ic`+<3kf%=v9oLuAbk-I2>ebGM8NLn` z=NC2MDD8S>LMi)M3`U8bH`bBW_`ZmC`85p8xflAI2SXBkJ|F3#-lsbS*>;&Xr$fWB zvGkC&v~9>@UbXM8wcSpYeS0^Zvz?i=dlO}NDh#wy;nOy)AVGL4zTAy5&E2FwTxcL4 zt`$f8F@ss>7MtICjDTcuQ5FZe&H(<&va-8GvYQ^NMe>0kIEzfL&b6zESUR0>F;4g3 zo2iJ4+G=8%wvk}PDo>WPC-FvZ2DdZ-dCEN%AvF%nU0=yaOUF@OGJX-FqN1C61`e4% z(6=bNdKe3|cHkjA9)ix3PwFZ5LcKI~Eq>IZU#@tDypw#}o6_@3SD!?sI6$^bMt;et zJILw9@WB_}jDUnLH6;v0hy(|3m<}6J8eAL!ixCK9hUL}hiA=h_tbA7&c`J`6v6iAn zZ+q9ZuhHhN2%hER@imyqZy(x6PegNKzLY(I0c5+{5B4{nF0LW&@4D8Cm2-2%R(8uQ z8vwFh0?)Et`BIrnOE!5VC7YWtFw5BSApqGfwgMHsmPh5wtvGkx%oQ~!&rgTvdeN_` zs`P53h(YayU$JV`USYgWW(s+aZ0u9k9$-ye!&W2LZbGwr^ssaVPk9necRNK-Ewevh zml{@X+8_>e`O5AP`$qsu$>6v*l!Whkv zvKXAB6Tg)Ikg|}odRn-JHAYGL=502Loen{b$p~TN3fK}Fv8|m&vsc4CFPk6bhC)x^ z3z4IVaK~jPpuwW#85bO5lU?2oz7xJR!iBz0Hj&n<0NdE?-FY0IDlTkX5cG#M* z?8I=cfGH(%tpZZ&XUUJ-$4+;E@CA=x3!A!$-WOH+TX;rxx8uE!kD-S=7jXbYy+C;n7B@C|T&_c&3|9Sh{rEUv0DswAQ?W|d!H%D`fm%$Y;m~CBXn4^c z*1sUU?}goEAaL}a;hW{JwsU5-bM)=&@r)iBUKJF>ZnRwVQ*)F&bBe|^Vg|fTeWMh$ zSHYibEz}=rKRye0ZO578=4y(^Z5f7F%|qTtd{IHfeirV+5}UMKK)|cEBy2*7bD*Cz z&E76`ST-v-w7a&-Y|$JznTHZ1=uwpdKu>gho^$tKtZ!?CcI_R+4l11X9+e|puMd^I z9?H0loHKCMLKnN5*Q=!lw@DB=rt&iHgr!zYl5$XcFvpztWqKj)+#K}*&=Vn+@zLeo z7$>Y0Fzw7Xa-cUV`TVJc)G(J3kJc(C!)$&kWpv!R?kj0db3u3wiSbg&cb{P??T1El z#*kf8D+4oQQ3w`!J~v)Tl`^9gPNjKQkh3esyp(WAFdf3AEW+mw&>-`u9dHB(Ku`Jb zIsoX&)iXdTI>gNgJiHR8b*3#4>Q$BLOveE*WJ=JNI>@aD5S5x-9qpu)S`A_viGf?; z%{O8_Gn!fI{4Bk*JDalO0Av|nj*ySazoPDO?07tsiW%CL!1 zE%v*j(i3K(q}su!>vT$i6wfsg@EGA)=$=kq@h?&3Z20)kdqeBO3nXhQ^jR7E27xzB zV(B17w5xThw34>Pmm@$qF}%*vTv&20YOPD=WfKyGkbt|Aa?q*vj~tF4XJ1y8xbf!+-irVsb*Zro^T zJm-l$<=0hP)+dXIMzh(QYqLcfC3{QELx)Dgi8mfjnTc({Isptd$FiOl0{EV@$S?S* z7Ikefn?g!JNS863?F|LmOxL7Wk7NVMDQqPSVhAA~6k;fq?|VYpI|@k+7qV9o)_lonFsz3GkU+vFlUqjP3vAy+&g%8K#P zvby3}$4qV4c{?;R6C0<2Q3h;KiNM&mIm!UfEPi)ocS^bNdcU3t&tO!b&Q@LrLDm|j zEd@_ivqNBe9i>z07*DZi<&aZ;%W~Da!zfXr8cuOiY!ok+5y-!%HXKr^4x5bDACoVr zupHtLx^MGVkqK{3s7{X_Y^WP)O?lvy^PWJtaAlB;`WbqncvFzoWVUYw8CY}p33%Ma zMqJ7zV&yheA9E5Qx3^EeokP zNEt@^W&+KTX(`2YY1_U}C_h%P#f|y>?)FU^ge;f75#byBUX8YSku2BbfPS{qmxra@ zCj?gi4g2|)A@=R+Ju(j}eRd7{SxKd2d#Mn@y~Zkr$^s*)N3f6AW(cfLe8y^$ z2sH5{sDtoIO@Ut~ax7`xRNBMUvu;<)=!(Z-xV;Au8$h>9}{b72nJ;}jE=r7Dj63iB8zxL$`m=81fW z!+N`06`w{k*ly*^Q%Ogu!E`P3+|ix2xq|BDM4-t|H3YNpP5Z?*m#>{szf_sWmCiAv#<0>@4$OXFVQIuGfJobu-lCpHz&Y&|zcTHdif=h|`2@fuBO>8e2Z+vpNaD|oT_hEQ4J-u)Y zFU=0gFZ-0~rkaowjjHO5zG9wlrab8ZOSuNCGpbY2&UG4L!P&gUPRK-T;oz^az+RxR zNs1$_5=>{Ay7ROEq}ZEfj66snBRXy z-7y>&zI59u_mirf_d5-o0Zrv`dRRA^6s$AZNn)BKi?i%yNDMw%A+49(0}}MEGdbu- ziuO42t(ES}YQ61T&$?YWWv#6K?Azy%@B}k0t7~8P}h9(-2-t+R@5K`Q=PSl<0ahFv+|fZ1MoeI~!;j z(LsLV=1bt|nY;kK6Q|#I_n+0TG=fcojEjZ;*n5kB>xMl-Vs9_G1|$B2isl-;nX1>D_xvIR3CU*V$7J-9^-fXVd``pHg%iFm;>y@)gs+S9-tI`Z zyy{QWIqT=gc-HOe5ibWNX$0tYT}A8QavxL80(85^aU|12rysuTj*m;Pn+?#SqJN$y zH{xwS&3pZL$dhXOaz3Fy;X=fT@s66A^ueV8LWV7@=`&}eQ|YZx3_!PQq$blOw>+gZ zJ7=uX7Sm;X3iegGU)RH`6cmE!h|^rG*&U8C`= zwqrECAOe+R>ADNNsvXkVpA2(+&kf^yL0y@ndB8C{LLB65G>Tld-%)RgiCVG1fMVMvd8GM*1O@*ZC>KuI0nd?OanD z6#Q#rZl&0D6_VFjVm7w`FTf_#$&6^IvyFqSB&ZVT=_pJ?QP)ZHn&4x zO*%3>T{nRxnk&YA+)(5lv-IR(FLcIe@7mnA-5kMH5AlpT$T}B=SqF3{l?ZGSM!$7=U1_(n2H|ytIgoFI(3%N)u;{_;}y*#KvFJV4o5*< zu+w#;Tcr4grp1h!zcJ3wp@Y6PG(#==%!Mnw(qj`@Vu32=2+-|n%Ut9+#Q$Jt-wn>v z@51Z#tlKqxHiwYlLPFj7{OIcC$&n-yWLj}DTGrFJcVp2rPWpU)jk!YFF~^&Qm=$gWu8iR#zgT& zVHCGNd^^^HQ$btUqfxhi_4xAe>ii)Plj?^Pg_;}s%aM@Ln8cULBh$r%;RG75D{t(x zs@A~u$9-BOq8)vS#C0z=wP*{OKHp~Vnc!bTkvq_D9%?9QXavWf>jDW8lTaIP4-h>dniDp+s${WxHcS zEnSDQAbaDJK1LX0E`*z{huP}&^Bud!MLJ1Ck48zE@~sk8N^%6rPMw9MQGeF#?lR|L ze+3TIBd#s`<<)(hHu4^^`SPpPOOpPi_|rHZHRK-YP6xL$A@ek>=9~stdpz zGnK>dbY2oS*&Y)uPRUGY7=-8ff#2V8gx<>q!;~yH@f8A0}yKm*g z5@h6!5c%8_7lAbGABkVVkKw{|WllAE*vFKuhz;;YZqd#n2CL06?Loh2j78L;3Ldpv zF8^rRi@Pn{M0obMpe9^YEw`H9q*~%J?WjjB$|4P`AllGkJ7i~wiXmxr+!kYD`+j1F|7gsu2 zHfZ7YR>9HrKHq2rW*|_E1qa-y3q@%tt8H#&Dk=N;H-*$+(sMk@NY5pvZ|)%EfL-ZM zl%T-Q7=3-mb2&)yV(7%+>jinWmpI7fYui$}k0m8$zic2Y=*fT)U2560MnU6i?$Q~<*>}ko;&F3U-_G?d zdhM3=D(;1#XIsPU$*K0eFdK`-gAa5~KBvy8@g47&8`E*x(opx-NJPGI{Wa!sn)jjb z;T$@9+GU*!oEG0elii!q)9VsRJ?F3ys0z@!tj5xJd2BIRpIZymmGhs%oSIU;j`!z! zl*w^y#WY|+k1x!b2`vQRWd*>twXZ&IHu<8cHZ2}qbv^}sDdG%6PO>+B-~4{^)<9pW z_s-jfUAy)mBZ^d_=me*r4?=a^Bpk1)JGj^+1;hb9B^Z;BkJlNf!1;Lk$+DGMNiwp) z#fwIxcqZt;Hs}5;E08->2r9x%_ds|4c(pJkL;cPc0pkFZS)X^sa)Gm4UjNkEi6ZsZ zu@X8i>ZH`(ICf1Cc_-_Xd!lQ8n$F!Wg14-VC5Ux>uN@uDv9CzZLsSi}>AA74*^)P= zhV$sSH?37$m;>q2IZ_yF1kuw0JA0zYwSCRh=QJ2r3mC?bqqI)gjk2ez@r9S9&dv~A zv`Z2(AxNasK_mvn@8HKhc!0pLnDDMRK(y9QmdZv=tmN_H@`ssBZQW#1FfN)aIQj@4 z@5GA4kH%Bc4x4Ihw55tY5LpO4UkZ+Nc>#Y-(y1kcxCxs z8dm1#y7?VkWCE~^zkn?NL>~T*0aE>dL!L22CdQxil)8o{(X*T!6Bht><78w72+J`( z!&?Ac?Gg`1-fJSn>6^ZK;Z8@^^Yg{2cVky z=k)ov9pV4^K>ib>_;-&@ZV}pP9D)<|l*8lOs``sVw_fhfp7|4HZQ2q2Q`|||-V=?~w zTG0P_yZ-$QDyF|Sr~umC&%gHmxm|yHlKgEa=>NQ3|9%G5_kD}srhmw5M^Ti)oA-?U*g~G+Fy6{0ItN}H=X_mJ9_L~j6e48m{^$Uxwu$40NbT3 z^c>H7e1Jjx@9pS)|4sP!KJ&L&mYDx!A^qOXzgIe7NAKtF_J5kjSpL7ZqxXMScmLJ? z`!~D$_eKQlvHq#Mb8xWy*rNV*0~^qw^z6)l#fjs2-~9UqHsBnAiS4<&v#`>0{m;7l zuiIL}fA(zVV5DaSn6gZOvjS$W=MnUm4vo+D{&%+|hqks6>Gco@(a(^Fz z|DJ2J?I$9*<#Tj`V+id)(4cC{dP?Vr`11XDZAZnp(uFObj(5_26e47dyYoK0)~FYP z_VngE@hd2j`jO!;u%-%wTweHm3`sW#r-hnV!8t-&^Qh^Jo}no>@P4{#wM~=^AGp1q zGk>oiBy9bW*zc`W!Y5=jU227bY*C#UC20Bn87^2G$uy>FKM{n&sW&=gcVc2e3o@1c zbvAJvh}Onn6$wkkLQFP~d#@~b$Jo9psP;`xJHH#6P_u&=famdcq*DG&h_a?-oiVml~=xsWL zRcX|eTHY}&%FEiD;t#>@%M`5~tG<)O2|53CnDwKAf19*^acyQ|{%MB(;oJP&l;7vo zKj||t{g@B{#=#%n%}mTcZJqztz4=Erzvch$`#1l{;b*#M!vyeT&+mSe@h`4_FXeCf z{Nds(DJh{Sq4t+54bQ>=|De+F?5+R327d8z{1@Ve#bC|w?Ct!&B-;R3_x@8%>DKb;OWc^sJ#Eu)7eBl-m0-sR7_D22LcSZ1iPiRHO3SR0t?Gj-&|Z!Grv_t zP`(9R20Ss6cok8Sr6$28 zK?T3`;LOYeR3s!Iu(Q{o{%vkAUfeNqa9-FU5=0QAZNZ3;h<@vg5fSRN{g^2a=RWBO`+W199^gjQNHXL#5!yI|phC`gskam;_l~jTeI}zm@~W)a=+r zX-}XMh#iUth>V2f(=87k(FT}UCy_pwAn1W9qH{|L6mU1Nha?o-{`O-fn)UjXt?g(o zFK=dM=8`_8nl0I&Y-|SV8nk0AFilJ!4Unk1E-Cu)4M4?!ZTCShl&63-N98{e8ea-PrTYPd8-M^V(g{anyLw^z zp{t$xP+)tTaN=&=NG?EH05|8)ipo1K=MEcSe#q<{q54goJGaNyyBZ0u- z%-ldHBX50WcVUD&)0efIbqr4Ql+GTc934NyK;d%b$DyKq*$%%h6>!IbfobM)x6ACP zm_zoz5Y7J9+rStUd044ny_De&hs22KS{(k6RfdbYvdYd=j4KEP1qK5O4lIueGzDuo zc3v9V-pF#%hfV~G-^#L1vAGY?*%EOl&olXLIKion5{fMV$H)ljJkhzaU zwm??J_;CK$u2;A$Q&)&yc_3)Ru=+4<$iz@U5102}M;^q@*d@W1?(Oc5BeoS(RVP%K z*Ke$LJCbtoh=T533+=gV&D-HwztnLmDA; z3Dl#73?a^5kW`zi9E0uUyrHII$;vXUjqCm-fXAxIq$arBVwW4d|r*f%#FK<800U z;f>E_x6wHw6v&C-H{e?!UYBov!axLlzJdloh!1vdc~S(Q1lC^BYyw_%##T@Cx`mb$ ziqlNK(RHQ`d<*pT)yS4-paQe=K!HYvbty)$eNvzn*%+v2C-bplxa>pKt;hk1;IUnX z$xDJW*+hN!;MNFtm+&+5oiv%<3ca4Xk@g~oA+Rc1t4DaY7{`njl1 zCQcL{iy<=BWKS$Yei=a3A%C)QEwDalxl|9$tWkB9C3oW|@hqpx$C)UbpHRK!R(b?3 z5B)N0qSGk;Rrit2pHyll-J~cvJ3+Gc1Z~8Lg6%YsrOj;&X1yo#X@x76zU3+uNG1AX zz4x{}2MTz#uBQ9bB6_f+dr%UW7y-}T_4tfb{sa&EGWq1nF>}vZTOAm)lhwi{;_;`~ zIXvh*h4fSB?*@7-uDovG@OKt=JIAkj>~Cn>mua1;)ebb7dZKHx>AXV`6N)l$Zo0KI zlaB+ntPR>xsU3)B?QYC1c~`dPFj&nU!?+OFdJFCzAOB?ss}72}D_KpL`YqB&VpuF% zF)WXX1IQ5Ri*2}i)L1-@b4Y9IHHV5X>7Szc{LaNaAqi<$Oh(JS`$TwV)aI^dLO-w+ zax#WuvWZ?OmT%pL$`-CuXe{x9cCM7~@%cO~mKLoT5DUN1DUxat;@K+(fn)7hjj^HB zVqJ$9ahmnnRoh3LO>e^e8!|J4MW@Z|I~7!-?@$>M(L|3=bw( zmNvM$;H?`88Cu1lqFi|(2`+z&wu;o@#+cljZ&YI1%bE9|NeApT6AJ{t^5SVzG#^#O z)7H2z=N8Avq0dKI(M(9w<7p|ykeWP-EZ1m=7i%QEAY3@H*i|^EJ#RYaP{%YulFk4^ zg~536S6e_Z8@(q%b$x)oaX9IsC0(ULWV@F{5928+?ARiFB5KzkpF$4TCPRFP*Fb1f zi*3|g_Rpe9mD)}~2ohud(sxx9l=ay3SjKd(U+KiKbroG#BWQxr!P+x`ut9FT_C|`A zCqjO-5wjFd?g96Rm%bd=!kafsV%-(E@UUJ1_Ys9N%!d7J!db(xiPy%;kIzyE$@Jn> zguM@FfJDm=cDwP7ak)sd2EE8#hAjck1|^2e8Sr|ba%Iz~ugBBUPG~S;uiCY#Bw(#T zWq*q!JrLjOBw_mH>BS1otk;%7`1OP&EmzuG#FueSnD*<*6zLp`tsbSaTZ#NTM0V_} z!N-0X_N8RH25DHyWVq~#N=-Nnm+!Sjgn~RNd%R!6Dkqps_A&*&=73@o>qMRZJWxV9 z(>kwU#}Sa{`q_^C)X(zjM5w$8D4)r8%%}8HYWSQJ7Pc zzG4GUGCP0GPZP@x_U6P_+8Y_`yo)ZzMmLP9`nuLU-}k2b#rnD}925WXNKN(!txAwl zoP6Aw+kgPax>_cV;QETmJsxQY!j}!*@9V11K{kwOC08So@&y4AoP5%+N6Uu!6zme= z318tDPkj;_ZXf@o25yo-p+ahi0M|z77I~B|0JQBcp*fZ4VtW?vpgG(g@r?pFs$(@u zl_$i167|mg>_~q;JA`R~XRwEHx{wN!nC+#GZcZSYmYq&ehc~)tlFKH>$zU&0*?No#8S&J>zQAxoWkIxQI~=Wv)D~E;HqopA$%gbxfXFIXL%(Jy*zL zg4-@t3eOB#r3Yt^rWcPx*pe7;8oUbOnYU)^>yR%d+IJ3GGpN=elU6=KnTfj#L7O&h z?Q$l%vZS{Lug0N*&v%mQdfu$$fOdYC7TG6(N%|5pa1V&w8y~VduUNy{#^0Ud6d4K; zJre3p?t*p`&-U?CU{0mvVQdDM?-7DNRHlsNWAT=Qyh>d^YxvPI%=$3{mF&AS-epaO z%5M#p5EoHY)_K?q+k#@QuSbxv-}1^7MiIV1?dFb4N`jN$y^YDNkHp>;)yD&j&M=ki zDy3IEF`W(fE@grbKB*EkrrmEQL7E{vb7sM~llPI9>QIKC23UFw5@B(_&Xu-ifA=r=N;EM)RdN|)C;#Ew2Jc&rA(H>EG*<%D{vp4cBOhnKF`jse2rglpPr|7M&Ur2MQ<<9%qy}3 z+tsBuvzJe}t{da?szDVCd3stI;@LBiE*VKpz@HQzesfn^>cD(+?PXF!mWB5^uWTb1 zz9mX4gp<_7+aTwS1nEv~7Hjj#5kJkCjF9=Ks6@&AZh6Pa3y@OJ(W%bk^C)`F(YJTe zDc0}dm%u2ljL$H=_YmrGM`XG0p&Bi-8fd1v+({aoA5b$=olnvS6gR_dI}wQW!&VU1 z<-gH6er6WUPzerr1L=l+ZG*z^>r5*nE5T4m2P)Zgw&~flkQA$>#CpXZK2mAcXKf(K`~Lh(fGMS@|o9#^4PT>Pn#OU@7`4z8Qh{ppw&iM!0ow0zOU?x_h}01 z@#9y8B0n`fTQ@+6FHQ`-O{FNWzIL3Y1)HU;(Qgw8ik?dtVuV*3HwVSsPneitc?c^M z&ajtnRZO)r7T8X`Zjjj;T{kUr9Bvx4BJ8?>)mqe%&O^E)V-{&a4A_)Q&?K03EzWE_>B~^@&Tl^C% ziIjOWK>OP^l-4oq`JYijbG2RTZEvNOMzQ!5MpTRq zrxxm#_Edstd+2Ih@Sos(Qj3e7e0K_W#5$qEON^98f8Q^#j7MWeeF^M)c>%ZTX14Z# zqq-(NI()T6U?Z*2BQ|~SRO*-d0n1w+S`(2gRadFY!q7b!xJH#g2hGbseo(cxuQ4_! zXlF-zO|bp{VeKBHBVG5jfyZVi9UGmbW81c!bZpzU-LY-kcG9tJ+j_J6>@%~^oSAv& z%$jdmm38O7pC?uIq3ZX)u9|jbd(R`o2QkTryB$7aSjwRghZqik+oa>d;&0IdB#Eoc znOj7$<@IAFL~)Zz-x(R#Hf^@=r5kb~5(;EHEShJ6OoM2NiI8M%4%aV!vK;5FxIq^@ zS%4+Q{%mB6BxKHd;B_!M5+ys`F2&Y^DQILU)|6y*Z*>W!$C+p)+{Wfa7VNw9Ifol;io}Et(&rN74uarj+c6XH)krE*KLvY_X<$Hqh_(cu)MwMhM z;r*>=q;VgigjdOuct)%?2&t$(0o1gg*o;Oh0T|?@oiYjCJ<`1dwbS&7sw58L>Hcm# zCZ_gAs0*D90=>@aiObxt8cH&hpo0WGcf9^4F!~VbG6&dsEB|WPL(w+2db$qc&fYre zz{%!yA!lNn@-eRf5@YEh@Z-(!x&SFXHW4k(xlPD^k9U5T!IEpPN#0?Z$r=1SVT(H$ zRr|%g{>1|BuexW#Kb+j&m7VBAzhFp&U?)Hov*qvrUrln!u{jH-dmvZ|o=x615a-7^ ze&h6`+JLFoj@#F$c%MwJQsxO=oW=~KGk%HbD@$A{ok(@O@5LNeSC-vVx-)t=gI-ci zL%mmOxbVRAKG&1v%Ipb+8`yz#+Fp=DGY z#Fv-m5*P>ZUZe1LSg*nuMzko`?{!l=bR!+x-c=8w`vSHPOd;NW6T2w#_9!_jn>^t$ zw?B~UcsDwMIb@}VWzwOH)T>$*YE9s)W~+5OG(|W-h|#YK+gX1#v{2dx?mw-Z=GreoZRP|XHR}$;-ROD#TBPgP})bBMGA9w zoZRh=ELdgC#oOvEm$(2D*h`EZ{^~Wm&yI(S+!}0blAs6s?r^Vth8+VE;R{@2PkFmh zRDer;%yFRr4k08-vdCHR(m$yPm0SChkafx53&IC zkR!J3q*iq%hV{Gjd|sez@OtWjcN=r`3u zC5cma&(w_?Nm&o8xIC_c{IT?_M9NTCnY%I`6rwy6WY4j0lkB1bkcOt%Z4{5Ulpm$T zrIR6%98?5fdV=_l_SkS7(Z24nUNbkgrwL3*aR8+j5>=D5V0hlPr3bjCpD-<&t&E)VQJaMrnMe=AsNL!BaE@g-;b$laQdihqxf&)cIa}(V48zH4=Q)11mhRW4bTQ`a^Ii4ru3gyuC?qYQCa+Pj3aw$+kSl&X?}v@Ryzg zD;D*(rh8<#uTiuJ1##04mK$J)O%J4cO1{jYi0rV5F9vbr5_3LH!y*YD>WLFS2(0Ac zuB`1&HQ*=H((efjPAB<@wJINPCgDA+?STzMj{q><$-Qt+~w9 zidE4&opIkjowp~hA>Xyln00Aq3h89{pej5}xTBV=Bnv7RLZBF@9fa4JI4)nnpxP7B*8dq=Ya8UGgPD+V>u%}qkE|2lq!Grc`yb|4NsXM8P8&spga5}XU?hfyp4Gv zAv}3f4N2k&)l-H1?3{eU(xW|{5}WVdkC0$EA|3ovh|XP!kP`=$-tP+n&t0~b8K%y} zr&-!+!{eDkIe??p!o4=^Grz@K!UE zVUu3m)HWJQ;Q`-*Q-tL_Ij!H-tV}ukqpDW8_otm=K)s!@OmkHWm}C3BO!K$aMK|Sx z2+grX6M7Lv?X#wdfZVQ3C{cTK9~Q;U;+}R->|PIYO!n03SglLoG2QlXOo%!wo0tuEIANm} zxo%V@nBVcaSY2SIv{a{&tUMp$$F;z)EWj;=HLT2&LRQ0 zI*v|dwSPg)($PTlIalqDKI~eh1@6LY3Y$9cgJ6+2u9hK(N)AFdI3GE5z_x>K9wuNY z;o(9>F)2&ST5U5=<~b7g3TnNBzt(2$QO&Kj4;d+WO~@%}c94mLAR1zO$P3wL#-oZkp# zc6lq=tF96G5ux?!6+OUt+ff_fK-1`>X-vAaJ6`n$L2g5GWMSXGS6rNc4-ZmI7Qem? zR~C^o_DbQ<@|19rQcHx@@Mmm^?41+Y2-;J85Pgrr9CutGBqP?zyb1#kqUAIZh==~p z6=fdUAf7H^ODUb0$CvayxGnuAUIQs)4gFG8 zqx^y=giP^vE)CX$y?*W9np~LeZc}IHMgHzsmOna5$N*#lMkk500_C@H zZ0R3s->srTN>Mg)C)wH%tB|xPz zg4sAWkU9+b_wgiCY1(lQdw{@*VRSFAe^{7dLI)gXn3w|^p{n}Ja^22Cac2zL>y_}8 zH^lV?7?j%aC>jd2FAd_bfahw&GVaBYevXJy{+*s{Ke#v0Zjrftc1<_1Cfa-RFE=M%W?Cr_7uhDu{(s;x=78i=S%B(bP zFO-;qz%N6HKO?^2|6%U(OYA>tad86-r*(%)`(RmBX~Bv5XUghCg)EuNe)N1;1`ZWbx{UCPJkq53+&UxX)< zv9*p_5UZIhB88u&Nrr!fo5X6rgh6`FROhi*xk)J7s=ex5VmWEA`jXEbZTbYh4!Yq= zO7aq82=|c#S%*s6)DGPx5w+N5dnCnkhPIyIa+XH3s^YJZ1IpLYopAfkv?e|0xUbF`SHQ*agRlSg(YtNxn-Wh76TDjZ=4fh@QBMUs5eW5$E=Y~hE0;KQa#idR% zC?A^W=809UYIvoVX{x_oOGI3!ve70Uf}N;Rm{S0w#8avU?_K+7C>;K}d%7363`^^CJdwbJ2%PcmL6C(qE2^rrrmxF03=I z5~+C9QoI4_!Nz)JtW8BF3jz$a*m<=qU$EhqBo=|Hm0`lFL{G7oQj~J)SMUwkiNSAs zgoMCoVk$o1U*=1cTz(aV!l3A@5CgW!U)v-0zA%hp3zjI@ctAID+-M&FrC@`MYJF91 zn!LY$O%{4fR`DHKFXhOLbrzO7%)P6CRpEG>hs`Ewlmp9Zo2`zh={=B%0MsaXG)y)yr{{J$K;{_m!+KUe>C;{0Ff zmHAvs_j@k=zZ%5;bN2c_ox=WH{I@~u|4*+BE&V^}w*NrJK4;@U$<6|u=u7RI-T4>ukTxsN2Z7ily&0wJtV9>3oA9q}lHoByB)Em< z^(qD2oC}=h%w3^X!!07AiwvfddO6|=oE+*v^AfDQcW}AAo$aQZcejrYO2*QX<^Kqd{od*C*ZbeWF=l4kf2+ax3~69s z{wxmpn<=CFjIsE9&it2f%t1wAtr3jaK95Mil}FAe`LiX1pJ!R;cT0wjF(4#wUS6v> zn}7f>#t}+ho-AJ;=I(#dkugw~J4t93(cnv-Evte9L9GHqT3K94{Qv<3=G*<+rcHzE z9dU*a6z2bN23UpOLrVq0Aa;t2*T2tIqi$1N?9Bl#P0d|1hj+wmY|+_ep8drIYb$@ z4E#CJU{iOwPviR*n2Z44+QmYrM;KXhnPRl8(d(Iwy`h?=?Icf4d^mnv~^ZP zEEx4X@F=W*PMH^Cwg~F2BF8LjC=XwG1w?095Kw}?gU_#0oR8CApn<$BI-1U!2t!($ zzMlEb%f^E7^&HDnh><-l0G@M&-d$Zf%Ip`A&nLmyGR98v4;zbMJ~a(bbD{O6NJ9Vt z){X(C-(QDY=m6gvmU^mrcxlimfKY&X(tWJqldumL2;e!>H#^g#wf0N7+}qNBN%5qX zDs6H>U_Q=)Z=L};_yJd=DK|wuaASW6eX29#2yY!9C2MsMs7t4Na{ASMc=voDm>ytb zsE#lAAQ10&uXjeC`SfNCP)AS9Zx6hFb#wN!2?;o_e-vb}vetqBN)<)}+TB6|0mj#s zmCdSxKs&s-kYJPP%S`x`V_yyYz(U_;FXyD*3XK~-<=E3fmNno}1F_zU#Zs4F0$P%w$DPGOm)rv?a1nLy zR+-;XTp7RCkgD%$sfSq0@;P^vJ0Peet>NZ8^;%wfGu)4rtb;QNDRmS5zK<1^?)v*tCXcfuUiI3=9EZQU+(lQXZLZzA1wd*|Z|Y z=HA{xAF6HgIg`Pv)(D=GmQIzUR!T7sB2`^vHW_BcH5&6=zxm-guCPO9J=B|5XF95X zXZw1M@E~9IfXF$@VuCHzAnS{jtAbwuEF6R%UiMT_S8ox-g?WS&dE5}9;^a7@EY@Q> ziBX4-XDw*`K_$GOqo=Sb*Q9EkqXZS!BCrFAH_+~D-ceg< zw(rQ!FP_wyqTV&aWJYt}kmHQn?hBw3uq6RE>|&U`A_Udkm_QdW7U&>6oOkfTaD@eL z(XG(FKnd?;C2-^1NLDYEX0n@w6dHH-7?PwW?a=ljq_)o=^YogEiZM;KVAhxLyoYB7 zuk(Z9yy5s)6g}%Um(wLHzdI3b`7*N&U0;gH{1yR+lFG}ayqL-0`Zl+CLhau$)?WzV@jwK5-G~K(iY;;*k8_C4$^DUzR zI8ml1pIzJ#U$P3on*1DfjG8B%k+vXECmAzRyUoP-ukR0Sb;y48WB40y9?9l2pxr3o zawk>!QSb1(-nsdt{7x-rSu(>FV?HRcE8ws|?`EMOtK`l>w7E{v?lBp>;9|_GwWjNS zhr+GUEgKj%^@3l)L9TVcL~7?SGvQAP$jaq}Cg~8Zwxlf~5fG_xT>?3f49&RZ=AlTHB^LX!wq8vQzF8m^Q3u=DIY+2r@9*gVW%`j+Psw|Dz7gp zy_MqUHIUQNOsEx5rp8QQurujVhma6#EfV@oxWQF_Xn2EOPCVaDNiSUnvl^7>2=F6jh9cIKS;H+A}Gwk?aOW zct#-3QOWpB=MYE5kSHo)!-lZM1dJtNL(I?)wPN(2_nvE8Yo#k`>ACN z>!xkqa4hl>Te?USuUERCF`Qa!5ij{xfvM+{`>XeyexDr!rBUvXXq}c!X=gR0tO^-z z^P^v{6d`nGKWKM&FV0+~yo^?H?I4$1uV**{!!fb(*mi_b8lc|4crZM16mqY5S#k#p zoDV2C<%{X^DiJvl4ouhVO<->wfpCW{jMQJRNdPVhEkmNmzZ$nZrgrfM>BpoURT#W7 zmpsBZzdX`Cc0S!Mhcbu)LZ$R)H_{{YRzm;O%ext6{3__e{6+I%0Z;L0ejL8Q+7e9n zTM_S;gT-pgwq|L0eM7JFGdriDo|AEb)5WX^M9;dQ)lxZQyEDBlGJh|Ggb*Pa0~y6s z=6aT^A!Okf*N!l{AKd01gy;PXM24s9!jb&aW^Ys&BP9p+K4i~qOauWa)_IpwRO#GS z^8SvxX=z0rFnkq?PmO9th}P7}&_awwKWM z=~`6;glI}5SyP4~TTe~ZA-?&elP!^YLE+~L3#)E_JFJG76fNp74QC+cAQSgc3y?-x zq9Ql~XAS=eeLDv!M^nJW)nvjA{-fv8wxe|)86y2B z`AYi^BlhKIbKjK^yZiwB9YxQ?DiUsF8ISsMo#ngUd^dZCdfb2(BK_I}VRkl*GhhQ_ z$^*lOXR80GjT_TK$PdHSW96JGc=vCYghzrrv(V;P2W5128Vg3%)}6oHj`aJnS+-v@ zN)3F}AD`Yy9OxXho`C)69O$e&VNJ9OXc`i3tHtZ_O6)5RKmGkQN8P|x2dlN`UAr~<;)9}a)S4y`tezkz2{ z#@On0IG_hPvuTBsQ_a1DGHpe|>i6G*R+#vLo|d$1B;=C^qeSQjum!T<23;VFdb{QTVJTY;IY zyHL#`ddxj`Qp}a8CFymkG2igB?+uwbQd*Oj<^47UcBClo!sTKqnQ9)o;C&r9)a~xq zL9tfcau3SNw`y~+dl+)#0!$Sz?-28YWPC=njzk{J8Rc)DX9B#uBU;rrEW{qz%v~{y zESnBPkM$h=5vI@83KQdvxh`LJhb={$9E>9E6jC%marD-C8Crim=i^oIJ_x*+WSj^* za8yT_z?(nw1CxX}ok~c=9o8N!p4-th{+#b8A^u2{?quvw=Bc^;lE@Z_{~WmVOGUWe zjh8>Szk}14Lfc6Ce)+V3FoO%}v>pXH1|gwz{=oZd7GFN+Vf zttO%TM>b-WUUAy+56SK|Dzk?vArQZZb#VB}$I}71lZ3$r3OiXNj3?B+_@Q=4w*ZRY{S}ER*owc;-Gx5e6VuO`Pa4 z&pyoDnK=mkp!7RYg$L9#QiUV0_10T~g&F>J%d_lwv36{}uv_-Ame?i9;b~a{#t+FbC&&8xvs)_5PHWwu;)uSl zU)@Ly92>dA_Azn9j$Dd~2UpV7Y-Zygd5xq~alc?lb5vb!3W|Z6`6MJ2o-$|WB-Oke z9i_Um>Q-0hBd5@#8OcHtiEGq0IG=>*8qBWxy=W@dwGx%vg$XZ^T5nW^=Xh9k;g;0o zolDAXD&b0!uj_TW= z$mVT~*)sKC>)?mEY_@T7c@ic!B2q0=EKO?v`QRsdAROdyYKV+-rD-tP&gq7RoJr zGHqOfM;w?}it4 z(V)yit|azg8eeNO0$dB?{ixI31B3E?^fAyt=DuQ)28F=Wu{Jluo>qnDw5Rk2?oCz; zmQHzCdw5I=F))k@G$DLF4qNwt4C2Z|;Hn?uRCC^v$lc#k!ObRbZT!{N5g>JKnYa*+EdrzRYymg7 zz?qSVk&HJO&(u z=K=q|u4bqtFoNclyMjL{M?thNQ!-}!^!k`52^`N?a!2gQ1Z~ldURyAX#B>2d$YSH^ z%bY^2uDEeEJ{NU}KQVTEOH~yWgDYCA)zhm^MYh=O67xz-%(F$q6c@$8PO_kJhIC^T zIJ8`ME4rM3kt4R*{1PCzfE~p};9%k+srrz`M97)XuX{CIFV z*7ILg ze`kCN%%#IfZK0al#&-_R!R7tZQoGwp#px-2EY|(vSNsPP&lFRiqqdqNciUvRUe?qR zoW#5<%o!r$e6-al^^f6Z+yXW3o=z$+dub2mlHGe1fjXy!4B`y1{^$8L6U$n_ig;qr z6W&K=7S`QyQ89Od#6H^-(m1jXl=846;7m9fo8xw}GS~OR7bdSQuHkto@(s-kTZm;F z-W@gP4@}qdzV=A-z%AdlS-_k<=$l`_y=9ROjc-V2I|{{Mz2A?T@ST%_EW;X`osx{Y zoSV)Sz?XN=n}Y3lZC^OZy{}4ju-qEndk|ePitXhi`Xn;YFc&SF4ysvK)VJ&uqu>Jf zgzTn5kdaU_vr0TJ2yI)%((|^?+fzBFl(5t&Z-ds2-p0)oP(5}|K&(gtca?A>mRGq#HrOi7+o5FOptld0uc z*iVgh;>dXT?c7vIlP`$8Ia>|?Es&v$XS+7Bjw-9F9CN=o?aMJQcwR?n1_6`IteTUFH&J!_%fb zxFdG?0|pG)>JxI@fjFj1>an1nz7Td0a#cra&&Y~Y)0FBDk3~Rj*OG$uwsvC@sJlTZt5Tr~2lrV2iVy z8RlE~Gw@+9R6YKq;7}R4ZH(`@7h4{V`_9}LV<}3JJV!p2CX|X%l3MyIL)$N@ZjjU~ zZ;QI5Rvmp~b;wU-n)IO(U_pmfoJYAH)D1j5VH8s@*CV z5GWi>O8%BpJ`fET_Q?h}X4dnU!op<&~qS!{u+A1 zbY<4X$9qrLQ`4x~uho?;KDoPamr}_kv4ZoLEiepuSN~Y&)@`oT#27o0X}YdFN*J61)jpD-KYO^t}OmYz1Z zHug9;bZ#q`GT|u?0!7L54(^ePehgWq=%j)?THXd_it7<@Uvj1^lQe?ajElX-;TWaE z4?^u|Pn}342HQq@?j4pGs@dpARDmeY&TII7t|hxXK)Re!|M27xqk@u2c7s*qy1XdR zN_eCY3Lm|BQj5e}djjTU*UvXkR}w^PtIcj*1Jq9NU6T0ad zsgefWz)FHIQC*_vgqjL+86YcFpSsbNJj)FOXG*S6SPm%cGw_V*2FJK|#?em|fS0?; z=;^5>BfR5Ry)gD3mYiJ>Y3u2PO{-5RN-8TTz7vWHDz#i;X>^EjP2q}$xH~$G{)MZ) zQlaJXknx;bv@13ww(Zl}>Bhx&v{bI})n6Azw87K0TPRmBvW)E`B4@&2J0e-U(`S;| z%D_ox;aal7+plds#M@Qt*DBjpa;c@xSM?0Mj9j}E+Ue{Sx9#0$|Fk4zE%pn)wD13-pQUTF=`UsjB&)nuKv9 z{N9J+a0$M%xm2N!cAY7Hc_n%PFVhZUYG9T6et`(ZU7F%omDO=Z+)agQ>{=0#tWYv> z{zP;GLqCM#KxEsfREHE@3zn^&5ClErpKpMOnJN~QCFz!EL{>>SrdA|Lh?73*G!7-|tXBjY zcFIz1*HiayjaaxJX>e17!5N>fG0k`Ov5FQ0GtY%0z4iM6z1 zZ9UpaUnDcA!WlY~;K+V+o#4m1-ulTT-w^L@IYr-rbj<$(s%X%@|E+5peh}kXow{EA z);=w$Q?S~>!X{y!=#qTa@OfNddX)K?qpNh?V}W?GBKb0Vd%mdtfQZigtXr@wlXr_`GBaYY;r?8)~p+b$lMjZ1~zv$o>Qq! zehT!=WMVHb7m5fG>WQFSDhFaC*S7`+B8#8i%~mPdO3mI0X;k|h22DN-#y#l>$Y*@^ zBmj{Md$M z#3_|pq&FJ)Y{9bsc~t|V1Sf(ZDN6lF%i;TGu)ReGneKA1i0!(WHl++jmoJPRxUit6*0T~0!Sot>MrUke(zrqDky~gYUJ{xlMQ8edShZg7Tx$+sU zuSf#h)l%lF@8xO+M3L(|`GiY5rBac;O2SmXKgM3ptDG)yTyQkApko>_EBQ$H9Ji&2D4RA^3Iq;%FY!Kxbv;>(GGH zO`IBv%Z0}DcszGL#V>2_T}+L^pnl)b`L3H-Bmm4ndiJ5>CfdI5Cy{#PPAGWHsEA)Chct}siiTi z>rcT3ohpViwI{LmMlXy}?o*Z%>k$qlE&6M=eEqJd7KUJw_ke-Rh;L8#qp~uT=`W95 z9ml8fCps|$=3Bpv1{qx@&%e&a>B&gZ)5&_roGA%v*Z)9Tu0YUAHn1vP`o_Jh%Pr7B zC{%Two6f`J-OBQ7B!_>ROPFp(7%X<{wjq&4Gk+Z^S_lo{I#axuRs5TKKc!f z{w9S!mwyJ!vwXs%Kiv}jXZ+LuxU%7M^}m9J{wjt3SV~XxS!nUM2U&hsT>N)f=&w@f z--{a<{u)H~Kg_-QPh;K~X;`Re|H!fWYwi^jI$fbI&VDxyo$td|C+{XqF}5vPpI@ip5ln|y^bdSx+N zzYd#-j6C4&ZAo>zCgqge-TM7d~L9Yr-!ydRPRNcPjy-pu`udp719m zfvl?GXG&KZFKzA`((_OTd9Vt>_Yj#>W0a5%4%W(O@+qZ(Swx4_p^F2Fbgzt9wfY6t=v@1yL2@LNDm>Wu0`t1LKBq*yM}}?jHPJ6USCqF z9~awT3s001Qq{fMytIP!8sE&<2`@TAW<_&IDsZn!?quCzXPCnsEoMFdSlN?`{*!l@ zKW^~f*~?#z)n{zm?=-5v7%PT-RQ)-~V4QR=;=n zbKO6%rN3CJzdru^lz-pn-;I^3iim`ez<+A2nCbu7SbfI%{k|#xA7f>uXlbHnrEl->>?=!&fY{bf3v-f8$oa8*=`d+Vw}C=YNH-oSYa|)lr#p zh;oF&xCA1Yavv;j*WJMUbjE0e((weEa~{^!XYmNW;ZUm%AFa0J0B3797pH_JzWhZAxsA^O~tG%1e$*)acu1E}e=84wQ=l6HM#4*-;z z6v_~u7oZN`*r;e_JoFxcr3=9X-Y@6vy%aRDjvnKTiVSaaXQvn5=4MwoCr!WE1(1uT zj=l@v7*f!M9~+=kAJ`tx@>gg76;cgwc$!XzR{=~+q%khUXLc84m(IP6|AAx-Y7by2 z&}tbxjlu%ZVGBQ^S8V+|(IwD38xBA)?yXa6XNec1FTN)#|LIwbCnCPJ^sXdlA106=|EVQJCo?>t_t#s%Xv_#NvfDc0?$xP`*9@9R z>I9(9#<_p=#q+8)RS)lkJ4k1pBZ zv8?iVfVCrkrQ-61lk{Oi@#MB;0Va8oykjEz@&JG*abjqLJNV*5d>nZq1+u+|SZR3s zJ^@kqFnw90LsTetF~n@R$PHH#Ak#Hpu7{{76?)5O@Znao5EI$jpL3 z000sb0qE=V@%H{W&ja^)tB>=VDGH#2Nd+aXV%L4uv+<`dh%+1Q%?MV@BO&Q==x#(Xa%<5Mf!mXybb2}^48ycRfYO& zh*KBCEbsHERYG<H1aWYJxN-Iz_NHi%WP0X+WhxlFPG&W@dGQ8+>A)OFDmACm}|Lo zhOKGFNpYOHs(=~iU_KTa%l62eQg*0ZxY|lubj**CxHq(Z%xOty+a#4y)!f87QfMk9 zqN!C-67pY@iAH5ORb0bm_0ODkvC>>&mPB#w=A9&opR8rLz{5=Z>ksQ17Puzoa}1+t zp5FTb-UccrjU{(4xs29P8=DHrr<7t$*pkaqrpt}|L%67ie!6nsj>kCBI|_iBIR}-! zX+sOgm5SWal0qZ3(Ik@-HII?stdFz>RmGI0E4o zK(DeoWGe5a^zBEcC)eI#3Aqi2FBG*6ngwF^c#EA@imR-M`myNu@+T*agS| z&N^!#0@4{`Qdww(iz+QkF4Al;Ht=eA*eDi|?@dKIkF~p2v&2!9m|D4tK>_1 z{Ph755q~uLvdGKlIZJ@mR_r_38t9hBA_a>Yeh1A7w4pO<}&_hZH@b^!OI3wQx$3A4j>~mOB`wfIzwwK^esE!p$IAy2MTaZB>}B)L@}q;pX$R zH}&pi!MZ8z$Ddz36t=IZwRVS>(oL5dNQ3K*Wl(ri1G#PM;#Ek}5_dEdR#uL|)qhnX z!Nwmpr!{{>CpBRUP{a$PsbXqY+*kK&RY)vza^BQm4!Io(rtVKz?|xAnvUY^Mv^3Z< z^&pY0(PzuI`i6iqE{6PpPkDVBzd4+`+P+q@#^5^>evH6)-vx82U-(2vx68*Fld4H! zb+GikR#j%^EJ(3ZXa$D~ITri}585*p!{9#uH!IZ$6lc#E^y1+~tj*5}#eihE_Q^eT zJgsf0`>u6WiMci9ls$T#|FrJ-qIr!BDzoq(X9*j2_RSrmfF-ZnM7hwrt3ZT56A@2(U^-Z3I-ylx1 z#CGb4y*9#$VR%4q3+m@Nxcmm#W4P%V@LektElv=sRaTp>^72VnQiGlWNO@E^u1RC%{J+HQ#SH{&E?3%zqEG1JGBhcFkP&V%oKvhAvoH z8*64yc>-w{Gy+U}l1@LYc?pnO3M>Y8DjZ+Xv3rBp!;cAX!k2V@*<0VaK{K5Ha)^+A=6jr5uqPE`>8XMFUA(A|t?TkK}-x2FP! z?80mp*XBhnVf#w@8UyHwh4c& zam2$~X5M#RAA1^aTYNq_TiCouw>TSKFOuVQ-b+Jf><59{`yzdzMs8nsWDD2H?y{lC zx<`_Ji^bG2t9}9NK^h`7}ku3eKAqpN>+X^M*2`Coia+XL74;Yc{p#5GnhS8IMe4g zf%5YR1~lvxo>i5tG(~Qj-jix?HGNBrST$s@KvZ9%$%2t?O8!EAdD_(O5Qq?ZiCv~k zbH+D9e8qhRO(3q3yfe)P}m4Cg-leCK*>7aAmKHG^@ zdJy(c#EHz9ggd!JT;D1hmf|-aj8YuI=>AOak{~E;J-5%kfP$x1o0QlGKAlIp{uXcQd=~Y+ zS{f9?DNxs_f4b=7Y`cLQ2?xftfB%(7@gDq!#W3z67eS>0X7t-;!eC2#yyuc=IvEMfIkKffYRNiPvj8?K_=*Wsg`P|_4 z@bO`dBrO*bjydFuBh+_Y7L|n?rKg)8_dtSxUcR2N&|Nakilj5zd7%fP+vRy zYqQ`B0#&arj(K%%lR2CD5=Zodh0Ss$CD5i&CWWR0HQ%*tS?uhx`>J8@u~!=G`iQsx z$1?n|@|@SI3%0d-QeJKOS&8|Di&$6hEr}vi^7CrGBI@!^4*~Rjf-E}jRfqX@{CTU9 ztjF%EJbPdvUj>sMiU$2dr(oQ%}`^n8zgce zH)k%_H?PcN@ycCvzA~{FuSt;*w&+`5O{IZ;PblG5Sw93rdgn!@++F9<&a8eiZjO{+ z-Bg?gRSCY5k{grfzbMld-#XHN3fcU@z7n1Yvh9|i9pT`gs)li}v7NE>rBk0ljWP!! zy^&P~uGD4Bx}wytpEOo&!{#vfZh9cWKin>X3A`)026TX$|T;}p1Dz9AXP|D*0KqvBf9g^fc9?(R;o-~@Mf3GVLh?!n#N z-Gh4w?(S~E-QnxxoHKLgp1JeQ+;#ulwYpcccXi3Gs;=6se)@T>M~~y%U&>qZ8Zy%6 zMLBw!^lH8gqZ384C=p9|5cXNu4S6DcOQfsE3MYej*TzPlD-p!vhWX_AYCi@N>r+}4 z7SjDXxK+NOD71Hcv09O_TUHfYp+GLy?8vwUXC~`8$B^YnB3s?_Zsx1|j^{|mkE86o z;*jB6wEOnDrPB0x%3Y^U^O_0+!L1sLhQlAbhpI`5yksR~9#j0>Q@WpQ71HR;23PUk zMz$GFT{`$R?Z;KB2#AQwnF{qdJ9!^MjA5^tl-YAJ`~m&b9b-V;a7b zCys~LL@csTJD9@?Huy+nQKxa6t@9B{v6EVYXHC#5fSxnd%sAXcpF2o2#a67i=g!74 z5?xjJUYUQk1wzH1#~;HGN#)>P>gZ4Fb67!5ZdaRdTiCo#rLtmtLjDk)v9u6-z_?9S znbgWpty&u3d)h~Mp~*fA+VH{$ieIO=27ZHw6|?i z`SpiL!Xp1({o!HCclfx@mmQ99d22l6$3lloUVEO(SPvpH;Av-_uri?wOXtbj;#Caw zIkc)Pibv{eyn|U)BXO+8YqNc}I?_zIj3QDQ-Y8z7Ie*N0qrv;-N}U?MlreNC-=}0O zgegna_vCH|sG!zMd@{h>CZ@J?BDu5FM+3xh&zaV1!Nx5`sMp9SMr$h+(RRxO;<@$^15sucC=VZG}QgCHEKvcO}Jn85}V)7>m~i@ z;0iCRm_t%XK`RH&aSTq5b02GPJIs55biPMAW^?4gh-5$)I7w4f@s9)epk*Q`tdb*I zn*w+&9N0z=Z#rJXf$>6`ETD1q)jl{YA`b<)-YXTPoP zVrzu#Zi~F<$*m$)v2IX+1m$@(a<}t&P6sV?5H>SM*jh&eH0UEKdDO^7eB)T%!!HnTZIkHy_ZH4VHTz8z?pTO~(iT(+$mlxSpOiENt)WDLLc7mAW9;y~!@0Qs!#)e-#iL@+RV1d@HzTEF zp6|(?BJkYj(+_lU(im?cneVq%)}S9|v?=DNNy0SeI)xHAdJY&xco-gg!Re;PtZzj% zWbe-L5M>_3dCw>gYod0J+Q!i5muu};JtI<;bgu(u*m~5Y`Yz%LRWcc8ULus3ly?%3 zLWp1S`ik&_D{3zuYoX+Txz;rcCWK|C!jxb}x}{YXsMlB`Oj^VVUcyw~ zS)dEdk+2)RmCKn|bcn+@0yGL~4Qdw7l#nonL@7J*zL zW53X`ocoKFiC$2)Lu8SOdeoIyo;PW5jaYv{)u$9h#IC2gT#v)l$?V#J4{#3%x`L&Q z{BjN*JXBNGiRMeqWAX89oWP!@VZ9B;+GuZjtc}c-qSJ0n$5(#4DB9u91?KAjyX&_h z8kDA*IP=gS-Wf!!@i8pUWE$-xhf>R}dYu%+GF5gQX)POM$i%4?ttzsdZ3@?`xjXi? zWj?_H;rAV%jUqZ4k>|p&Rsg?N8Ayy^k?v7#Ifnf5@?I~A@!UOexs627K zgoEJVeA6xL8Bn3K*wzMxYOxTrj7b1!4Q8a>DlSTKh4Ies*5ZR(hTm7DhS*5WQ)}L` z71B+nE@|znAiD6E_oZaa{5PNBO+YL~%FX+PsaL(ieJWDIEKoCS_$IIKz1arS(oz+6 zpV*RKcdi|`7VJG!$?QBT(HI?hstR(szNxJfH0&}YYR+yFj4vxjL5a2a*pE7;9GoOW zsx!pRsgR%bCuJkP-y!RdWqLNwJ#o;A3$swmC-gi4QuPRuXHv-f;l*D}k+2zdxZ9K$ zxtD~4a8!?SY9JFwi0_9pbLDZZ1CRowLB{3MEqmz&*A^TON#k{a8QU=e?>tBiIw1|M3x z*1TM=##0gTsQ5UWuv?rNK|J0F=0 zfVsG)f{@dAQd!s93S8+3>(FWvopz9q75pUOThj_46vRj<=|?D6%^D~bCs0~6*ILEf zn4NL2FRn6L!MT_s`~dzg**DfL5YTaxvrO<0bI#RbmC}fGQ?27EKPIgmS1<>{<>=>I z=70=EF`SIhbbT{fjYi5nKFN9dR1N6mxIfv2f8U-{q;R}>5G?fg6o_|M@gF;806xOBtgXS(6@lLwdON)?%T&aahFciN{yOlNRybr~( zB5<$f&9ni<+u`uWrEH)#+nYcJ${_xLT%x9(?M2~Y z@O|cD?TdalY!~1w5Fq$|3j%TJoon|`h3pd#)dgRD-Q$s(5ia-I3feeVR8K8@vXnmz zzWu1cQd`erSQmTOv0B(qEi*ak8S@(4Pw|N&x>hXQj)~Tx!u`amDv}>Q-zUnrjIVe5 zn>0QnaX*gB3b5W1$x%w!>Y^e~+HT?`Ry9yUsVYtcfYH^)CG_A&?<$F?nI-D*owS}a! z22+M6^DB!p-%hgQ5k9wCLonSEHak&u@4d4!E;Z@b#22XzDXFBc`kDzvQeJzwUbMB- zUd`ySYDipENZ{#5=RK45EzAFmZ$nSy7eHolbU9aDA%BC{6Tx1 zUAZ};Ky616(jF$yn(!qxl94zbAA&Hn!0|@CB2%N^QN7s=~!RKd8WU~_% z$RiZe_#T%$Mdp|*A9_#8R@-pVY$>U~3$s>>h0<{{)5(C{%~D#SiJ{RbzL7c4#+)l1 zEqz+HM@3d^>_bAynf{Kv!1-&orX^Li={~#lEVEedRRS}|^k(hX7OB3~T6;ygH8v){ zH@Q!rW5Hwfn{Uz5EeH9ZRc9H61p7xH}2b%47> z*E@Zn5O=M#cY|8)JpPHr&;i{=yTYbFd?pH5ORwrZ)bS4;#ZEf)AnYK_2}y8bygF~_ zTXRe|uar2)Pjn`c=f#5q3I*XZ>C;B4v>0WZeyw6sl-KV~X_!{Ir#4eY)LpHG9{k0w z)8sE5sXuk(EAaNcvnzHW0=;}-32nIOKmGiHAu_u@RTjq+VD zh1Jg0ium?(KDA3@7f#4#VzQvBALc>NZqBfd&TH!!MMdl6uwb6Pfb_=Fun2b@TM0&P zf8gU9d;r1FECNxC`e(H766*@;f$D%?Whe~iTaM1jfv>eBoTRrgu5JcQPu(5pcGAK6 zUXFNDwIR_VzuetvwsTEhGu{B5l^%TF7g-i`_jUWaW=36wdnP*~P@~?L)|Y;G7UIrhiW+i=!xTABtx#;u!V4(6v3kUL0<#w1m8k zyF0nahe=KMrzTf;;jZz~8eXhib;IL0Q zr1sA35KX;cD$(dTAA>v${io>M!K{5s?H&z3< zN9G(iG&!1H%H7mf)Lie>uv6lmYsCGTWiiHgQKdMiD^InKh`GqE0#obDOH8%4>S*@X z`X5>*fN-cK`Oby5L^Mh{f*dce4hp_if8;TRl2k2!KluQKrleHJpy9!@y7j85N0iyU z?XR+vahgFHccwr^C^y&wx1yBR)_5kav)3D1A@Q-hypjz2(+>Qwee8#e&9xl+&evVk z9z3uyHf$A_tw!Ycql=niNfyzUZ(ULl-QJ><%IdS^obi!~mvXN>Vy&0je`QvFQ4>FD z0d)e#Uw9ZGco!|xf9%Er&}IJ}@%C@wZh-uZOn@MS0CtN0KXhaL1l#^*yZSTn_W#G- zer9E10MyFJ`jPhIKe(~}PQ3lXPW~O9f|ZHsCnaO=U}van3GK4vs3c{*MUT?9ui^rq zumsxYBnpDcUwcJ(Q@vcOSk}(3*Ahd(Cl>wn&5O$mjPB$X2#Y5w%lNo^#lu}oMse0^ zl%#F{xbOO;gnE?ZrGp)Ul~q+e6Rr@`3qv8rEd2z3!G1o`oUwZOn!(-T$L3fpewv+k z>Bzt%k62n`%LU4Dua)*_m6+?n(+Pez`LaSg{tu%bKJSX)_5#mtov$O@i7D#$(+-}i zEbx*m_G6ZW)}{4aU6S~c<9-Vc@l9oErJu_5)J(8akLF`joCPA4=fBIZ4WWjRWjALYy|b7A_8u7imIPTxOydA~40N7RXP>Vya+`AiMh#rkzlSg^;0Lse zv^j3S>vqca1n$l`Q93JPcPD{%;C5WmjIG_4sLti)ZGxW__G3Z5`G^m4&pEEV47ju? zmw*jF2Qe(yBs&OOTnHN?o1C;)4k6V5y4&~k`-x}+MMxe(uORMshYrpqY=_@msW$EN z^2Z~fJ*{R&_JxJGS*dQDg%?R*%F|hm-nvwb6rWt=#^T)dTAJB}yYWO|hKTwPR z4bAwgm4DRpcbbt;Sw%%v;Gfcr0Cw{)#yFP0(u@GO?%y=VvHS&H{8ii^jrzCHMP)#+ zQPW@0qP3MFz#3=&zhsSL`3t!Cm!A1w0vCVP^v5;-D{zsO9bg6gJGcnI!~Zw}SmW3M z{NsNIE-E^Hj$dsA&llt<5FsfrI*32DG7Z-C@zsS0!q5SWM>#wdjz5r1<9w-ImwA`la^ z?f3~Ts!%{f022do=*W;4AKiRGpvTb(2Fm#{rHr``Tpcs;eH^Mpf(s_U(TCo zFZmY)BI9dt~0d(H*od?y8SC*bt z1|Aq5<2gyZTidb{UJYEgD!{=pkcg8j#OoKd$X_Y5rgC0I?Zihg2{p35031~9d{5f9 zHY_y(25f)lXGZY`xL-zm!L<)?0}=G)%?6W&0dj=o^(2t?>qKL?b@YFg_VWs?a=h1H ziM9d?Hm8*JP6oYxjDp$*z6}E5l+%*+9^pZL6BZ;u=)!}z09)Y0)rHFKY}F3Vf%D!l zN6*&b29nrlc*h5{QMImZJvYx?CqlH*;XUoGU7zh&l9JMlbf%sACQeO7aLZx#8|4tfmj|MWW8d|REg zf5Uk|P*ntSv98E?fUjR360rQ{EZvLj!G#J9C33GV^6pAD@PYT_MmH0oh)42DWMyZ5 zpj#KJn0pHJ_J|R50}|NxEul4&F=Y6r=-c*EN~gla5yRtMEEN>g)GL5l4E+M!MS-%A zbnuv46+;1F7V#wAi^qR5i{J>N<3~tc4Q$>%ph!a3N6Mt%fcy$O`8yF0mQ4L7yaAzj z368B@ENS?@@oX?-KA@w-vXqQPt`!AzNol=Qg|NstETU#F$O2Ze%$T19!rA!k_rJZ~f zEz{E+QCDT`j5?0>6U{!GdkRI~t18XosGe+nuZpbZA=iscS@=Hqqvmo{9z``@E1RR{ zI)!*p-D+s@lM|M0G}l(CRhFory?m!4>O73)jHOwLTS|e{Fp24-XZOTahGyeM?LoI? zDx|t|cQ>&6gL||2t zMejxF?6h61jb!zXXNq@NPd*Kkd9E9aq2C{b8ez@VW1S&>ztUe)S|%H7cppt!a46mL zt=!W1G5IxSt{LjyezQWtl06E$VX8C>h41J)0*_mL9@_o3gKs?#4W4sT=dnNYzLC|Q zAa#bDMe_JZcb-b$0IcDH%QUm<{gd2lLMr@lReL>P4NM7oQFb=4`hBFz)q zV^nTo)iU9Ec=SEr@A$MKv7MF2Qneiys3N1_v^()rQy&<#2~oe@f8JIPx5|EImj&ms z8CveKNq48-Vs4?J?ls_Yrr6lv=wsWb=yubZ6aekuFjZwyD*ilLpTGZ+Mzr>t?U0` zmifm$rgE%h+n^<)o?n6f`njxaWTZZG@5U&MA|jeF$wpUB#wQ559<1pa(!W~|QBMS*^iTPqYQwWi!I}j8xItc*qTUDP}Gzd6-hM#aY;J`|X2f z)r{w948F?5><|6d`fY2g-#;Vjo zPl0AabvbAkBW1k3P`uL88@2M|>R{yMp8153#n@a^aZ5NcL`5h(B)B8ef5&)6bkS&6 zAAueD;~d(jzGx{AW#Mf@g>cetQ=z@>d6yjqT%=mx>^zsg4WJhU!@5W68&Zt%i#;pl<~=vcR~+4L)>*$jI_-4d9x4pkZ}54ecD6{g*N`_T_MJ-bP(COjcm^CZ z64}8dnof=jXztxH(=w9{sE&`6!{uy*CM&1F|kj!2~g`^)llt_k)>aa0NRHpK%` z^~QWPs*w~0xdcocPh+b2I zkZ@P!*?&=9Fn_@Y=ezh)Rm?pQU%-#WrwHuPD2=a{CrA3eFnyaWh4~^zTf!lfnQw7- za5iK?6Ioj=D`u2YO~=3Pubz;u@7|AB289#sH~l zWy**OW?OeS`M8x$V`!crbY~i$(*@d@C3=t?1sjl-|RUL!T>v3oPJ+8 zrMF()fp8E=+54X3E~l09Q_r`g#=F~Qg*r!%j;=+R(Sb+!ih)1_R;7Sh$F7|h-SH9} zg_V0Rs~g==+=#)9nafY+rAr1Web-K4Vb^F0WJ5Vo^D0zFULRC}J&D8AF(6r7&aIn4;t z^giW~$m>-&W(<0lvkuDoA?O??S0SVGa-E>#=x8y%@93P8$R*Zbez50b1f_T4Hr{;3 z*UQJ9`s*zF#m2%W1yS_v>O-XD?ROPH-#N-A`y!-RAjjx0vsXRA(^F_vG>DZ&BWN)a zk<^PH4!5o14PhpeY$yB3<+)PO)6k3w)rC{yjG8FHE0RyT=hcaK^js2c{9huSC-qxY zO9DSAC!>x@fLk+ody{ABLo|}I&ez*MQ+UAN2RT*7)L>@Y*Tl}MDqx<(H{R(mrefB; zb@#Kfcb2SpJ>(ECTO2pvUJZ+z-FxM5IM!-chO6FghAHi_v~jI$e#tzq-tyn9tMYGo z7G!0ou9HXPuq8IUi_k4T6hoUVuw)I2h6jh-mnt%954O0SCCkj!Uxp5}e;sLcV^9`3 z;mSX1h)Pv5+w_aUhVT`ujDQfdnkf;dJJ)eXsJ3v%cKy83!fXPZJ6gcoPYBf(^2Pv{ zm7N~UwlyjDZ9MW+Z2Zovv7o8!E;?7T{miRMg?4#PV@U|xGDubuMya<5;+#XNdC-!d z))5Qu%ctOkgiI_uS4fS}e)UM6W26w?6vuD5QA3S`!FSN7Iu#~9u^fefAfu^C_esg{ zxHu2?{!!ZS*u@msc`m}iA(*Gtb^&Wg2;~SJsr3NDZ{&9Dtpl&pKnGT%V zO$Cg#-{hcH!^N)3Vdd%9*VT@|3}dTwgwdG%Sb0e`D;W`!wuy6DVe9nDTwa;IvhB4Q z_{S%=p6n2VSYOvQW6Y|Bu#U{D3;VHSitJ>*Di$4YEwmkpNVa$>T|LY` z?4|r-DvD`yrCWD+?n69z%2ND89>yqf1DV>B79{`hqjC--g+Z$~BzbQXMiV>7Hww|A>iX{A~Eczab1PjKIlq#Q$VwpSX2A>~ijl zq>+CfR@{uCdRdQi(R!d7)Q3c#_ABYLcasW16uOOSAfk_ty$|ymNI}*}<4S=k)?~tv z*sjLhH7F^PvH0#&hQ?`H0t7YcIP!O?CYtJ37R0wHG-9xI7xIKk1FUH;4--QolE!W= zpGR^jXDcA#J~Ok>=I>}oT$>gz4p}r>PerS@V-?XVfAsjuzbPVzIu)6#97c!d=*^+t zQjX4%{u!;6TJxD;aq9*2lcBYCStJE+QIO%(oa`w^d2VdVd9Pzb$UgT*?p+G$fy>2% z{fXLkjTZ==bofGgOoZ2?R^xhoTsBI!ha=kLI-^!RW?b$-DsxNwgZ`_XQmN?{8^iuh zd_X(LjE^N)JAhtI;P6Xrgl~^>k@q-uL`Ah-TnFuJRQj9{T|{DaXhluNDScc3(i-tFJ z2anb%Kc~7n4=Efy96a5UZ@tu7Rvm-f(kBq)AGNVa&lIsrdXzk6JdE`A38jF3lSrCF z3@&udz+QtaawAWCWYlA_JmPmm`fI2&%U7WFOkmgJF$Itw{;!K?nIP`I!!X> zk@uiK^x`vh=kN!EHWmV#VcVIJbiz>w9=z#FV_?6 z!}dv&gL+CN{eG6(oGI;7KkNCrGz?`W7lxw~T&v1HielOAWKPu-_!QV8%W#ewNa+uP zBKW~%AfFy?|H{qfE6G3g7w*V=So!~6)@27@&wqUcnb&aQqq zBn-r|84g8B_2zat8DfW<_rQb7R(xg6gRpH2kov7P`g^Mf!;gg#Pg56y+|7HfcFu{q zXz#=t@Cd)3>8yAP*N#Wwr*2bP*Pe(XUscpcYYl0YglQm!vd2LCFWu+jWXCOxT_#02 zfc2hdWfog=m*=9a&|n|9G5K6qR%GTzzUySe*L{IOcp+7#ujpvOwU@RcVU=EY_=RrOw&q?@T}2kGXndutJ1SDB^wPaz zix1cmR_2k?0%ep(Ns|fiN7A54#`d)jV;OiH9_Lk%ZV@c^utA**FX^%OaN8tnJC*&P1ckPc@YSy#=^eJZ`wvRUb!4#L`6JOXlGfhz;kYzHNT6pZ zwb?PM8tJW%ATQgk`x#i30wE5ID<-PTG&DSf=&$-MQ(Xq*S+~6HF37SRD}iB((M0eV z-TPcg+YxY?X*TlaKht;BMi$QJ@Q{7<*nAm%iEoPJjRgCVW?OtX7t5=JQl$(=_g? zquOpxxL`dS(bA2O?F?TMo zJz`vZ0;fRg-BSe?U*w)+kkrynb{EBXBk$9d1Mm+kS&PH0?64^c8=s(?w2*A4-WY+n+07VsN=|06*wu(DToNg+ zz@jb(Z!e<|W|O)8s21_eH$zU^Gv?yNYzlSbv@P>9NG%2@uqgk)f; zlRR|~HC*|eoptB=#k~o||1Cv1%wk6QdUf=`_GoxJD$jo6920t)n3BU+p#}}2>O$4Y zvNrn^t;^G+P){b2qoMsCfdMN|CGM&wxB`|rUFT@VAd>`sTE@iBVrH0g{M9Ph$jKQ^ zNS#Q%X!$ss^cvR}6a;vGxGAdtkd*I79kWhDFFBW_`A6-1(dviJ4U~|NTIX0C-DN00 z9JPh`Td~}!Ji!|BJ%$!aE(9t*aHr!U7<<{&nbyKpetFht`(d|VotnWozYl&{Jiw`M z`N>PVv{~O_bAoU_O!%x1Q%p!lav|p{QYHKY8iTdbq`oLhB-RRrUxqTasA*1v^bhm~ z4x2TtLaUm@f<<&5yn>qTjkPtYJ@Y|o+O6rX6xl7e;YXzJ7`Hx?=N4gL~IX8(m3hjhCMee*};v`4W1ak$^pAPOfv@ zWK~!hCaI+oq!>D+u}mAUShO!DAy(*AdKSDBPhv6~ruYWZUM`WPw!)^|8dl}f&mVQJ z7clnnT@X%Y3bu{*2iGj=N$O!)rS*%L6wR?+t`>u7!Y0rTYLc7no-M0Vu%ph2?$Qx5 z(O4h$49tkvAz8%co&TLhfeI4&xc9kKgJU}mu03iFF|3TCm~7J*a@d?w$}~Df*jbY{ zu8?fbv1ixNXe<>|I#lB3C)*S|4zF$)$AXi=_YbDb=*}*iHzsuxtJOtx^66Qw9?)$B zI(Rous_%lWQFI^JtxBI&Z(K5>`x{5+M#?_S2GYqJpoM^OK`I=Qp>aqbfR@b5eJ|TgJ}uGeucGT@!99k}hRG4*t@L9EG7wdoR|* zvofVW+DWG;i$vuh?!3cUm1DFpC0T#p+mLvuoUT=t)A4j}_Ce&1MaI?gOSPS5rM59a z(S5rev{)0e%w2iuj2!Rj2Rk0RfT*w1>UF8)C43i_G?qA@Jh+=K#7#lnjaM_9yGKXH z2XBBF9?h&|YtLzEHi?UDhQU$`Rf=)J@4^q}Jy`Y5HXijSy#aw}uCnRmrLT7YQPT;# z4zp`Y6OEIx*paVK%WLvUM#N6*Wqhy)Qo7NofXTvg@EHunV{@t%Y^Qu}ed2&N_QMOKYX zo^#3G5*KksQ4EXX%jA~j@h_z6>*Ge{+JFQeCanHW~>+MYCy4`kTSy`!zbD9O&j zyb_^#Fix*F;8SX~u9}Mjj)pk4^H%o_US<8L11;Wpw~VdkVpI3Bc7YCd#J~~898WSw zk7JD0Pf7#${Nb-9-BlK-*rdBh+HoIcBC z>xORwbhtMDM&l*Vt~?7Td5Hlh4z@v2ms9&i9qd+Se3sUgt%~4iEMC4*^}uYEGM3|Z zU>^16wlN$N>FH>B^}bvj{7iN1!gf6@W`Sqj(oG>+x)p(v;--6IvNM6{q|PjVVxw4X zT!R5|Av!$zTqs2iE8%$B9rge<-gB?{%JgoK*X5}Y-vP`VU$o2-Ny|E!zsh{QI3)rNU>}N4;k~fqiaZboR*65&FlIs#BDm~!F-YBF%di9{5<8P*W z`aaF|x%KF#9%Ve6Ep*RO@jA_8d+*RN^x1U)^_#`7F-B%t*OyY=FCEPXbKXnJi$ztANvp}moA9bC@6$6 z>5k|ua_FF{9R&UEK~^Mmbvth8wJDBLk6Th|@RpDaKe5W|ePKVQ;aM6Q!8jszaOve0 z*p|FeuNXr~jP}uW7Lt932SqGqr_9-KwuJx+CvIj+uEtO*2lesZ8PCVhW94XWX;H_z z>0-oSqU$}31{EhR!RhsxIt#`6ef**phYAT+YVx$(1B{=JV~x28ad`#`3UYCAB($=T zJ=@h8(RSP_idU_IKjEa}1=4(WRZIw~INV#&C1gy}?v2UnOfV<^RAyK5ghI|qvYTS( zee#tDZ~yYkB&KfNbF?iTy4v+s5%5rUzq=FSnDWP>uSbnStvFBLpcMeXY=FpGr`bG= z4H~O>XzAU&i-K!;UIWB?eEHHG`^@!crs)^tDp93NQ8QMRsy;q$WJ}zI?R3t`z+S^I zkz;#}Hev*&9h%z9;rjM)bY)M3!3>anSPUyJ$XV&7&kJ$FWG^231)V6{aWch^K3FY; za(BgA!}A8QQ3Rj)2`=Fze6Oy27+l_)em7L_PsOY7$wm%k-X*>H*27;lr{PjC^aj+) z6(RIj#N!ut@bepY_1_S>pBUZ0c>Wf%@+YcC&kPXBG5%7>vHglh`5QI(J3IJGY4>Nn z-JgAXfb1*)Q5x$%@`CJ4w9Nk&fC$L`XU*Q9|NgfC#Gm>9Bjw)TeS3hcOl&{ReZO1t z7=Dq1j4Z!Xi@%-y?*K$V=6_eY_jmM=<xMz++^hr~Syn&Pec)m6qw(8G!fhZ!C+Xsg?QP8enB*?eJ&H=9fV44~T>BXR1Ac zIswDS-`rgczx09ffJlG8AOE^Sc>+N30@hXzhE{-ge+2MhzxX04LjzOY-(!yg&M>nx z(|%+Ha9?bI>BGjx&h|5xtgam(6TwG@pUj#3p9!&_o%Jh#&hoRh9<82{9t#V*0iyvM^G9|zLqj$rW+n!9wvR@J zdio4(fHwR+#NuxmYz&O-oV9;?wthZ;weXL!eoq2KTKz5BDxmTIB?%w}WcVcp{A&_3 zAPMmNCj|nWXTOWp05k5tln5Z5enYbUlE4n&X8u`etZW2;$3LkCfZPBcfP|l8|8sW9 zTRZ4F7y{1xtX9!Z*UJ89&;3gJZK}!wI{F_b%AfJF|08qzC;0Z)xy|w$iujLn_;+gR z|Lh$8pO5R$`SQ0;`oFPe{fP{u`}>;3^4rY+$8r5d>JiZWpB>l#`qBSaYtz5A0R4|A z3hPfg^pA=1U!13aeTNOO{rpcb;g5gb1Q=L6g2YkL%X}_NSZP%E+wuFxF}3vQyn1C{d~`cb3Td7V1Xtv z?kM^oV%79Pd`B=O@o(|*b96g$1ftfIl~V~x{N8_%BNK{=f{Ckq5Su|AB2V<5QnV8a zEuqEpN2Yg`|De;CU})-;<+;YQUcq@KnOXhJ%j8QJg{&pH*v-Kr;5>uz=lMIYfSdCe zSmm@H6cj$2MK;8Ymc7$%63-YWzB@RDuFg*P!2PtM@vO$>q|=%YtDY(grJwiA(EI8( zavK95o|2CzwcdcQ$KzE0djI&VcmEi|KMPM;e_#Ioga`vd`~JR<0QT!&n=>HHGz06e zO$^Zee-fSoWTC&_`A>Ij&7VUBXvxo78GcpsNB#c+BK)g{-}C)Ld4Ia!`iJvBTlq&l ze?o-CWW@yJ|0yC2*foFMeFz%b>)V;yI9S^OuJyNlg6`iIo&v1DKRe>rj_PV-_*;p8 z7oPsMGyg+7|6xzH*Z+BI#KI1UsVtytBWh@BZ1TsUiLRZ3p#uR0oiuFwOcq z8UFLW>$eEgeE+qJ{+C$cUp4(PBL9*V{`k{^{5Q!d6Tq_j`w@T@X8ia!v@ad`W@A0K`Fo8{DPETVK5#*wU)!vE8sfi0lkA->NJpCet zuQ>P98|GT)w9L=$R?F`kU|n`BAVcU62$-h#Sqeej@y9x>n_8zFaXG;gs76EDi9whVy zTrj84_X~nd6OC*bdT}Iw&k9gEGzB_7)5==+i(5gbmoJX2G{I*{$$%$k4%snGfI*c| zG#l;PD)631I}wT>5LR}!O>*mar?`zMM^kHS$km0btLp`S`l^eZiMy$^RW|y{DsVBp zy?+ju?$<7Deb5HNvFgs?ZSZOkSlkUO?*JHCr&BEiCK#6!-mV<_{AI^j$SJfS2(2o3 zCfRAQA5C4+-agE4KsKOnD|iIS=+Dm0Jayive#GA70@!SZ7=3ZAX7%FR8)1 zvXL8MYC=m?*|9PD{8sD@74I)LOfYWkSKJ=n_%+B#yMf=H^_RP2zB+XZ*WB1_GV=Fm za_N|T@y@N(^zrt%rFYIofC57Te+Q~t$(zhN<9jww6vRziz0P+|cXxy1g@GRbe&W;( zB^iGO&glhMXN4e>KKs)W=w9%{cvmM45C%+rpunmx3%xrV(z(oyIqS$N{8K8pNfYY? zj)N3GJ;L?T!_)X>s_|C}lAeA1`!oN&IE85?g^B$~_k+&f%S#Gtf*=tvn4L{@5a4bC z28Qeq3aEp-Gw~LL`>c#-kxHy7IB=Awyv?JG$1>A#o|LYOB3U-yx5|Qu79)C?feZLe z8eV>agPZryZ-p;s@o!-#ou$t_P;cv8ltE5gu(S9&+Q2|tcFdjBg*KfaE|x>Oy2hqUSe>-B0Uar zR_EIDFwu}OATKJ%YR0lfM`hnmQd2qws?9N9+{H*qPz;|Wte^RNc%iUhFMu`huNz3g zzWG!In!dk!9W^Atr$gHH$^dD@8-q6q<_Go4C=H>AmU&7#Y6R~v!ooksze=t4D1M_4 z^z}IH9QD2dy5Y^MVtz=YoQHiieCq(xp|{3Bp=S*B6AxgU3#`ZKjLy5-QuU+@ZH#kz zj?UWmjG2|X!z*Ao>I3sXE?14P+fA{mu1(NS``UJ7JC;~Wf4D~AT%SO zUa!8a%{e~RCO~KOY$>+>(jb+!Svv8mdAmZozp$}O5f-9&0%HU_IET#ZrviJ(f5y9x zqsl5EVvyZpJn;iQk>2?uX(+dw5T75`b~DJ<)r$Oh1&m#I1a#nWqNPk<_;}ifP!Ma} z9LbkQxZd`(3(jQP-LXT)Q4mFO*A{;uLWTVjS|gKkeL@hq57nMIl-o{ueEhDT@!E8u zZVE1H=Y87JsH4wY{reG5$!}~{Ya;v!C;7|XVV|}SeGDigFYR&3xvz!W@@k~;lw6RK z3i#ffA=E(=z|boeX-n1dh^>#coL)y0S+UYeA+dWtkoc$}YS^JmTkKrtKBF}eshF7ELfgI_g z7Q{*G;C6$c{nRnvwH$c(iSi%q)}0aJaOaUy>$@njsB-(j5hs9cnmmG%A=w!LLZ7H~X{C)|w8(9gCD${hbY{DtSvM&L9_6Om zs=}d5#>KZ9ipOH3ouKe{+WGNQLP|?9QZ|ay)kl5rQ}6?{3yCACW6%NSQn!MX$kN(` z15IK6()>=h;+@b6?FK4y)G0y7jkK^ExzO*k^E*QeIOb}N1EKZB3TQp4+-=6@lwZSn zXnNVp{M3%XQ|0QA;LwgZGMk=U(rhPe415}NE!!KT zp8-*t2rf69IQin2&?4PKa=|2i27|!)C57IKfO%^td1LS>eKlh!L(y;hx|Bdk`ORT> zPFrZc;SzrlMP-^zvceFcYG6z&TQMys3i$rSGTS{0ahoLtI1_V(+R=WbO0 z1&QLL;ds0KIzlTf#)OzMr<2g3@0}mki|1%>nIL!44)S+op|cNSt%2b)ri9*e-&Ab0 zEA}$(;BAc~e!jx4N4JcFh_9F69kAQTyCJ21_pxtQOiZYW5M_i$-+jOLbjn2KsnPqAtqIB!I_ot5l8 z$0j8a(kmlK*Tr8D9j0e%k)J~F$`(q!K*$P2`qcD`4j98mBucrgBN)ipN`^e-!$h#A zX{boabL+2uNSR88xm}kW<^kLt36d={R$hkqomE{T{ zbAMqO>=R>~f5|4!dTKu9qWcVYPCs2KHe)V0VyY76>I#I!KCg8RahpPDmD9P20`)+m z-Qe7Rr!b+Y2fICLRhWK;f|JKwRsdcECt6Xvu1BdFK>qJ%dnDLGzl8)*w_Ob9=9thE?aa`h^ju_N^ zxeEB%1}bDG(;$l{Diqw{>}aL43itNw z+^zNVe0{to z+iXQ4@ofXmvxN>eAb~qaUHMki7p|k=s4SslF1fDhGe_?19t`{c*!vFnsLHJUUF(Wn z?5=ukFd!MGPXO2csm;Cp|mr#2=sDW5DvU zrysuYo~z#2BEHdK|DDn^`PpkveYu%{W`^*3QcKO~L zow-Zt28>Uzy)Z%i5f@s2%O-gs?zG?jQ__ufDE5!8}C^3aj#W3{`<^VzpA?Ss{WOSPP(nnDId?Rn)JcKdH(ZXXsz6L(%H>#J^A9n z7anrzjjR9u%eDD&wptae7}#y{7mp7aH*V&Sdww*% z>ajyVZJVDs=#$-DRbS5D=iAHQ?zj4*_Qyspcb|6G;Ax*8w(9p6zpr?3@g*&1ytrG} zC6}MR&5JKjd3U9IRQTZZneV^y^;@rXeHW163wFNxzQiu68=pGmlx^N0^pNvF=gaZS z){b1;dd|?7>NoD+_4rp({>yHC^p>mozyJQzGn+Si@s&C6AM)H!Gp~5~?u);==CjBv zzVmy}=&}8wTaNB=*~#exhCZxBT&yal>mi+ve()FE3bfR=0`Ey6p1nt`oBjy|#Jm z=W~O*?=`1~Us=0vaP>x0uRr>Szd0|ztIKVZzU}K6Fyp?K67i!FW&^V_*+}!e$!t4bN=zi+y{B{<>tr>1nDee2{Q=bhdB@~Z9oy?bKKJ=3;* z^rsn{+=~KEXD@rO@Apr%oO$5qyFK%8)9L42wfFBAyt=mjU%{cD9e?+d?|!{(K=QcH zUj6#RMHe?Y6C+m7n*Klvkl<6>n-Bi)=Z8Og*!2$|oIJJhrPmJo;^?I}_1WmdbE3Db zIlulD&-f#UB%V2W*_&s+u)~bKqD{#!cR8nfw=tudUz&Ezg8jFDaq!AMJKWHHNbiQv z=I-^w8;ic)zWwFidww+H_$Q)!4f^SWGk2TeZu+X?V^?^a%b)tsNmtMN>B!Tke=)t` ziQ8Tnzp}pd#3>*DGH|DpzuK{@lKsRzY~h%|QBP0lxZ~k97oBwT-LLJx*P@g6Ined< zem|af(_vpPc=M^r*B|lKpxK|ze`brOi4)&@v)i=mpS%3ot-d+)uD$Ml=>1uH-v3zq zkl#PNb$(*pVV6#MbK?hgn!Wd&V}Dw@OI^qMyx zx%t&ynyaHfpZNG~TVFr5?^PFMS3bL6?=H~?E?n@*!P73{`gIoZMo4N`yP754Fep_UC!UR`<&%(ZuG+{cTMjH z-+06`Z;t2a3l`nf3Z*DZ+NzUhoTd!Bmcv%3v@Y5wfY4zXoVx1Q7S$YtXWe)#D6 z%jX;$_3i!7H>OVS`Npb!Pip@n8QP|KsKrdtAKpgm~PBc({>p7)z&>GUv|{;oq9ho>GpSyuXGRi@X52cKh|~s zqM;vr`TY&)54&zY=H8d~oAPt_^F5JK%dUCg+4hI$_dfQXbCGFs!1C8$KJVQbPyKN9 z!pG;|a@en*{`ld;H*EY$|Ce`69^KN>QGaFd=Hv~dcaH6U!E?c1ZaD9r1y6U~=C-?Q z#=Ly-@JD?=c@BT*(C()^*Z-Z??uq7A1DuZ>wrY6&-k)6b*>i6M*4)#v$#%OpyfpLF zcQ@X~-9>5s@xV!w?!N8J$(7DUhc<1}W3O(@zq{`d^+gCJ&8rN*PcEabszc`~ytM|*Uz3#v8i?>hz z)_d!ai+y~{c-|zipqtVLRzrS?b z+`u_62QPm9zDriP&bxKy7bB~$JgvvjapTT@?TqO!9dXfbhtFQ_{@2j6ZvAEV6KCxd z?fTlPtLNYJ<|Ehcc-=wYFF$MLz_%B@Iq8PYM^131zJ1}c3zpRld+v&tPw19DxMf^0 z?R;Xd`QN?x%Y$FJaBPu_FaBWk7(e>Hmf`-yFz|LW`M zcTN~QdCf*!qMP&yI~+a#$&a`1*!kR=qt94Ye`)1`|4C;)y!3!C54<$(|7NeVuNZU0 ztQA{%8=tu3-IHJ3e(uv-E`8{=-*1{T>Eo8SXCL(Ww(h2H65LuD73j>@GuRUbkq6Re$@Ye(^EaoOpNJgJ(|P zY|eI1ztwh8)y8*yeCwdwC!Kfni-+$2kGW4g-1WG_kGZSMv41;r&vQ2Y_YF%%PP~7& z#GrT1UpVfh@Y>-QkDjyFJms_ZW={Ixm$iMLe{fpsm7Z?D{+c+c?`fOOom~Iw;*o1# zdiwN7Pv7R~@kh)Yxcq>@mt4B+^7}5>>*j~<-usJN?>T5e^VpH0c=FAImi@f+$juLl zKljPb-(LRHvz{Y=I{T|@XZ`cpu&>8Kr=}OgmcAd3R>VHp_tBq6elqyqUz{{7u;!Yl z_j_#4DMOWuS2cdH?MJsgws+U_evL=|bLWk-pIzpgb;QQk{n8`h+GgLG_x7pZZ^r?{ zR?S*Hd-G|FxBZ~{mCgDsnK@wf^KTE`@$kTv>FrJ)f5E)hcV2Z~%l_A#d&Y4a%{=hr zE>lB}UpIZ}?$?fT{+7M+_Q;PH9^ssD=Qn-oSDdm-*Zto4_S%2!aLZ;r4}J0TL*ARW z>~FjOaQ4K19slXSp6J-`jnkHnsEMv!bluU1)_(lx#z%H_Bo|Ektk3fsU4BAM!=kE> z9=+${Da{|8)BEGa`}X|d&_n0^yxSdv4p{q+bLo`N!v`#1vFl}PK7Zx!$Bz4;dg>Op z?maR+?eX)DuG;&TEu)jKoj&V`U*_C7YT&MqFaNyTz*8$fu6}#%Nqx5a^sRTgcVE`? z=pVn_^MKF~JDhsOdq<^bSM+^r>05V>+-=C_kDRgJo$u7`IB2)bM}ZUXcw*MncTa!u z*8{&hZO))W*8aBn`Mn;WU-P$j?`!)0z00>xIaV)DjP(tDeg7K{|LMbj{yOQJ*}o25 ze9qaI?fU$j=Cl4i`H*FIx5VE2&r6S8e^sByX6!ugy3J}!_&1+9@VSTg{Ql${kNoDoqZV$v|6Th(dB+dCcDZPm_k8#DZ0~#AJFiSQ zZ~M7pPwTcQdEI9RT(x`j+J7(G`s}%9pSfzQ7cTB{!j@pyY1CF>O^J|Y!&wsG)p|&euZaw9-Dc_v+!HehJG4=Yj^$VsC zSTn#gWA(u+XN{k6!osJP?mONEX=i5Jg{_r1a*3RpG%=of41Dj0EwcB0 z=RbMtDJQ(@J8kU+r{BEd?jAF@o3&uV@Vc|d-_qv|@4`*%pUrI5F=y3@JzqZRyVH(2 z@WEq$Ic?@9FFn(L;bULkKV+8^Hu2xN_`Qe1=T7_Y`RAQ5`>;LVJbC=7Uv7^Q+PCiX z@}UDSuehem%uiRI@Z`wM+Tkl>LxaH>@$-)4*7r|*`PwnB*Wuau zd)<=S?(N}EI70^vJM$&)S+&Z5_Lpz_=Cn!syfyupu3LQBvf{oUmoMIK&ArD@duH3& zt`kpubokXjy}4k<8=ke*%N9=c-*MvR*D9=x~9 z=m%H#TK(kO=Nz;5o_OyuC+~mA^7^UUpVGJQ)Xlo?ap<*g9rwhvQ@VGXz5D!YCx5up z=c6wevgN!R?&>mji1KpqKhAf?&+d82;_V(e>dnXcJ>+lS>DyDjcyr~idwntR_iILv zIOm%QBj-Qa^E0(hKh4_uHe{gow4-KcpcKUeYr*}=P?s)k7<+p7c*lo@~f0^<9EmvLg{uEc&y?3gAKXZ1s z$NOH}_m!It>VNI+m+$@dk6V3scW~$S>ge~6{qRiepnVTJ`?p(e8r1R2xG~$Md^Lv- zs5$(z*Uwlq>hTx1n0xCX3tyQOYx(xX9Vhy_-%+#kNtZ6@_t_8czrW(EFHX$vcX{s% z6ARy1{I@mFRE4H|de|xLKf71HmEP-?Z|BU(Uj5x|o34Jo-%rEu+;-1h-l+TGz4!k1 z!Us3(@p*FLip=LmW+wt+-y5`BfLZObo`LF-)wdbr32WCz^^^6&HKm0!E*40=4W1IKB|7dDt z*uTcleP-%SzlWdQJ~3nR)~&nTI(_A<4SPM8?7P*M(@$$W>fJ%JH$9~zdGt%;4n5@g zr*0X$!~S0^9P;Ie-=e3kuKMp_*XUPbmp?o7tH7K)&!2q#z)z1jApTTS-OO)Sj(u_O zr+~>l%FFbhR8Ou)DbJK)Y(M1F zUag+VM{K&uHrr26O|Dpe#+nQFUHtsSXC8m;l3S12cfjk{R-JH(@5kF-_pH7Dtq(@M zdiDk9H!OI3(Nl>z=fwvtj(_lvC;G0u{q%vOcS#PrMB--- z*zvdar`zW=4w{o0^T1fon(WrcY<_>&S2n(J)UMqQxZsV~Hl5Y|yGtJ&^TgHr?RL>m z7q!2#d1q^-;GCx5p3y>#Q}gBsqt;obh{T|MQ% zZnu26WRvUOxGlNOGp~OBz=vCHJb&Q`_s?rTtGZ{?%Wpd=w%fiJEg3)MmYQ3iK4I8C zOCI=fV)N;vcYEuX#j_th=yQ3-^DQqP zbF58#U1Dj_vX~K)M!2K#_ae|oM39Ye%m7zAl=T)X(SKO}o?Cknw+dimXP}R``HyuQ z<_LI6Vcre37dO;i+)#VbU1!9!Q19{O>9FQSd zK@r%LPGD>+35KOD?RtU5tR(Q4He?My&NH!6g9?D&Knk50#ip|B*u^qO#&2DlgC?*8FSIFW2JV$lG*GHhfv z(+mil12rl8qdGDrEes&IxMM&ckINHqdE6m1oOXG<2fAF{@t?eo?x}ZVThnc^NW3u~ zi`MtRS4YG;+LNhhnxBW^6?+tbgwbpyhG&h6wIx#-%u*RKV4zYHZ%Bnx9ZH4L-rnv^ z@pp-E1E)+d>JF$;&H=C)LtQ+R!1ys5fZf~LVo{*;!ilsA;?658X}Nzg3p5@Uw)enz zDxJZ)t6({T11cVuioX}@DR;-Tphon$cpBI&}(1= za+q@8^KG3zM*X*Z+gVM+mFuZAubu$8PAX5-X}+X|D@gskxg8q0Gkx1$Cwr5p7o zZADal*~Ak4?x0~sR2o(-s$T;*@!^ zMqSA&+TAX@S-V|9!>m==9#XGV>013uT@qF1S(d~=*D4K^xGG+ncs_t1i&UjXn~l2R{RZr zD(WgB+sv-uoN8ziIwR-bcuu-J(0LMkYI%8hWq2kAVy25tX069% zr^I>Gp_-&%Fn~rn8bzxVU{T^U#}3iAMjtkFD#)0I(=c^m#xvo4VxgkXj`vvqGuLGG zxQr^D&~lKhD;W_Nj)1Lco~Dfl!{bt=h-$GKwH%MjqUES-plLZ`iSZhnWhls^Mrm8i z1g`I;?gY@Oy!@pxDu4x7#P9N2wRewH5LIFp3teE2PMS+W_r_qlLO;%YYWaln58h=K zI{X_GJYTGHyIOjH&H(s2{+7iV(~r*d`2qaA33QvzLHIVE;s(&SI%m+2(w`bVGL$k`K!Y)5UOiqeCP4m{FXk^SLYoGhLon*NO|uVF+PGmfhGjz{JOd_?f@f9d zOqPSl6VfSx514-ZEN6?=X}*J}%M71?ysVC$?9%o!-AsQJ*U-C4V6nJq>3N->2HhXV7k3cA3u+{}}yHziu z#yaw=Xgtp1l)&6UEIf@}=hbT;R&TP70buQeUkq zdKs?_e`dGRmRIsg3W z2j~oSFpZB#&+pY$W}OIlOQYsPVW;wQ5WFxiR5#7c{H&X%9+`rh<}0*m1L;d%1)ePb zXwW|6iLuJ2FIispqi=@3;reR*5wbU$6`C}$Gn#w|gy^1PE8kTaiF#wfHS-r*Go222 zee?z472t{TZ_E~$B6t~e&S6;}3mezhkv$5$IXV$m)5usdTVBLF#Gmoo_)neW`6;Yx z*3tubEc}@mq?uxkwu)-Tp{U9caRlU#R@6d&YJER7EUkuSWq8WXmLRGCs)mc(CA2=b z%EjGoLAa3GZdJUy(NC0o_HNZ5;#Mtvw`v``RUO?O5CY$=DxlEP#eM3Lf~u~Uqe~k? z${v1!lun{v4DdG|H(jGvg`r0Ql*2zGEsD~w4pDV9v-K!-jVRTJw%K(xN_AbA6S_D< z{C|9<{<{-POBJaiEmWG&SRv4>3`lkvi5ulB3^Dvlr^|;zN4SZ;rvIgyy5QCFQkR1F zw(v->C>RO00ORm^5#|YTPtCfh;utZyU(MkDJ(RlfN?lK-!u&19JTlMg;b)Mqw;!L! z-R0(I#lVG5-VusJrUv^zb|(k24g|R=c((oXRLvb~6=@WY8{FB@Ods`3J-GIMu<} zE~bwPb{4##ve;_76zr&^Zu2?wc|%UW@M;n542(4Zj?jzzTmfw==4+ij=f z@xa*E7M{3(%W?`mV0B*fkXKJ4xC!xu5PedE(ex6c*xH@ZV%UpN}^h6A3;uq)K)4Z0&9cMN(J^B)6O zp=a>Q3vQyn0gFvc&&X<+qnNHJFgKL;lPhUIH&>;0Mo;Kc{!%w|6kp(wc!iIa$aWAx ztS7dcFQ5gc{r7vgAS4RWsw?dFnwB`?@rT2{m^T!rP(!RT5cWrdu7+qZ5~@VqSa-1{ zHoPO>8k;Z4zsSbnkL;pQFiaI;YUMnS+vvSfrJW}jtn8GulafdmTGBhsLd!r^SUhn? zyl!zadJlw5;IrcNc!EBZu624{p-LzSSZ_ZXZKs+@E{M{=#W&VZSrX(H=NB7^hXpl@ zM`9zYn3e;FP=~ZsciS{vj~FfrQSx0hT$r&q+VpJ5F5v7)9xD#FqA1mqP;@yHpBPhW zvn}AR1t`&5>4fhP57Xakexg~ztHaP=F9#IxqZBe7eByy#xj+@hk8{WbvvuS3VvRaC zTMuTd3u890G*n%A8~1BJ+#MeeUpu!=e*hl^rw?$zzMdbBne0-Df8GA zhY!=PBi*Gn321K#w5-3-{3zS^>+h8#F;2(f_Q^%9Fn(?`&43HKj zuqyr#g9>7@gIl3(weX=0CnP!L-&OsUf5Jp7<@<_}FY6GUJ))JE<*;NXRszkc@A(s^ z7Aib7G9+zvDx+++y};{nR&uieoMzh#z$rw3g7Tz0I#mb;0Y%y43_|P*O_^N@6=Y!G zd7439w8pLQ+E^D@AeCPM{oz{B|h5s&xpSAOnS3jVmm%W>^_?IG7TyN7l7PRI@@^fQ8l|Fy0 zF%a;CqppD88+A2AT(QQ6%0^cR$a5w?+vA~OLilwi#jZ12cbOWkmUQCviP#N~f~IG) zo8rK*PR`o2JQ|Jxv2aQWClbBnUTT{sb#=|i2mr6g*)6%#^;8Ov6VA5RCDN+y*}XP^l{$x|{(r8$;pQ|Jh_$1~0F=)}{CY_6h& zv(b3QsSMAil$fUcr-kybw55_0<546_g zLy;y4hUfH!JYGL`vG8uvUd3D55;1Zjrj-#@wMxAs|I*a==%s{}CR+2>aBC!{G^WBW zF(jBxU}|Uyw}zW&jwwo5OOPE9i^|FHeu96wJVChqcthJ_xDX2g+G$QDvrWy|KT3XM z{s>1~GKCEWg=6pdDZK}x&jHh;Bhs(BYN(Lq`q6M;@GyWI<4svS805(p^-}sFpDc=p zpu|26JOS5Y1ddb7$bjVZd9ipPH6+Sq6QIUSb2y{4B%|@h4yLH4Y&aE7h2zNk0|_Ay zOgt{lF3NO$4=1mnM$DObODvI$Ou(!0Sz9uVSF5z2Y}Jmfx-cF;Tx()sK&WC44al3r zOw_eZWAR~6rILwQMMF4^r-C>!4?)<;1n)tK3kCX&!&5-+OmSGX$?z#jV$oCP1wvw9 z%-Q5rs%nPH7t%Go3v{OYKSS(SX(05fiZ*V8%STEK_&f5h8&oN`&y8GFxo%Cd zRuGWwibWDx#3zWRTGOWa67}GSW9h&RuB_Aw|Xhye%ZU!pOqK=|y9*m>9TOm}A>;8i>hN zG$bbxJEC0-QzE*8BSKPw^b=)bHbI2KWFL>EoeB<}<|AS5$0tJwA*CUkQKHG#?j*R| z5@84#a0@(XBAiMIr6m?lXH&$QAz*2NVh~7Ru9SOR!U^!f+uHdPfLviXwuLrAjH3}m zoNT8vilGQW)*i=%o5-(8a^9e^(4LD!-itMrmKY>ml-)(W_;@4kmBqZ&g^NXV`A3Dz z=?eJ((dHA_oNTB2I+W%x-qsLm3vi+sG@c`3Y`o(p`vD>x@oof;v0i$Rnq%-e&(hg$ zelC)0S&C;1*Fjzvg3mC4qBFF;V1tiA-@Xk#28(C91RsM}p3{o*eGEqUtLkI$27G@k z6|0X^(%AU_l$d#s}ti0n}ZRk+_RBBi)_YgAgZEwL0U&Q@th`VM5HQ0@v!D@iE%B}@8!)a^G zW1W?7452rw^dmJFe1>MfM@(gJ0!RTJ%47r4vf6z6&jA!K&_q=}Wpz z`^Kfnd+1|z|HMazYYaDNkIE|^jr5X-pu8GE*0FSc^tr`MJWyl2h^}avqjhy0wQJg zG8f`;rkgiVTz-lR80aMrK^3ur0?-+B6=of!IP~@`N^!uXp$|D@r^}}}^UWJ5t^mau z=*6Dmie0e5=+t%04MwL$Q!c^i)Ui-y8=dT;v1By_fLi=Rfo&}W`zJ@7%SIJyW+1qk zZYW8s!o1M=V*ro_o`6?4AmQGY5nl2rvX7f>LOALW%0)m0VIOjPl3_@GVBl)Akqox23CyA9#>{2i+Nxm6a#d^@R}lFUQaQ7ST5H%AJszn=0z~-I=(m%eKb<}d zFR&d!060E0>{Pu|(9K!V)qdqFiJc&@d;QCQ1cQQE_4 z1z}(cj*HkJ`^n+vh2NjH9#Int#KsXu!XNZy{^+!cOyEiJ%a{g{K{}3raEBt|rXui; zFhd01i&gs9;gpvtUxwWtAI&${^3+6H}CT) zGYUCy?mzJS{>c{NVTXs)TAVXy8eZs1!2n=Pou38)9`jBk0KbgH1DCt_?z~Uc7T-&O zI;#X`9jc$@m(>4c>4#5{@*u@E>qco<@JL!R-r20>MZ$ z>WcXyjiGQv4k%{~+D>(T41-$%KR|qQj9@K!UlmZ?0r=+%tZRNBt_J$cnSj1&g`Qks z$B-ja{*HBiCcqHI!hxtKinL)Ig~6(PZa>W>472*?d5bu!$Ek~&2cog2T=mgd=hvD z=rA%hd5`tCI9SNXCbs0g4X}tvwl(KAQhy@N$#kr>1XaL?Tt{64lruA{?)guD9pHO2ZbMGGic z;6x6a7u6!I|FY}6BB09KH2Efm0*6Lewt92g}HMrip8AjzT07R!ec>Sug5p56hy(d(&+w*muL6HjL> zcL67$LIm@EF_uf_r$D{2cvEY!%Z3Upo#`@xyfY3AGZ?^dJkxawffq>**?pP@7zlt6 z^CH!Fa7S%hERisNp-Pv$=Nx*ufs9SZ7&Rw2E{a$vMMeFnDnOq!Dh1-AFl5-2sh-53 zGk{uB$reGwrTw5S|7n zyd5h<|H4E_MJw8nOHS8NLj}O07^g!PtBP7S*u`wz5_q`;=75w?Z6>zd(}N=*L#X)< zPJZrDf(2`Qf$a#~!GLDUSkibpXTg#aM3e{6;^M`(-0kxj$j?4}>j9ey1btAp2ni1^ zW(nlV+yWEvO7+NzL+jjxH%Ai4pP?+ba-ZI#5jdO@y(j0oWP471e}GJ$h-N z0)ZE)_yPQ%2%|Xadq@(}WEv4&G=@Mts1bY~;!my*LdC|L*|S1pGv?{$%UadvPy$`L z-D-WR0xGLqIenSr!XdLc636b-T#VDz?k1dHYXog&06fi^EIy73lxBxqr;;6L&`mca zq~ZLKzO=z_Z_!Ch@Z0ODz2*DuN$a%u?cFFJ!E0th|2BA{OG1)4bMK1^- zfk-;l&AKqFAdvkiNCb$crS=K*L15Ji3HZDUG;TzK1s;s#+j@5}sOe#bFLKNj6l|*~ z2-VB4IH4+15FtmA)1eu;eU$zp0~xV6a#$hB2z!FCNOLkFa*fpdJI=0AQj`RT1TQ3< zseu!l6sKH00(+_Q1q4YHm^;W31pSjvi*!1XJ;+K;f9=9#Dp$kGYd|9FSKB??{STNd zOsTGtP+C{F*CbZtuA&5)&qrmuidYiHW$Z%*I#RrS@FsJLUnAwv$OKCfHD&56o=pj- zmCSy`BV`t%@O5ztuVzkwu%WoNrD7FGIH5EWHQQEXdBMF#;j&&bw~o|Pm^5grsG@@- zNzh-i9mE1)Ycgt+1DKx}d;!gzvnK|)NA2z2^@u?Q_le^MW%s#WCQktsw) zTgJuEdJ#*WC=0>l!HoabBCz8l;bw)^e}*je`$uMbSXI7aD7g*}lsA(QjL zf+3Vmpu-1O9Ndz9lxpDdk(n9kP$KbEB#S6x0}Q(faB0U{vEHfAUhTP2*p}dMd9HG%K_@7 z1D3LIq3SvY(!}NXRrjjuH&uFT4q3C`6}}bnm8;1QB8^jua6$ig6~F=wSyXn#TBak- zF-jpfETdT}N(Y4G%e3TjOu1_+=dbxKDOO_7)a0dwLg$olj0UVpIUjNoQFb{62at~ z63GTAA_gT;=zqk9Xt^MCG#j#e8ju_KwBmN!prmVkPf_4i*Wpc6CHFLdppjf9=~2|T zOyATx+Bekzp>lQD$@ZirogbOUbY|n}mUJJu7Tb}WFN-jkbY|5zt4E$3bg4iF>F*|I zEjHe?l1R@&da~;53`bEcgmiOI3nk@B87i`=!p0&H4e?A%I6XlcTDb$jj8dbZ!{-bF z?2boZ7_6$Q*?Twl(AHb92@wzt6Jw()3P{{k83xMr#M`n6 zGJ$EAvk^YEt0<1H{YIc-tuF5+P5pJw1~!8DV--uglD9%~M>2O%Edsm5foicw3M!;B3-X`8|8NbK2fGfo*+Bb1?>57``s2hNIOExbqi{bWdOf|_6TJXarGohzeC4u zh-KQTnvTI5k}-2(0;2*RbaLYbG2MObVh&a-d}Pqp6lor_1ae%=foS3x6Rt*rQQ&EyKF$AnQ z>e>P|OCnJxM$nov6je7_@MTagER5f+^d#{44)M7I15huJyF--{Aue4CJi3Su@V8!U z6&lP?Qzwp6p^{$_GM2-wFyG2YKwnXZT9)b$6SwS4oloy{qb#+w>db6Tifx19lOv0( z$&1o~YJ{j^$(d*+2x2!luY9BZc`J4SI5Pyb9cpO3V$JaEdi?=ws47G+6r5hP%qXsrffAq= zsR5_bDWUK^`6vORU8^{$`uQw$fV6-{i$3;0q65n6lG|)c2OFStdS<}}C|&tbI=yj3 z`A|CQ$7Y4np#|X|E4-!|%fTQZ4YuV}*DGO1t>KW7!irfT1(H}C#f!?|Zuu_H%h)KU zW5>#Jq?lRInM9fl1c@-eQEG~4gU&t6u}Xtb(dA~_^0^=o=u&wyt842OH8?}?Orz?N z37CIQH3x)NWVzf~B?y_h>Kji;g zN)_PRgha1zre4@6wL!)zeqR;PkwQB z2H8fbf@QNZ7-z-r(>$%X8pzS&_yn_X%Z-4ygeO(Cpb3P~@OeMD+^VBjj^`wufDe|R zXSfXR8+huK38>#eNDedKto^_7^`SFpVV)Xrp{sFcci+znk(u+nV7!akC zQQ%Fpp^N$ja|Qx#L51o6s0R?$xHO8k6&`*F5Bso|jqu0`eU3qL@&w+N#^{8}wDFrIfWTaH0R-M0?0abpaByw=33RvTy--)#=YJFM-*wONDPa& zv}8q{M1;un`~fx6O<)!?;2jwekqMRxiDXP^azaTYlM}LSqCb=tuY&^$6pJ=@g)_!T zOVw9H_7%$|T&V|YK54HZnb0ii<)wz8agNF;d~4<42`cbv3aMhf_qP45kOu*H2KOc#@Y^&OK6aW`> zv!#r6n7u#}9MiWiUnl>}bhN?h$YO)Y-tOVVO*upZwq+s#XTarG)ocUoMfW7hXIow+ z+Nqe_Oy~`V90tQICWaH#6^@e6DEGRJ>q>!ZTIU8L9xbP?ofcFv4cnadj(l)e zE6|Kc5MCuFHVbLm7b#t@XwVDr9Hv){1c&29r`qFb6z8F{O`s6#ZkQ>}u*1NH0h%Jp zg+nn1YXVbsVMy>udxAx<$6KY z^EK4;PQyGvr{?6Pq60SEEI6gzy6H#8a5)`oRvy6A0n~)s-PQB%#=9T^KvIU}k;BN09-6k;q-cxg}Faa7?2@$70l}P(5s& zKI0JRDnY9IrG!{i4Ei)3X|?M>vvJQcZw zq86WSHcS295H{gVXCcx-CtO}Z$SBQ%*0PR-Es^^)(MjAi00%;ipu`qveK3jJD?~$R z>-nP9v(@c#!ZjjfNj(DrVTGrNPA=UIFCH4%7bk9=JkhsHlBYfW8qm(xG<$2xsT2V^ zJ@g~EiL{^0NndIWDHv;{I$^EV^cou_9~ab$+#7Yp;qX+WcWXb>o>qL~b)mm1k8H)u!23gn?_^2SPzkXVC)|TDE*84`FVhs5%bFL<~4GSxkjAqHRlf%Jvd$}bUjW<&N4d@63EM{%n z0Kz~o&dQ)w-_tJGfUezut~JY6dGg@rE1^)OUb(G&bSZL#TUXR#c^LRt`~d z)S|9b1xY&LKausOtlX#%JMLICLnnY^oN5*1GQk1W1FJ^Y)E!qfVCdjs$Bi0ZQ*C{a zNa&7dxSkb^EEcq2Tio_4nKBp5LLGY)%vP&?sBm1$JVuEjG%KQNd1@VVE+iy#l2z-9 zxgshSLy}A047N=L)VOX)1v!y;bkb~cqaQ>-c-JTrp!>|Ic1FzEDf;ZlDsbJ zB&GW1kd=-4g)@<6TLnalaEwK-!XzwsF?`d=V0@w760&GB!nkjs=aR{9oSY&SwG3Uw z2rjli40#J%7C8*-R#Xu<1$W5pwbX^nJqsi9lxMTjaGxCEUwveC|Iu~BN7<7+HmZrL zd_eDoc4j4(00W3)91_4VuJ>#u`Oi; zP^V0)xn;4*b&E1QPC#j0R`5qIW$-~}j$v5O;$$KDfGrCk{mx1Ysu^Z`JsI9qTM&RA zeNRPW1eh(UIO@4mO>=&vWN9D|H~`5`Aj_r~Sy~Yy7UCNybwYJ!twWGw&d<9F3_R~m zWpJfHvEW|-DXbCwBCSxj4}ZDY+AS2_@H&pSs-0m(B<;S1vt$OGkcRZ{jhdC=H} zYn&MgWUd&(W{&dEX}FL`imxw=27mqsY-WUX7iHMC}QdvoHXxhHFIi)=~;41E|8 zMRwI%DK1yqi|ddg9q!N`k*5+Ei)W#ysn0D!-Z)rlg#;W4)Aqw?cS~@TA(8~wFptDK zYzCoG%bcd9;P~A2l@D4*?WY7p3Z@`b*mx@fc1RGXgE=Zjm+_ji`6ca3yI#_8yo^ z+Hh8>F(X+|*t)pLA5rVC0=+J;7DgZ(hbiHs4Ws>Qwjt3`fs(lB&c#ieOhk#cr!H?0 z-Q|fR=AsF5dV5(%0nQvwBvD{f?OcmwJOgk7dVo z`zSRaP}5O#G~nW>+AfV~o$f7zgaI?tw$_$7d?+*^)rYe7e*J-cBV}o#w)*Al^)QIL zA~({OX-rgUMG4;~R*1{lB2@2`J7B*>hUn8@bc?iAuAYPwV4Jyy4jzp7PD-)XxeOQ| z!XQ#*uw8AhC`?p-LiW0)Q+V76k@-quhT7uW@g#dLsNGT%Yi-KddqZ0>#&#-GrnX#!f zjC!^JJff9Eyfqr17>{NFp4X{98q1U!z4nR%Oia=CB=4`(;2Am5YmPj?#>BsBXHslM zk-ns8bIP4brHj7AUITK`@`r{!i1g?Rmu--QnJIO!gz_p?Eqw!YEx>+kJOn+QBib9Q zA4*Na+CpI<6l;_p&b<}TRfHT+K`V}~6sRB}x-qDc3^pR=j2XV;ge52|+Z(D)7y=`j zF0R>|)ONx8VyNFR&ZNt)Q;is;ooODQ*N>b5URVbet0$LZlFG(`K;h=V9FtLMmqcL< zsmw!Fr{N>WGa@7iw^7VF>-&iWT z&ptIv#)XkoHR{j^m68Ii)L{UF$Tco--oP2;sBKw?r|1Phc1i8#uc*ANd*$L$Ia6D4 z`#m$5wSUDenGmOhdz)GTxf%%~s5ow zFbIlPHTJmlEbbh6%}|blyhN6agu`Tn8gtj35Q3EzW-A#qR{2A0#U0Suie&;4;RVQp zr!uFSqGZ*&7_S1HPap@3eKtqi>N(o zSUYMtqtXp<6-JqkQpl3~vh3G26h}Vtbt;S#)U*h+T?br3p|l;ee%CsJa70l!0ZBq@ z0##QbOF@&l1A29LTP9Nc0)tt*K5!XHQh5MAQl`q2?Y%DwIQ;r$D)lnTd;$g(I?hG)k`tfa-SWK&u5R)59AfrYOLcf?ZTkhEOF>TB2jIjaxHnOt1^)psNE z(V4=d>k-+9C)Fn6ZEa{3qiOxR?&lB%Y>~FiZGqD2m4Afd0kM%wYP;rv&cm&inu}!zGQ` zQz;mnREWzW!u5xL^@oO(iZ^DPVnsx@exX5LcY5U!C6NnjyYhYni964|*z}RyYbffP zwZ<}IMT8$Lx5Rebgw2;jupeX53*cs#I!RgNvu@>VIYXfk&;`Xn8#vO#>-zW0Ha4P| zL1`gAaSI3!hARj#kNmW;tHh0C^=KKoOvmzQ`WTcVg|D>x1s~}w_4*Mjko#&+8E{ZZ z#!}YCuRnZ4iW(nDmi(Rb`Y0N+_MxvS@|U<09x@m@$+P{4AIPP(Uwe44C2KLSec5`C z3LYw-bGYp2T0nkAlNGG?to>7yv|RD&CMq{(~LM=%ese3ZMY; zvQ86@4|R^@lG<*H5ACVtsm>GlM%M>-umtCNzQ8xvO62ywoXTg)xlkS9QZ;nI-7aB_ zRv&q8WjxBgD1H@ZA6YM>gb;w&Q?X1o#ktRXfz$;ZI=FH002MGMa0>&jeBO`cECF2apIW>ueK5yt~3q#CA@r#lcQ==%pgwvVU&FMye5TD4> ziK0t5>#yaFItG8LbU2a6C4_0lDnTY(`qQe8V}Zg^-a)|LoIEXiO% zXarp7C(KzwD1(e_FB&S$%_!#r}GrAw;kQ48exgh@-I|8wYZ#Yp^*OR!Y298A&eW4{9K+d~GIXujri|&Bks3|gfRY!cM7Ej_8B=oLY^Rd(!YF&09 zJVHTLvy${;Qyma{Lg&tIW~#0W;!0&>Vw&2z(l7zugm$B;gIFj!iPnJ*?LUC3Zdn%t zpi)^Nh?Df8N9ODZDp|ykF&B?DV&Ght7>XfkO*CPdbY@OG9(B5Md2)_|L6F@%XRx#S zjUW2W0BDxUjuxny$Xrjw+cH{pjQqNe+m|;9C`Z@H1q>VBNP4$g3O7Dkocy#H1Z(W7 zD#Nw6X*I38BU*n5zqV0r61sRYWmBU&cUiq`%B`2`YPwp3-LUFhfkrL@OC($EzHELJ|r~aHI549Pvj;iYM1f=BJG-9QpnW^5pG?YUW zf3`T>gk_K`v+^2NLDYnn^lHn?jkQ9}N45w-uO~4iv955Sl}E&R++lfWk}WDpfT{r~ zEj8#u(u6E$-_V4$paaZU$+N6|TetikekW=rs%`p;Ga|XfYN$*Nx!dimO)ytbg5?ov z?9J{GlEaZ^%O_rQYeB~)(+wS~rSn{~i>a)Yk!{J>F@^r)oEv3zPGpeVnir+IdF^1*d@mH(az>bK z#WCs3jfUF_OwlxOfD=7vY-^=((+~iwJH??>?PS+2{lHln^aI0IOyjSERv<+^xkdCfw+XfsVlA5Dz{_p~ z2th@pYO9fWZoQgfFU+^YT98nf4uCspaAA?$#Ord}~-9&w2QFxMC=^%p$7;6#IM)9-QL}V*VcE>Bk zZ6u-L31FajT9TlSjY^Da0kXxdY8Vm`8A-OZWLv4*ooWRDp)o-RvC`^BG?kJIL0BTb`y7EyDyXMArrw&P>qL`2R0BL(TaBx!L7NRcF)llCf=M~V4 zDYRVgaM@IKYXlgg0py~7B{ggZjX^89MP)=_j$rtae$`b&fdNest#P6N-pkQr;m1R5 zGkl;O`VuIrC|t3#X@-T5zlwFpJ^cpHOj#})`8t#Rn1iU0`>vA^SuGH>bxCY*yfu+* zviH}FiZy|C&?ajFvzJC*fa9jbCP=nRxLo!ds1Ge?M~da#bs9IgZUyp~(+1rFs3dv6 zZjR|YM6{nb3Nlz641mMaD%_aB=lMKoI5@FH2f5Hhl!Wl!+J55r0$Qy*j+rN;tZhiG z-%LG&AT?pdS(!*aASwm?Soj;>NK|3HzfglDi#!)(29qX3&*ba^iBUuRjUOy|h`yGN zJWwpBqLa0{Af2b%;y_3^h-G4u*{!sm$)=#o?-3!`Vq`1Xfg1I(XDvn&4r?}rdP`wy zmc(V+cLvX?sS(VCa>%Wq&S^_;>A==yq&U2)FMur)r!&O(u)AS;EYS<0ZiZCh`riIuq{DK-e)G!L> z2#DRZhK}?E7#bN`crpYaLy#$jY_u2wDB4nSY%w;BKeT`7aTKxP6PWujAC z?itA?NE0g4qz`o#>>2>rRj9vEkmT$N90`Pp^uu>T7iY-rLwj!o;#`XS9fR}-Liixk z(xo?Cfh|WS8=P&?##H-pA-6Ih*=3~JUd&PDB-_vxh1xh3s^{SaVFm%yU8%;f8 z`HKQmDAsNi57*{s%Yon_L$uQl5?MstNljKx%yu1e73UCY4hPivx-~MXFnpXdTcD`q z%S>dFv(~w4Gc*t_6k&UU8RSiEKBcZgQbV(%h~!<`Ahm&6oeLV1Md3$nF>P0S18#Ez zZquw(O1_v6w~3TPk3T?6iO>OVmt<=dh{hPcvCyW{1#DiVhthA<7T&|h&e|Z}g`NrP zD{#}mGaMf6ly;7lpsyK4aGmZ@Ww5w+RXS|Qq851~X<)%4$k?QYv&28DTBwN_%hGfL z{yEOEC6yGZ!f;*kE*kU#DllH}$U;10vjKF$6Vywj{BYRxR7IY<&lRX-h0Q@pysY!K z#@a={KS+BXQVgyaVry^!wq-@Eo^&JY7$VtJ3IXNgxK|*5DNC^TH2tMPVjQY;`4l>FzL1h?Qb2HCXd88@ zY&xS1rT!#2LzO36A7ZhZ%)w$81OPFTU{f<2DI%=btp?6WWDDi=Z6bG9f%W2EzIFhLU*4we9o*uB$I1H z6H!5S)wEFU0{ishCF-Tp}KcC$q@2Q&A{(0KL4bQoyI@y4k8q0ar-VT+5>M zb<2NDEN2V;QTDLf;+ZpirLl$sgE>&sq_0H0j zN{aHyfRg16_FG9W2YF=LeAvAnmz$9*4#_`+=RpfW3YP-XZcT_J5nIxMN0fSE->h4b zrba%@)IDehj*2QGZp(*7H37+qh0=0yDxs5ZgF6F78Q`HpaT2(aBo7BMtL%@;eG;#1 ziBlyR4tUW?!{Je}X}$2LY07}D>wH$+AmLMGaZYq<)JWKt#-Xuv4#5oMvke4O>M;en zx&5ipuu8zpSQoEEZ*r?HE-6vh5=5*~B~(pYrW&~|@d`~Z5LaOzO{zPGDS^c!w;0ze zKsv3eqME_9L<*c8FWi_ic7c)lUBQ4r#1S8E%0l-@QSV3&#jBm%)!HqhFc09;V9Z>T zNIcOHB^5d5YH)+Q62o_gv~|Ra@N^S3fdl9)8$kVVHh}^_ro$t&esyJ}Z^V&beQ&`d z;%x~u;1l5+nvnHwyFhdVwL7WI5NltC=?VUB9yTEIp^((6^uwYlEDUl*<3m(wioq5D zyqL5Ms*hx7Tfy1lnidGqBRSO69foyfS|`{@l@chWD=@JrFOC>2IXVqcG<{U?Xu=no zG6u0hL2XI+P$|rfK}qhzLXP5SX>)LktjIJ*E&hPpUvrCPq}C zfFcXtCh#06+$*X{3Cl^~OK5NyxWsofm?!RNNy5{NH`-KI%Bz+6igi!&A?&MLQSua$ z@2`jjvTh+aAO$>bpVt!V$Ym~7QK&KM8PIqOXh6!AT&;dU^}wppHFZ={V(_rzMh&m2 zw%%@Iwe$x^;2@bGxY)!bigS^*!Bz={f33&gFoJ9=gQCQl&9+5}TZwE>WnQ2Ls3xrU~wFgWbutcmTaT6jNIaV3jWp)U=u`IX_WOzX}+}l}4#=1ot zZl^2c%0Xl1(gx0V1X?U`IaX#+ke3w3P!e?zV$azxrWx|W|7Im8 z+w6UE08{Zx)YN3=rb;neij^=ZVo?U#GYyLJdj1STEapJ)z)0$p3wGV&Tpnko%M0FR z#(LyZVm13{04-KGRa1hfAhc#(iL0Q&(yqdL2%|WSzu-}|%M7YMvbz81y5Xa2wIiVi z37SyfshKV+w8Ivn3gz2O4=phyr;~J|I0dLjkzkLS09paITzav1>Pd7p17IT3gh>o(79E759YQ%j zevzdT9$}^csmbJd9CJfEPyo5oEjpFK+s;OrHcd9qp_^GrH6N5wjR!oLIEpkCx6p#zK@r>3 z38Neyf*feMbGBQH(ukUEwI1^j#QB11ysFU7_Cxz`bx$<$V$h3?8(Y*G!UpReJQd<+ zJn4W1HQmxhj1n_S+1ou{4v`<84L}3MhodE#%Rp(?Ui}h@p^q5siYRgISS) zw@u1#i}WTysJ`ivADDNUR7V0Y(r(Ku4(IytWLYCOIzj!f>6)C_-$+teMG!q1y<0&Y5ve^006`u2=zlp{}P9g-cNx z4;3$~#)hNGm^hWv*-h#6LA8>QgRmQ@_XB%V^-jq`J!v>@N$sLRp<7ELo2Dv0aF|lz z#3X#19N3cvD!dm}n%mCLz5Va9|Xx zhHrYS!%!Mu3U^Il*oPd*`%cbSnmQ;Ae;HEi;hoDMRs>h}pH>VXD!kVh@LOwXXmAs- zT(m9_7nH?ay!6Nu@IO>|4}un{Q?5%v69Sh~f&5bw6t*h*$QoV(gj+6PlyZS9%ol0OlRP~BApE%)siF?ubgrlt3>D{KI6+9o zou)mNiAZIH1fdzd_ z6Sg|!&459N+pWFR{EhZb5hbvN5)vNK3MF(00)G;cFlel+W!_7_8XNQmTscL}a(QGO zIZazy!qk;RM8gIdaXGvI1{7XNh#W;%`Cvve@`jmhqY;QCUPc*va*>6)s3yuwdj&x& zR3Z_;`Lr=!DeuzuLQ=YRx`P3|OqH#qF#_Vu$6r3HKtNYT{uz;X@-BS@MJpc`yI=zv zS_d+2U}2qLVH!NH6KH4>XBHObMQw#YiG`t(yVL_M*;WiPg*c+pZ7hHQHU%1`Q7Hg6 zC8#MEND`PVW*?-@LSsYQAT(Gg&MyKee<0ln`PhLQ_*0gZxl$M$xzaBYVw&7S^cgNb zs(;OROHv9^2!u6hieAPhsbHCj*#uf8gd5@sxM*YPgSc-LW?0*azzal&xVKpyMOa}GxjRp|NfZUeGwcsqd@S?h=H zCfAZVHkw`l;KA@chqMw{Q??F;feUERiud9Li6kPpQ6&mp0J?A#?*s^(>x7d+3XB5s zuK?J_)dsgUj};1{c>qDTUrrw`%`|2s6RjdxI@BX4+$X>(z#&J%SWRe}Z3LQ-q=I+E zfFpo$BP0}5?+ieC7+tstd3CrAP^MTagWnZ`p<~@l13+y_N(&TA5YA*!1%RMUq~!`M zJK>;^IYtOmIPr6yj%+ujHVL&@H7`^|RZAFmi}q7^632`z!V=&b_z&FN5zJwj6Zgb{ zftIF+qGi?u(*0>l;!|20;wIlNfpdkvflam?Lj#hB$nlmj>A{^NwK$kSeClNMzEVh9b8Pkmb zu8iTQw1OX&GQ`j;D`h@+1}{VjV^nWIDnF${YR9h`R0Ja#$Tc34^*D*jB1ZET*TWCi zp;(DGY_{|Oae@CHMsTjPBS#DH85%(6?luLME_q2jVHby%6kB-Vy#1>0gc zpa6BJ)k+e)s=SDFhJnEb^AVLwwJs8|)+Y2$=ko^2#5Q5^&ASWJDCm(y%}fZpg%gSA zigsI|DaNukqE1{|V+PoHyu!e*Fg)TjEON99@L?3KDF|``zAav)q~hrb zg5|5V3h|Uy)4ZK-B*l6}x=KrUJeWu$pmNO7@Ow)*p_QNIa19Wo#FW6^angumIf3>U ziKmbM|^X?81dQ2C+mVjg2H7 z9c*g8!PUj-&9PJp&osQ@M%RU!^ZS-t|{pEN}kGe&-{qiI3-F>fJddG6?Ck~ zQvtps+K`YG74jJpE3IV!n|2>mLU3I&i@;q~PA4)FOv7-x0%&_L=ur6dn01RbLn=xE z$V{n@3WRO6(lAUTveUkyWXo%rDWM+e3s(cH#01gda zLcEuc2UP-B)uddhH-n{u zM{Def$B~Gm3Prp*nFJ3ft4>J4dIxdvlw^U#r2CJltrZ$a3Q7z#aMU(yPDXhF?XoFy z^j4Tv+8WxjuOQ0*%jjLcMb29utR(-L;RUuM0Ki`xK_@S$)SIlE=U_d1AU2&_1+;n# zdECn)Up7~phgTia47*%rA^XzGJIMHp9F&5NT#{Uv!t?<-$S*=g9mq2wgu1+vg*r?! zfs}+GhaRUk?IUfSVt{CuheSt~mogJsQm}R?^FrtYm3oGm{jQ<hGa|#Lf&rJY=m=uVHML)K zZxQGV(6Jg{Ht$Laq+SZ!HNL=hi2h){^Jy4?S}#K9I1=FE^!Pl|wt*D}7eTCvyTUUY zlA;f)I5_fA|&E<6!hC%GofsCY^fsnXk^ide4xZ}FT_FjRW%f<*) zKNN6b2B?;${#%JYfIfc-6VF#zLx@3Cx`V$EwU%iC5G1i^CqTz>3lS>WRd4`6EkIRv zyAXNjM~PLZ{mUC{u|%Qh{S->KjtI3v{63063uVWmh!AAKe1)brNFL&*KtBzvjLCQd zet?7EwD^-J;|{o-E|*W1@U+C1@;fB=>l@0H6@K!0)kzxK|o zHxWi+_!<78#_ZN{BS$R}- zH=CoG@tX4>k;Sa4tjx%`;~#%Gf9t!>Wb3#jSzsw(BD+8TJ3zVbT|bbqfOjcP1eWDf z-tg`9gK@{~1DY8!QZT`QsROvL89Ton)(Qg13_%dL3Q7-6_b0q2}(y zgR<}U8@Ssvo5kj7E@hf^SFW9AJSdx{r-B`)*=?RC?6?C|e#2@;gR*HhGsMeoMf~#( z=fKm`@P^0G)5qEEVf39#?R(~0OKE6>m+Cr)QPXoQ~nl)A?xIVhg``^D@) z|ES7r@JIdK{-_FT@JIdK{-}Cs@JIdK{jn&41rlXzF}tLJNB@iJlB(F;|NP?d$*y(3vlhe*F5-Npxi$k8e3hHiw|EJ(29!9eO=OG6Vn_$$gz}vF z4sVqsTFl^&&#CW070p(W#mWxM<0FfuEPE1Jl!}KB6j|&Bl>SlDUlCpSMY(kNg7F!E zgg5x&qU`)#Qd?_+pMMhA*ROlVY+IA5qF#_R-NuI4n&{G!ZM3P1>KjRkz#&fVrR37| zA<0|VH~dX0b!L1E#@zTImN z*U=_{XLFmJdVc*}H2L+GOiwzFi8C2VhqO33C#O>Kz5;i$hm~Kmq`W)Ax!BIPKZI`JCau;D2_h=^Mu(+%i(0ztOV@}ZY|GiUm+S_S6vPGzF2Zj78{uZ0rZ2r=sKAuu-{a$YH!&EKF-!1KC z`?=dQQ-|olU?cTw?vcrZjhZk4D&T?iRifnjUjls{!6v@$KxK~p80p5TG98Q@SEl`d zNo6{umkELzxbXOZ<o#%k+wY&4;-2D>G3r=LaT{D}&lER^w18g5398UO9crD8 z4#2?~#7&LD(*J*IihF|dW-E$|U8n&klDi9d%`=bNtEpJ+W+ADXzG!C~kxQ^qJy3kAqe`y^IqSr6fjB;{yo7o1*vlVA&QfsycYV#uej6N&D;O;vvF^>sMQ;~M;sNRqiGibYv1K9FzX!`8G)v$k=|cdZwB#j*^! z&yoAVQl0_jZ0M7*$@KpNcCy<00_pB;(MiL;ujLenGSwtEmMkr$i&ku=20FIukjW)R zx^1Y-i<0b8s+zEVT{XrymPGCm8HAB{2iSviHHXY)74lEZVgA%+eK$1h8RfMNiF$)L zFoh%i|0h;Jp3Qo(&e zjUL@7Q1JgrBPw;U#p6V>2`jbUIDiE3!5Rp+c5Qz8C#!WFPkLNZ7O;>+n|MDggoWwlvE$3i)bP=5okx09PGsh&Ml+9>0^0@_-srF!{4$ z9UKrie}CulqHGHYV5_A{de$D9b234x4|NyW)g`?I`qBfJFAlBH6uobG>}h{8T36Fc z#5R7Mwn!Qqw04`;7E#G}kX*_LveM>OuCnC@$UkDhx#Pt93^_RML zwrI9(%L@5JRMYLHZd-h5i*2J4&~{~f+dwQ^KLCf%kcnbb+?JVd;|>m9FgnEwgu_8{ zMb0QDuaHNNYAQ^c0$#CgPn-FF=HaOu6-4F_s>lJbqw=E^eDfPXM13yV@Jj~s`u^nX z=HY(|O1pXvG=3-9OUtTQNs*f?X?X>}z~z5kAMN!IpkQk41B!+$6QQfA2Mj!Ldw}kzh1V=z#)H<(0?&=jV*Ds<8h{_#ALlrDvq3&LyCtN2(6V=2->MW+sRenbQ0La5 z#5RWpKCchHK}zlk49{4L2|>27#)g8FK{`|qnkD{aZU6KA&Gk<|lTPOF3$%xh>+4H0 ztx{hAT)VD|m@sE3;9*A3yM@hN+xDbSS>J9-Dv=F2UDC8jWfovC2P!B%4W)QEeWW(E z+o!em*dhe^#+rji8r~RS)dJiW7XM*3ACwSa!v!y_Z$M48Gztj7Z8+vZe{#P{LfkN6 z?b^}fclAqZO*3_AHI2G&!lxfGtXeBQw-vN3SnT#UX%V zJ;bBr`thMDb*yPwm8XeM>VG9dZLOYA0&6;ijO>tZq5nzA_DSK83S zdiD^FGbSL1O0oDK)eDuy_GB|E@|GAc6*c`rB2wp1iWV(DmSvQ;XxAhHGX50I7b=VT z+URlJza(>F!m{hODAOI&U=F0wg4-?1ZfXp!UK5%7bm5orAEuQ9knQ~^Onl>}0l0^y zj3Ga586UHO4tqMR)*)5#NJLt|v|Zt>C6AY%B1G3qLQvgOieD zNm~h9zwrger?PfHfyL8|?ttm>>wpd1@mp)Lg)bPLVo~Gv@iU0l7``HQ!6PluB^IdX z`b%|y4#LHI3i=UXm+J_QKe!(NnUssJT>uC|=1SFRTZ~Dc!usznSDvN(_)lr6{^Ew_ zh)ub~rmRb|J^cYNTJ$z0uEbz{X$s=vb0j+Uco3eAKuUD}m()Bt|QxwZa`=<{& zk5zu!yk~Q&cznyByd^i9#&?H50iAvIcGM0*BiBBp&eFP~RD~AH4n`ISs;i{tQ}VA5!zwcKxvfauGHWgLrnM>boW#R z+|%8v-lmn-A3z$%Wp$W|!D)gzvJlej5@-cF8yvoqJwWbJ@Tumf5ojrN=nU%Tot^jvXlxNsyf{JPjdbKtqzP%)u!UOF2%46EUCbEWTLxa3TZkJKJFM=Y!H&2iT{91E zRM2SIwMgF^u^vqvc;z{q^C(|<*0i2Atym8+8ro9RS{Y~CLDQ1lkwnv?=SlflmVm4@ z*o_OkNU*(SQk!TWHrKA(TH09D{w2Y9)Ipyu}KJNhB+NY~K<~nG-0yBASL7 zT+&2R!Z-X+5rzCDtlC5?O0HZG2%p)hi^!oN0>+TE@D*5$lC~$V^@?ymL1*B-+GOWO zk$pwVYVph>(*HhL&Mu`JybQ&uM_z_=Ak0pg!AN@S`E62SyiS$ zsf$W!Y6#iQEt=k6w|nNUc`S#|(d5t}3H=cr7Q)5ZZ3?t79`%a5zc`Y9dk^x~-t3L} znC`$HGm3T=Qc%q{JfCBKD2HC(yM^{vchV%!89<|&(B=Uv@?!iUvenumG`nLc=0h6p zseWqvXJ5gd3knat2w^j{baOqLYoit9@6OzVJ|kezfYU37tbR1)sB7nj9>5~wW_ zje>-@=;=pK%j=tWXQc8DE*G@P!<)A_48@OV!-Td~&5XnZS)9wdap`XJfhZ;E!c1sO ztx)}wUM*J_G^Blf;(8iFw18f^L%`d}ZW0%X%rQNDwiRnmA9$V7eIl9%)R{yirzd=()LqPkQ zim$Xr-8)$Qf*GG!{j0ieK1-{=#r%>4Lyw;8`D}LRRyaLP!JJ&%$yPkHB5J$3aJ_}G z_AkUtr!O!J31-wIK8mp|$iElrqCV^U%X(W@QG&(LmsGi{Q`d6)98Y3k=hJ%*L_>7B z6Wc-6v9%MDBt?k9e|P=<{8(OkLt{@~35+mVzrahvN$=ll4^!b@uA93{**v+{m-g;) zeAz^OOkJQ!u07f+dD$i%85zbbv;sV4$r}`IFyFJ+W!-# z>>Eb+&I5VmxQAd1hDqyguX-k;Zx^#29{_ZNMWcy)&f9T7!(ypEqKmFxu+N_+s3s`L z{&9!2l2zYrV#?gd^|b6dYRWd$Kddvk@heb7(PyU(5syI=aKbY#K2YPDqA97KNGqqZ zW7Uw>G#smVF8yhea0&^K+WmZ;R`O>ja{EnznrU7Q?z8B!LOk?s6bG3KG~|1b)Y*DgAz?m@xc2;YiX-3 z$=OE6s+dt9pbg9=iup5t6&Ua>O!9^zJKNe(HD%*~>Go$I!y)SDzHr$ovNMXG4@K5@^; znSkNDME=8*|Gg*wNQlnmKci+#n@Vw2ij(&ACDbj>UhSLMp8<}ZHV4l+^|Up!u?<@} z_`~Ck7loQJWU2(Vgr$1@) zNA8urr>Ky(Kb*gZD^Rv0vJ#72Vk$styZnWFQYT!=ki>Yn35ao8OxWY6)gA#zu|*q| z3KgXc<`0@$)oJ{1Cso(x^M)@Np8>G)gTpD(Np@2gC6D=y-v)GG4!~>WiRu?eV`jr- z$+uX(g0x!irJmCL7L!`v{|~K^QsK=sGHHwfoJIAqxsptyu}C8uSR=fL%B{m#Q6Okxb?`ef13hV8uMpc7; zr@ghrQRGIkvu-$>diI^r6%A9W;esP?1hr5m)63y4bhnZ!Q#*vCXJqq$Is}0>i{Wz( zr-btxnD;cB`3e*YEO9pAo(8nq;vkSww^$QyC)|pwSDgJKQxaF`od|I81KvDF8kN8t z7*iVG5xCaC8Nv_Rpb>olH~CZbq0RBD@Knxx!Jzpol*|L8=<@dMo+fMdf3SGiT!d$G zMEaxWw2WMUO{%D+RKl%O4@$wDUpt@(-4fxBZ9{@vNILqndcVgg&Cn&vAM?a9vJ+?o z5mXg*9E~?*N*?dkVS?L8NR>e<+!PpZ9;%dOu~ zUG3`Lq_&$dh8#5Uy4;nUgKD$0}O?TIzqTO+|M%u&h26h=UFVkXsd z7auF%t`h(K>5U|E=@WX7YOEuOWl%Sz^BBz4`iWz?tTr-UvZ^j|oSwv+cgf;2PidAe ziu;{pI!kJmd!IM!l8yALXTLY)sx2rVmenCSKt6WBIIzt!BhDoiP*XY!QaO|g$k9U9 z;S{D%`!Pvcy)VHlNAevKO=g}aaEWMx#E`^C>e}cH$OSNz9hO}Qr@zmVcjw69>vyM9 zkPu=m$;5QScX?-$6%LiTvbgY$vl4sVXmiKNm7(76K6Abecghg_@UCe#Tb=_WUV7s*00vI zO6WFkt>qcMU~~r0f!pW6?WE6Ro5Hxxj?fN)TY-s60=G0OuRc)V761ox`y|@RZa$R? zMe}Q9ht^d&>);ka=2r3MtBQJ-0*rAcQlM?>fYVU2~+E{@OW?wtX zgf|Xo=+HdaAErld;o87LAAuDyLA3^+Axue>%B#Qe8XTrjb}3W#$!dv{8$)BE=q0xKp_DAD(B`~#i-gPUZ?b<=&_E4$XzbKeSh z=qNe%05nw9e1&ZAox5P=(1X(ZUvG)2o$kHe`_1eCxS)5l*WZ2@krYXAtEP3i5`Vv) z{a(UZGKipuDrv(k1UXG3qmJLGE~M>=(U8vou%h62H0i+Z#07V zSXBN*kJ3QptFCHXkbW?M$Eh6Q(FZJ@Z$68^C&b?S_D-+F)RK%s(RC#iMzz$F!#g^5 zAKuc=`}pP>mo9W4?K4hj19JfDVHEhw{_-vgqG%L;7_@V>M`}NCS568Y3nND1$6^xW)aHw( zZyY-M`nf&C&&yje(Y$z*28ltfNsMW6*_6m@tfG z2oFeTPOQwE6J)?IC7OC=+5<0LXll)>1RG>KJyJk9sH+Jhq<##JS3O008Y#NZ~ zR?%+ef2Kz6{_>J6!GB1hooq;yx@-sBVbwUT?v&ooQr!+x=Rj>w`sjeZ2L}V|(yNGD ztVxACWUa`n+9BdUwP;Ur0E90XJwp`^c`K6nAZC*EH2>yGmx)*;UMJ`nO1hnW2W$UI zV*cW(^?JMUXwQs|T0CJ@0TlL+|FX{aYB3b8Jf2db4RJ^NR<&16!iY9x8?q*aa;;db zHVr7-A(Rz=%?7#oK`O5vZ_tFws1fKocD4rs92ul)qKdsTaHKRnM)fHdYv^@v6+IXe z>T?w3$UCns)IX$AiryoK%TCck-zX1q*>iq-hY7sVOwVu{eex%wBobiG(|US$oKwW4 z!c+=s=Gq7(>}C+z;j(^=jeoq3AI@-_jVFdsuA17VSiSa|XunVQYy+CH*^Lyn-`_|# zL{iJ{-~Ql8!7H+Ab$N9{Bl@C+durgmv?vIL_%WLWi?zKNl?~t-dhr#X(ruPv53J_8 z)g6OxGYYF1!xvOBp8Sl~!xP20rJP|b_3h;J2#no)HSNp2cnTM3d=<&SDG4k| zNMv*XDtKc9;$_}&CVE6xJlH78;NwXl3|vXDY_lBvqqC!?=}yX%w#E`WJ#EkGs=y(0 zc6xMNw4g$4Y+@F?gcc@)HDDHm%fcUK!k|p3KX=6ycZSi>c8Dm;g>m;CTR-jXu>R!_G-#k*iu;kb)$VNtucKVZ?&N*I z!Rc^bi2bHN^2XY|5h7pGPeZTrPP~fpa)$c(Zf z%V>zqG#jFjGWH@}uWX2hG5iquvTR5lzDSoh8=_Tg{wX28jsGGA67)mDVmLpfjb0Rn zUHp(P8WNI?A5v&9W&bAG*lfN+O(x0N%wGfiP5(N( z^4AJkefbw$(=(G?s~UDPxmMF=A-M*FfGXl~%qn~DvJo)#cwAkQbe>&nvgaX^viH_? z3x`OarvT>hwWjEx{p4CD9k-He3{0=Bs6x-KVZ@}@TFD+tp4S%XJL;w`L zTsOS7TCk1DTvp_tpen@U*o{Rtt|Vb58&?avjowj{xtw2X(aVx?)uPU?foz?PE4%z! zozf6-&QLTdJsYbrL9Yc!w9#)*EJ-n+`bqwccrXh42U=xW9GrnJGe zHv8VTpq)|t-mYABWDZ4hAs4Q~xtCt6iS(q`Adl=?yGrPwBZHj4LVPb&x=QGtajWck z)dIyYyLapzt+Zv5f5tcX3My=}Ys)r!UYDJPuA&GzySMAoJ?hwv^qh085IFB>E?leA z`Ee~<(|S>+d(QK+^VYE&UG@%oXr=p$ryuyTWG-!&@|>>j79!E&IYaj8S}`tvZ>Jhp za&MKM55nKtpDP;IPyv9|UySUi`u$=q50a_HAW;Ry&;k@%k0C$Xg`5M6OlIS(R&0Z}e?)E#-+^D;LQc=(R*{>9vGT z$Te#ElJ^#5r>Ao%iYB2`>Gz2A&hC}KQ@BTr!^(|$j$Es=@1;XSB9moYBSvtr z=Xx!n7rEBv?Oe@ z?0Mw%WY_A1{}qd}Nae3wOZghtmW5bY(YGPZ6MB|$Z6as5RuV=`@1-k9`faGsiH*a( z0Jf*JQC8Hnq|dA9UzuN{8&xvrs=~3BUaLpm(I&br3KyM$(|aq9Lb@NIIVSc8_u@@V zzZYuGpI7E*wc_UVJE}^MT;lJbL`ht=*?UpCQhK3tY$6x97pSfLUIbG5Ub2L; zxzKSmr57BoiCo|v$kmiKs#R+DFizX~B;SiyKbzmGOJq0q!d_Aust`TdoZD1ilY84l zZgCCWF)(V|E+c)&$Z0+kslkvoQ(Plgcvo`x)wEJv+bD4YQCFk|r{9JOlHS|UK{s8O2IDzh`&E(ZAhZ*rzi)cB*xi(_Sm+{s zUQvKpliZ8=AnzoeGsaA6yCAi23ncr?>{5G`SF8%kbE5StSbO@P#n&2vu1T-4RM|D@ z?3s*1M^Ef6k(OOb2L<)SsqE%jYFBVC0x8)ext8*0u4ObS*AiVyo(Fz&GUpbPDLK=O ztFnFLTFNIGN2jjrd9(w}uO&8w%%w@sxm=S*h4Qc0i)9=PCcl>Q0~wdm0VY^_E*aN` z>q1nGw9lItzx&;bSKoX&d-0dw6^qlhJUedd=IHqFea);!=A(Nx;Q-A zqg?FQFPIb<;)JAFg4zCySNqpQFbmm4`Rzd9$HlXW*Peo{n>%%O%9_ClU;OGL}h3; zbV{0(WR^19$h`OeUF-Yyww312IrrZGJ-_?AhPLfnYrVtsKJW7`(#Bfzf1N&cHbwf+ zr#GJ5dH(SBU5e6l@oC}?E7wz0RmJ7zuiNNs>jWP+u5h;1vR&n{+E!d{f$b(c=e6Rq zB+(ov!t#J)?eo^v@zphwlaQM#a8=9t`l)vs;k~Vke=5f0tN9Owe zUN)rs{E>SFuO2T>34B_$ru&e?iNhKv>`h;buPD2u`Rl~Dx|+H}mky7JBpnUTG$ z<)`1qypUQjuJYiTTddJnYOjgg$7=SbX-^?JJ%tuXz)Fw>8v$qw()U zTDPWZ%$+ql-#T!^tGAk4{HF{zd6~MU>tV@i#fc+)?|SdvZJm74vf`4H-s*rqXEaK6 z4H^Alu`qqp;iTc}JS&%;JK3h-x#Sq7aFnMqJz&X+KSNSw&+fBQw7tF7LObT*$lWs9 z6&AB?3!Kb@bOpo2bXrZCC;_~!krwKy<$gg9>x)z#zOb!csr@Kby@7iezp0AMowk3S zDj1giY!tI=ztgkjdm^Tpu94o~>2Sh5al|@LJuQjOK#M=cC;O}a;gn`Cx%qVUIcD*@ z)A#sy$%yHzRePL0cS3IaYQL+oYRzGFVbtkTQJp3CY)d=`FP53?OtIaxnmk?j44H8-qSQ@QdZ@bD_T+Ym8r8EA{dnM`mQ#&a*x4Go*slb$ zf^TVRI=G83g?F1KK3i$FxZ*P4Ol`L~xHzq{-6D>RkoDI1^XcMB3h-i6WP0#F zwlRX)s;je%P)^I%b=@jkQyoq5d1e%juU2iLI8vrhoreDtpT_#+SN3Ng{u}$BR@wLc zB>QFTPqwVuL}9-amce>!dkU#n2w?J z%?_JvH#v(dvMovpo(;@QPSC>jq1miYXv1Nm}5;A}ICyRBfAr4NTrZ`Pp zZl1$N2Pd=5D^|g~_E{e*fG+JHLYHyas{5e2S3F@!-HQ))ro|_iG@F|=Z`(arL?qnQ zk;}w*ZnJ8Q;sag&?9S(Jbq^oda$Zw&+Z_JT>xLfH=cMv4PaOK3Z-&`3^ZVD_Utew9 z+MC2^Xt=m@OlbOrMhCB&gzEN2Yn21(1vO_AqZ+K`+;413d0FpjW1E%7c$lu<;UL!i zkV*6OujNiEs*Y#2S=?bh#ILyaYE#Nq>-vrkUX6D#2Svm>%Y-z#A649vqt)42TfMz8 zDz#v@x9mg7vUp~&P-TGk##mu}-JsldqHZrTTf-9nh8<{d$-HgR$ z292HZRbCp|nP<_j!oRWo(h|GuDZ#-RT^+X*;X_Twn|m*(1VtayH=Mj+FinE4RT;20 z41R&CNmYO{`c0~Q=(`v7@UKl&EmGTI<9@%nB}qv=s6aH@ zy}r5GqU5LnKj*^OZ);AAeZbmJVqpV37_41O-=k3@t$9<=&Wdzqse2x;a3f<-?yw%6 zBJvRHqpl^!OKC7q=<|z)_YBqz^6dNl9r@xzD6`&Zk z|D$jJrK@M1eEu~i!=m|YW3@))OvDdA>qr7|a0`<1nSw;(@RDJdmZuE3g z0*|eElz#>H$Jcv6riOTNpLei#47?FJVE>1bY=q-u7ozq|fe+O2s`X zo%`5v=G77(YC%M>Oo_P~JwZY3t{36Xf8FNbM~sPc>#5HwX1E2V_~fnT$(-nhgI>LB zUfxr;Yc`DTiZFBTLHryW``2v{iZ1q{vI6O9Y4r{V+&uG|R~a?EbYaCsgR~0z{rX?C zWPC14X!qOszxt8TaeCCY7rA$rC>M9Va%l+?Y<}s^dM>My=1QU9ytJy0SqtgEt6Vi6 z&KZvX;`1Z?_9TDv@~ZLH&Z_SD&6gY1i_=Shhh8=Q!BL1$%}IPE4%{&Bdw^D9#Y1# zS37F!uhn#wCe_bl8AM5LP<-*iU_bX>*(;V5o-N|__&ieC8Ki@O*5iYBkck^yKz9p z*x%5jj_fO$pp03M0^?+(+s(cMA+;|ob>4f_HzD2FJU;?o-^3GLD@U z`h_S&GGc1Qe(7nb;OR|?Ci#f}2rof_^pq-J2>*jC-dPOG3IbHvq!q2;e_-^QQSTGUD?*lvnZG59y=>c~;q4i-Fv`CGL*>c<3^x%%;@}Ye zajZuWKF;|IodnsD3UET0R&|+y&y}=4YHiAIWwYSw`nGzR%#Q69Es>nDtglkxpaK3_ zxXntNv3t&jUIXAm;5@wV%Dh^+`!b!6^yYPsB0b7mMkS-^ah8u~NpokrSafw^Kd6LZ zEK2vBeBVD?_@EGsU|YVnMb0v|VPT9CxjPpqRv9~C=3ue)1YcY4y+?)dm@A0cno*&V zo{91@2b>{-|Gz1~Tqf;C))7F8A#&GQ6kxF54EJ+86d2k~E9?ls*^tpTB)naa3>|$a z_doCG0Gmlv-L+ADz__VyO)vuo6QfHO@*1me?#YUlC4e|b0QnEe$-@cnd7}y?BVrS! zOpiyM2|vi>R-Z*;?&7mzM?KFKN`1(!V>0VKtNphE;jA-26c z&r-vAi%+VW2hfs zUE|vw2JNqykwgjn&*qc-TkM|L$LXQ>tAzI=5<#DU#WwGH*X58T^GkR7JB;Sk6fA7Y z+oHM0M#XGii0Hz9mt$6|BeFzQVp`(->86bxeaT*#W8XY4QF9J6-{83+Cw-F$9DTy9vH!b7T0 zgL`kZnss~iyQLO*Bf57bW!|o}3ug8_jzm^(fqNYPO%9-V?Kr|<0K~u!pdybgVEj?x z;!^xWKC9tr)w`f7 z;A{@$y8l9dIP&TdqJE*GA_X+#a=CdIK|BmD@DdVJB}m!csb}az1)#U%Y=CUW3X|e} z=F_h({s*wSs4LNLBNYzp?gXny2A=*SBnJ!cRMh>BdMABw6{>4$#U-D^Q^a(ME$}+C zsAQ5+lxMSDkWH5^>Srwb|Kb>QErCEq*E4krg^d{Us#pOq*d1iUcrh8b&huxZjdRpy z1E_xs$nWu-=n}$7VyOu-kq=cUAE!{j+b=A|N`Ss%O;jmH|8{e-gNk*#{n9BI9QMjD z@}nQ;70GPPUyl`N|9^IaUuhz=jK*iwySF_fD%{Mc3u8P(RB!BFN{IfhPC|j@>{_C| z)E8s=M0!N)>F;fg@T#yBf?w2N8=$s#bp5S=9q?2fny!i`3Otw6>dC!eYFsmuJw_n5 z|2Lsx0(7YrCrMO|XFMUdA;nd-v$8_7ERZFYjNO0=%!>a$Y;BhUfSIEr*4haXC(GR4 ziC&|zF!jX}%;lYJPc`qhkxMJy6w(@k>D^&`@{Bk?OR* zoYW_<{s#!eH!Ilc?$0SjK1Sp}*z~p^x!^w>mKzj+$$ryEL==hQQpsq&uuwL$C)pHp zt1J!l{~J8Ic1(y&`61kudRdvQkh&b3uYBZToh0e=3! zR4sg?0O1RdMTH2Uf76SXunfcDvPgl3n`d)H)x0IQhWU&0`5SmCr`DOB@cwGS z?q8d`Pl)8%I6DxBE=w@c8@CwIwMR)O)nIoIKpm`xf|YuQx? z$$4F*R9P%Ry*Fr;eoa>MfCR`Ee1K$)g|tDw@z+Wp{{qH=0L@6Pff+2|qCxSV)N{;l z3GGe_{Vjo??Q()_JZd`HqLa{K|F>VOIQOeEz+WIe&~A#4uhA$>*mwiI0>LCC4-+sLZt;qeN(MQUuS|{5K z$1_DGZ6X#%qM7q!_99@8k;72~Q>?jQMefgVB*DmP|4WEO79}v57^5Mk3}9_q@o7!& zRbnsXhF~>wye=&?z8*|1mh|emvX+3@fPv2eG(D5`#;4FbIk&Y7y zS8zOoKGv78w5<^OK)%eH{P_E%pg*APKQJ|kWyok&2m>aT%F?jxY67mFlkU_^&pn7c zAZVl>6o+7q@_M)p&Iak9W~I~su&~9cf2ZpC`;{?wU;K(k*YKKgBT-z!==O9Jq43xb z#sGM2DLF^YNW!6{;goSk504)UbpCJ8KC)-1U>F~d*$RF`6N98UuzH<6)jf{7ulr!{Ty<$IwalvFN%yWl-%m*UUj>BU+!(8X z_Ti)RDg%fuKBPWS7G}-ygjV`$>S^@ses}LW(xkX~zMYws(e8bp=xj#k%0R@gvGtx!<+_PoMl`@dc6 zukQHFE<#N(j#|$vBG-G%^JXF1%a+w`z4T1RlV%|!kP*KnI!*v({_85-AMV(|*Iv?# zH8%84P3rD#1J7<(wN#tmxi8N=ZkuX8$Z?@Z!pMi@ESp4i~en`G!yDb0bsf_>pBDbI*D)w}(Y{ z2)?A|&58QtHj<>s=lWLf=V^$Po5JSC@AW!|s=1Q1#4Op&<^w!Bp}{8E``--;)zYFA zgtaqU=oP+t)j9XolD3Gj<^ew2JoRY0J?f)MP<+3J-;7V_5e7`8WdOHOun3avEXsctUPQ_96ZWXw+LYB`7#8&9iPC^bPi=gv!)QhD;M1PJ0wG_-5tr zlf&~vb^sv)4rVeA_3t2S>fR_gXZ9#K*gJGaal6srEFUWcjR%*I*?dlGo4-?L6LEdp z0F$>QSM{}N3X?9!;7TGL#++p~@3=bRFmvdz*V@874GE<@rt4XPqepb|`cKL-pZZP-tkSwKM>3V?-Kk@@5ROzkr8z zNGAD{oqgP@$Ft?^`#nw>@as8g9`ZLF-@P2^`lGsWN4unFBXJ+g=eBGaCOQ5hibwU` z;deC^W36l+hd9xjg9OG-X%lHZP> zhl#$Wn-n&^J&D#MQ4n37(HxsS);@mBq!jbGqllpNaMz4GDlv??&v0EMV|!Ndt+wv& z!uH!delrH15&L9yL$ZD!sN)Cj8ufL=>_c@!;UdY(0PC*qd%wNAfz-ijS*{*?QL_zp z>~o3y%_+1LLOo1g(e>RDlvyKEX5i3>I*=+4DtRFkjnTy2VX zZ6v)n>zdZi{i0Sz?jf}>TF)A)g3~7b^HaslHz#W;sAy2OIOxhs*?E3XR|(v#yceBN zyYC;`w!{21dRvxUTG?o8Rc(qaK0A2EdU&kg17SW4ZV}=&@)t0VD|LHFJ*vK`=hfZ& z?F$2%p4Eqczv&eE(VwU6pG=0X`zjedZ#wAJlTGn-_1|wrVM2CQVMFiDd>Vlq&hxPg zWfa{|x#|yhk+u`>Uz+-fgUU*~#Z zJ#c_i;z=PTGFr9IRKjO*(+q2|S;M}#C4z=$$V%mlg%qmu4;&FOa;nO3WLgU5J~ten z7i~z(DxuZYiz{TbyJxj8+M5<8R9uKyrMBM;NhaX00SZz%a7ig$OHSJ8Nn|X6ee({B zFdN&NBl7kN;F%@7iOz&@l{39^Hny+g&IKW#ByY!pnz`Q{!(S*|y4`G+b}OQGiLz0v z6v`WSU31;NT`2`~ox>X;hTn(#T~$reUy@g={xt|S02L&FERI*g1sbiP4oZwyja6Y4 z=vIP^l`+;=~o9R1`;DX zeeNWomoXXLor&*c<2<`>gGMM7{RV@5QE#L(lhT|shi^nXvI0U)S%mH&Qg(zAz*it8 z;-&zSdE%&6KifY%F!5M$bXWf4w)??+R2WmP4v30Fx_I!Xm(<|=Nivo5Pg%G}wxg*V z6}a|^H!2mPAR0{84NVGUt1&sLukVU6P*s`U-2+!9&tndWo{DsS?QKQPwt*EIa1}7w z2mE^?P3T@DK1q!wjd``838bYDusbFaGL?7t397v-)y`U)+3ha0cK_7y8_=!m^CzuP z&xPz>Bqta1^@TM%SBD~TzF(~Vfs$J?_mLFaOT<;UQRS4edI5yjzr}v8OoTgFBPV(U zj=Z86b+rW|>_HK#I7*Ta#;WPX?425(9695%nP@F^V(;z?5_m`rR&!MCh;W&Ad5mP6 z`gRNRMjCm=^p7_qa+|wiN|svpZ0(fx9E|T*Y%C69R`X<8cqj8fna}FO|J7l6mjBdi zb5lDPROgs!ENb7W5YV1)t3v_wg8KhPrn1L)Y&XV3`H=-jcg69Td$QxU%19Y;_O>RBWT7lN*c^4r*)@?&?%AY8K_$DI1+en92CIXZoS{|H@$uIA$vzwkFQZQbJi(rDeNFvmDOxW zXC&n6U{On=`;^QvMMsQfcWRD5n8xJ(_+}w(Vcb#R1x(Q|pi}WU7kkoVcTCy&j>Oivw|1&$bEe zACo?Q)%ahA&bqnn{-Mo34_-Io=edIpJ4zl}$8C_f^k%3>%0;Q+i#3H!n}sYH)&-ZM zHuS!0c%k`7c-AW~W{p{5l--%?jK;Q-))d!9(P6q*6h~)|y*V_v!Dr9Hso};kLxb&Q z!;c#8P^Hr2&MLt%&7qk-2=h3i$t|dV+A!cLl?z@K6)rh!a=5$hLMMgE^RmO7;%@Wi zPY~d`LFausDQuCwZm6AhRCwWPrn%bQ zY1wdbqd?V!4Jcf~-n7Jt#v9-C<4-hO>5{vhM><@lN_RU&TGaGF(Z1X<=(o*{7dSK5 zyNcZkNQpDeg=NxDm^85A1ZqO@?0gn&pgs=!vfG*tlbYg^b$U;f=$qlSTGg; zPU@wcO7wu)VWtraq;htlSHiDDAF{)g$3LMaGq|Q~cy@YmOa4B_*@CUTVen(?b)v%2 z`|6?nLQhUDT&?W#-dPQ`pMm!a9p95tg8KVqT*A=Coivz)cMt8P7J0DfgAraq-{V9X zi&<%0^$8Rq&vbJ5=9r;=`~vVs+EGttqx6f)unBOu1=~{sq+Z~~)8meyAE24a452yQ z?Mlc!JbS~l9`1_F+O{W*R|W0pw4MZUtPFeF#T@4vvsXAww9Sr59qVw`S-uTHV((th8?6Lda4;d^m3>#I2 zfPSj+N*9PKN}FSDAFI?;GCispu-jbP%?!L5jsSjo!_XC`%w4(D&X?;jGM8F>2C@Z}510EHjN;(=p7qtxCBhEQbqm98TH zhq_Ki@0J2GxL&hkX2iFX;ai0?dH53f0)*~Kj}x%Tqj(3F&k<5^2;7tkF*qRtxtb;l zZ?GkJdvL5Ge#QgBG4|iVQXQ>X?GA;;vNs~MDDI`pU2-qr{5Sxu zeLA0G*HfK>u)S(>LkK{@VFEl+xhoFW=t)_@n+&rko{!Z#Kaq&F*o^3LhQr~E3H&O~`t z&)f9vKQvpQjmn5D$g3K`7?O^*fR|oZ!hG5NDnfwBYuRCiA;_G`5P>WivbNzev;-mm z!3S7S!~$d|5GIevxZFaA{7MStUK}K_1rgzm{Fx_aIB3cb8bm)4{ctq$tvf@KmHl)F zg$(wnln{Uj!7VW?Pyjwy?Ak9=l`*1xM{o!C9*JRA8zFc2-4Whx?jcmzt7t#~#8d6$Xor`KP;E&7~iIz5hD z{+U;hOa1tNR2V?<`bpw5hi6HPFTCC^)VnOHUG)re9)%6&erPn1m>MJ-v|W|(1gq); z)jsQ)FHc;WkCGWW`-uQTw~1aE!A_!yW;YK^guo)VBUQG%Jhy;q>F(|V4~Nq+2Z;PI zHSQ!aY00}6=NY4)Lzovm^dhsjzPmj`J7h3B4gOGoplyXS6X;AVf-tBMU(mUX8I0AYol@Q`=sZeG)tm8a1WNhrvMW1mNu=hXBgb^1cE}F%*f=k4PRNd@ zRKQSYOd9rxPt|wi3*zx)zRoSEk@=-?wM!tGT`;hK$okRcX(m{BJ!344&hg1DI1R3? z9#Jo7>MwvYHIx-75&pOu>WYc84xgaTgD~+~?xEAl{wQILUxw{nK;M|C@+$StKpXaf zNgUw*NKB9el3as6+WP%31z)l0l~7=ZBPpacGUD9$UK&1 zIr^uc97Jime;5EF>mlRyy;o5I(Xi)*M$6J}yW(;JkCU~^B}tA-8ex3!v&gG`R)j0q zF9SmQZ<8lZ-O+kijV^m%|?W65uoB4EZCk5lBnd) z_2Rp&O`UqvaZr_BzCc;fnB8}4dxY9!E>(#@`E31jZrIAaQQuKW^%)6dX#40(%y*f8 z`jlhODUR%&=}^Kf9+g7W-()THBNE5Sz4)1rw>}>u=s?@ZoRznA9L>}d8w-Ml;5`Ha z50SZ~>^DtGCuA@xmD--~LIoz&ndl55gA>uxI7+~Pufe|~BQu?8JvC!~;)A+>HI5;H z!t?d>h$8k|>ji{e;e;x4TJ58+c%M2CF0|FHm3dgk^eB;sSqSfA*!e3!lR!*z465G4 zMc=Ja?|RX@@5+9u7fsg(rJ&pa7=cw`Le*abDg|L}3VOOHtAt4CNP$=iwTq)k;GWG^ zKx)m>PLN?YDR~$>0fs=@-oPs2VVZI}IRLEd$cm?JxFt2q-TXE`9RqA1hBEFDmN~ML?-3z-581`zNSCgJj zVntuIc|y2&`A%5k zGXe`9TeLg$Dpl$6QA<0AgswiO$-PMg@{eZRHcf8iKB#U@cuMunQ3}MMAt6hiGeePR zjmS`&b%rj~)DqroUn5{<)>JVLJQDMUTZi+=T%P=`d?tq#WrrQh+uv3@8NeEiJJ@GK z6k@1JRQm(-l=j+&VA9{jxslUUj%aVOF|pFCs!kegl1Yi^J3W?6lrGZ zLit;-h3ZW}1RUfhNxT4U7`l2MdL@7$9DVg7N3t1nAf@2j@udf9>`=j zAC#z^p|*#xL(|-lc3}771{n};s!}C}srr;IjQKXJd2e^nB0L){Y7Q)2S(#fC{_#={ z^E>o7VQR7|D%RzW=R4eQ+*B7$i)dld`fPII8*6vK|Cs&^3W97JvqE9*m_-MohoXAX zU*KFNgxYV3DC6`MP05Hd_rMrFE-N85C#|T9W_4RTn{zD^36pSNgQ=o#rL#yhM|4EW z5BMx5OM2;Pf#G(-h|*zK4&N7cF$oDan9r9R7OQfS_xGO62VwLuaZ|A9kikER?w1l8 zr@y%th%6|0zn}mh(WWN14h70MGhY>nll&6p+cua*nHR2hri|dSUVL8JkLYrcPtIA! zp@s$~B>lrJ6A&l~u6+~z&ZFd~AG+kgqUR89baZ5m>ekKh6_L;FTk$ykAGhN0Ta^ul z+-Db`L`6xO25t*PfcJj`y-UJ1vIS-=dx^n!4vK!yBWsv9BD#6gmJw|_+~er$+c9*d z8Ard5`M|$rM&(Eyt`T8EnMWZWQc>eXY&a1zAX6U@ev^-0^;5zu?(}(j1v=l z5&G~nuhdIE^=;gDzhJRLUa8Bu^O;d~@F?6pEdP_yp1@C$PA&ey+XNumu_Z33s=X@SZ$Wo*R3@5r=>U6^pxyL#s)i%qZ^Z_f%f$Z}#;QpT8;BQ7E zXn%yM&4F!Z2O!duSessraQ2VOJ_+iu-<5i4ZG!C%-MkEeAb}z$)G`CHz}Iyf zQC&Tm5I^8{FqwbQ{<889MB`)i;iZFIA3jJl%OyBxml-TF6%ipg7oyV__9|pl)rw)I zLW>4u+~N%8tEW`xa}tfGz8-X6ugKI3Qt}en7^AYk?x@SlvsciQT>?GdGPY(|A%5hz z0kq8>Rg!7zUc!lUFzBC>VGOY5Sc6>_J<^MD%B^;JckLBWdg$G-uHuJ4bF@Exr&F~9 z6pi$wZ-(qxii&?Q){iMw+{0C=Iuvu+=KZHCHw6j+jfTP;B9HV5Edu@;C}spdWsyW- zA(ZH64QQsL!>S)qn-3>F&wMv!92H(40kwX)>&li#m#gmcuKluBd>?}ZO-RJbz)n;{ z8PS~E=7!@%&t8ctjZ6cM*4U4U8BcYt7#H6yw1IlD>o4*L0Ce_X=7&_<4Lcj;_@Wj^ zV#xwXF4zXdskIT%PoqCht>XO|=M$^>%&_qG5&&szJq1?ad>d4MgSu#hNaIxSQFY5p zKoqI{ONX*a3Hm9=p#%FGwGy$HIU+Rz^ja3h3#S^Fo1oxi9CLf#snBaw0IT49h=m0@ zd>m$}@zpbWi6d(ih^la7>B-@xh44<(Xre0GYj?{%4n=Vf ziMIxb8n}opS)bG+z&MwTy3S(t!je#JV|~nbz>UM@*?#=3clcgSFGAe0Q)y<^af1bk zzzN0aZkHSefWtBpdWOrk#BD>~e?+ED(T{&BqTLhCMq9}?%hF7P0ECoJT$uAp03rZR zGA5|iO(?$b*9pcqk!6{Hh>81@mLI`-H>X9!c(DDUzeDChcP>faC1&XMnD(;6gx$Rdjxm5OtUdZ6THqKZ-Gz=(6 zU8EnuyV1Jva$4sgHpAQ}O8ivMk6jBR$HuVkn9-}ua+s3MLJ|N z^gFhQ8gBDw6}Z{o=86N_LwCmz&ZNL+)bFQ9|cCufINSe=kA| zG>$0YL5G{es0LO>^{WoCybbro8wAqI%?g;qoFHtw@$Ng%EZs}Ali7kdEdKiH{<4;t zFy92Bu5$l|=RFVPHiV^sU_4?0XJERATH>Ejk_LnBj-|RaKm{cxKd|78VExh85Xlh@ z2BItQYJGFr8PiT)vc1u8%N{^V)C_GZ5mA9~E%Q?PNFJyajS8q$1ASr5R9=WH`Nl z&ySNef`S)lr-Boz)ZIB*$IU7;OMD6h>up+W3kOO-o1GyL3?xGT7>>bZzw$7A)LZ0H zg^nx9Z>@q3XyYnzZJ?y)s(CiPp}r!|_wQbX6f=aZILRh*#>9h0br;ysUo}@-P69=f^a! zABpUUpcz@xBiLSY5j9r0HUyy{iand`)#A^;vaOUMi)TL!9JSW=ocGy9AD%SXbtY=H zA^}|03uD>luBX)bY`qg1DF!Yr-a1nl(w8*7&M9G3)chx&e!!^6xg0+KTz~$9Q(hak zWzi`(U-A2YAO#w7WgvvUIpfeBT&ua}lP(e=3d~4!PkPH0R3&1i4e2t~k zM?<}s82D-d%F#%|Nt1{2-`gdSkI=Fs0oW%>YwI16{HlMxUrK+Q5g*l$8ka!7?-QkQ z7t#IRk#+&V0{t+r?a@whbIeUjFxLEFxeO&&kn{zZ(+-TGW?rvNe5b**pJ7&2ZIkR6 zGsj*tX3V52)f>!;0=OvVs7}zL_#&)8{R)&4S}$`pS7RLqk?}T?trr++goCrH&Ir zJmS2EH-vP&Jy^RiwZrbJF`*%Z;{3}(hn`!Y$#}BY1KsvhGl7{K3yMF-XX6JLqGF-s z0g%lO(JTH$w!c!cMvH<*a0mtbw{td61d+gAN_O<``$6CZ>{YCBc~^~qr0JYs)jbxC zKAkoVKAlgyZOX>Y;whMadBJL)8^8u&UIGC=V_9gxBg%6wqOD-BT@ZB>1V*nNFFUWM z>a{5M%F1|Vf}=d*)>i>{;5vEAl?8BSeqf`X#BIkZm2K7Oh zg>#oB*o^i(SyS~^S3!M6HY1M0HpDd)jJ~pRNRR_UMu6g7UEmyx=Zdhxx(%h15f1bT zUNeP%sRpG>?_=swGx^DU-BfF)kxfV2bh@v_=D87LsHKA+U;(E7z?4qAR{ZV6%!h>7 zj+}1vH7wdMb7!#~G<#9M1iT1Y{D7Tkh`Xrz#^D!c8YYJ?K`eRiw4iX62YmyQU!HCz z`+?@GpXL%FiE@$5SQ(`T5Y#4`fee}=eO6ZIZnH&l4givG*F&ck$}gw4nXq_y6w>I5Uml|+`Y^Oh9>b6QToB*<$2G@&&p7U z2@sY`1Y2p*&R=QGL8<3TLG>7_Pt)jXj%LUgAN_%wyWSf*>58?Ib{DO8zQxxB91f)%%~JoQIls1rZ6p&Jg+Ki z4+yue2&xP#=^S<{_$d|FIGp1?!B)Pi3BNT6oHmCPD|%!m!L@?{ei^1Xp@Ii8RjKTQ zT}kRj!@E`<6FUZDVWShxATuI3hC-cVZ^oPOtQ&qBzj|E+`FO}+WV&K)h>2W7REhE|eovjzm7d8E91EHY7lG z&q!eFJF&xm9xykMohfq)kn;UzaclMfc-+&zE-XM~Q9LO1Nw^L?RvCqm| zZG#$L3c<<|uZ?=U+fXfxG!sg6FYrQ2%-Ew4E;cP=Lhqp12?r0VFslTOf60{stM(hW^_i|pb(cnW4CVS!)pi7 zKci8nFN9vsyGh_-isXspq@lq08E{xOX=6zfX1MBa>GyjklK zxQw0j+8)(~52?A8JtoGA5hD8Vl88q6Vbr4dhCVr)Hb^g~b_8XfsS>L>1SKNr~p4f;lEkZx$Q+|UdSF|2R?{???uyVYBdh<^0y8`z^MmrVi zxlbE{KXF1Rmqr);ha${ch7MCO{gh76TiDQ!)@&4jRO2RJi9p&WtQSUVSyMuhj=vr_ zw`RN*XxceWp4BDqk3bb$vr)8LHc3xj=F418Hr(S*bVcwyq=-!zwa;l1>S%Jt>CdK_ z$07tz`azyCwd|OB?fxzpbWOfhBLE#fsNe`F?-SrmNny_S-f2WDE-39CqC<(gUISJK z;dmi}p=HPg+CAch$A~a5$Q}9h&P=eerpg=N&~j}^G>jR|Sjt=uQx7mFuj~jaST_0$ zoEMDH_Y>RD)muK=WyB@DniC&AZYnyKKXAFvFEqiH$x!Ly%D_}O$6PV=i9XXfdOMn= zD2|_-IGy7XfiV*rub$8hc)2mvBuYT#w4oz=Iw9tETd+<1eyOYP;<+^96kI@gfJkWf z!HH(2x`SYbQv@2)fZSnb)+kbfSET8|F~+)ke2s@hd7_-rP+0%jxX<)ouP~K|SzR~- zgAA{lOrX+XW7YDbk`F|nGrk~nBc6QzC<}B{XR2n@6KO=-==d1o4GSGV!FCVl)Y#YO z`PcG}(~po7LsyKCRymkQJ+Uu-0sZ6mv)rCy>g^FJ4n1YP4n5g}Qn(V$79e~A_>>51 zsWrp~!Ziin_2E7#j$veiu&%HRfo5AtBPVa(j(?E1`?%-5ce^8CfF&%P(R+4-2teCMOw5#n>yLj7q=TCsNG^-#?EeNr*UMgqOBsEVDQ_4A{` z`&R|nTpXRhB6cI37~s%#p=bI!q<~1eGR3s?ds1G+%iDK0msT((q6}*K(W~{OIZiS?`f~Jbvps+Nmkr&!D08Z$v4KEoQ z&3MB|5mhU_>Rt--F*=m9e3O!u?_ez%5{O_Fo`@n9Y#=>Ow8?Pd^c_pp%ad;UM14a2^=`XmSI2fT|6mv~Dx-aYeEaIYu3mX$O}y^cxSta|@r#Vc{ch z^(B2AX97at#wGK+Z-vHDO9dQZupGf-Fcgys zC-dA#4n{%E$#H!RP!`Cq`tO-`{Lem{m%!-Ro|)Ym?(r1jW)3!cgQ-yX3hm@aC92Z5 zHuu#hMU$rP!q*2ipDlr2a=brE-l))lQ7Ji(`27>pI;D78D4CL0KNIp&&@FE8)%!$j zeoECbzpEhJ+oT|C>Fq0R-F*wHfQz~CRG)>E+iSwZ^2YxD##oXQddC3tE`NiPed}y_ zHt$Ky3cXL#pi-V>Z-uG!ebKkYqEXXJGw%=G?W8&f{O?OEnwZr;_u+6>P@?$VPVE&c zgu^oA(*cb#h)yr*Y`oOJrqReXq)8_Ea_2Qkd7gnQS<7XpEZHXLlJ-r53KufLfK2Bl zIC+xCg?y?oL$2=;Rcw=oa+I5O0P!_ zF0LLCH0guQ^Z)A2lO-rV9@TEUs24s>%ibT}`!?@{_{+splA2kemJ?Qvy%QTbtk|zn zpzLw7JTK3^xe-kxQ>USu6TZ+VN7Ca8sU2HyMySH28!1>8Vfiyz$qZb%+^7?X-L_O- z2K3=G$s5`n-@t_iidUzGNeMmpHRJAha!U5I?ieN9`$w-rdfixLWjFuyHd3iu6t9SI zoBpmcfY%_rLd+0CjQeG{W@U#V$uHn(l*HLnHhUgYJ?dmYz+qpxVujM$u^L9G)+Vyo z7dYE+nyDL9b`v~y@6K{U8+T(>*+RJJ4ua=^WEgF^inh7~Guy^2W@!Hkgh*s>Hf(Ga zA+IIlkc6|HhE;-KAq~tIN7D7W5Bf;>97S=d1=v9V`J|hEPfFWK8J2m)hj-~yRIn%qO`~^0zAS3eU5*jFIO~ykidPq&`_K=*F zVLd(853zL6en6^P*t+A(;aZ1sw3gt9BTlGNxdt0r4dntpiXyMNAZ}Y9Gte(Q9r4Du zCSf5uj@}$X5@@#&)Bx5|L)+5u1|r>}s$b0X>Dh#+ZSs1suR+?f<9GEDkjTu^TDX6e zLU)lprGg0{+{2oMbuU)&(~Z$T%Zzh`#uQlMxykO9A%lP42{rt6KZyAR(Ec=lS`z7` z1%Kn9O~yrE+EO;Wv}>Cm6w`SQmP>Ko#IsGDBy>%<1JkN^d6i1$oR8R=VlOF6`Vw0U zS;_1Pd&WHFegAOvhPC0nVqFEM5Q)hSBJ4Q3Pw^SrQZr{-Q9qFGv630}>%jYl#`kochn3Qjs0dSy^u@h;9 ze}wZn+B2hMfc6~7j2a?Cr_sjp`a1B!K9phyOf*{?w$3GY$Wy9ChAVT(+?`F82=lQV zY2HU1DKLDL*Np%!vTKPsBjIHS^+NY$+}-s!53hxskj2C}n}KEXwD`dBb~Z-cW#4`I)i;@nvhn?%(3-nY#hiVK3{L_ZqJt9Lw{1!f$*G9~XPePDUyW1tfWq$2!*VB|>`b1uS-QToPH zis;xs5c^+)L8OQ8{ArLX#%wN^P=sEd$vKm#@N423JNaYu_o9$CfN$6w#dB@A*~~%v zu_-TToDedg+qZ6YF*VLEZZUbl=xr=v`lz6(eXz&xjG9r;GrJWoJ-ucTk6W+kQdDSg zpoYCv&U`=qB4yJiKKO$Xm3qxRN^IxYAp^G|qnm>4XPhxy9Levjzu910&r!)?eI^k$ zfJu}t6d061??gjTF!qQe<|R~g16INLqep=+d%8vs`p*6S^S67-Oup zXs+4wkYP@k!AzyW4tYR#Mt_C z%C;@Id{onvJOsgigrXy#Fqnqyb1=na+gvHFMn7#Cvm34m6!xf8r3Rh72XegF3 z=-@OOd+vt&BEH0$AK&veU(7UZh6v}1AV8WCdF||XWj3UL?MH6VJ?tC2d%s87m;1_cB6lFIzvpu()q+UcfW$xFMh%RiHZp## zzp~1%8VC-!#uZ(qy$#)wF*zI>Vw8QQzw;-*pgPbN*x#I#29x2$p0s>?PmCqF8hFMC zaM@fc%bHS|QzHPS@2Ci!wL?LsF=UX;)E&@z5f}y?nkzh;&zd#WN1w4r4a)aip;9;C zU?x#(Z9L}VcZhupGTD(MH7p-zK?L}q3?nW8V`aDJA)*`4E8l@>yQWwd%G5s37Jmqbg)7NXBJdk99G{hVEtwonS*g%XHp8GWk5H(`A6T{cqO!L~*Wld(L9wA`w>M zP?_@XTJ2?G10A3!sDQOBA~yy9@h*=>WWb>+-EvYANfAG z_#A_w)VV9y!cAXAyAJODy=8)x(1zgW>s@kn9#fNg7OYV6;~&uFyGDax0G}2oa_>hH zk}+G!C`jFrNpDkW+we?She>G`%hX}t0nXxGtMt-qfepahIUxMk)`APT@r{rK&=c^x z2~nO8#k1!yn-0@Tv@W}MMmB9T)6NWS8VelDNKrc*c^VkNvt%q%{QKC|^tw~jvcvulEop$1&DTW9m-)&TR!Dt>#~ zo(zhWKo|1RcfpZMEC`)qbU46BOXKt+?m+G#r^m z&!GBwy*;XTw<`?$e*0cbSbYpQPeoHhYS5J<2>r~Dx!v-dYSc1`$KEB-dIP6A1!%iB z(nZv{R_A=HZd@Wpi<5I|!eR8h<8*IT-sAl853~GN9kQhKv&JV$v~4=f>jh$&OWCmh zE0_|0|G5FlFYvw3zlTNzcQv||76o%O;Sg|;nE+W^^v@T}8!EVB zwP#(=j_0brq(+fgX}oeHzPAzhjE58InAWcEP7Oiv>}Nmzyks-ydqYl3n_kyk8W~%# zBywe6d;W}}(p4ShVM(1c(wXVM*d-!la%U3EZETGnGgxmNt2M!LPS4#OV8mfz-;MF8 zzYgUX;?x0z!rXBa*7zI8FYk@bg}IA&*CKh)4f@2L+F!y=1b9BjE9YF=Ia;3CWGK02 zypl4-Is>sS@h7z`Ga&x?oiOs+{*-ET+Az7VT?OpkE3-(ahfB(ylFK@1n0zlf@l)p5 zp^gV~0WWGgzhoRf&-Cl3EC{%8cU=$ek z&vsHIB;ZaCU^xAM4*xj<|78>78T-dMJiA6se^ zZ1HKsKQ{3T;%;hinpyT3y6+{AWs&6{mCYc(Jg8D-;}ekf`nNW@pA(ID)WSmD;Zv59!ARc1ohPt;p zWC4GFXa~a(y~J?oyK8i!p8P~@^jdN)68LXqhB1RC-;xdQXo{rI^1Hd}mw4kHRYhl! zFQLAPDuk8y+TbXz$icJr@T zsv?xd*kYeP3!nGD$%THn0o`X1@%sS^d#~>?;x8GsO~|~yW!Z2nVC${ zDk6=2`!Q^6*cdX{Rpl0%uwq-=>ByV2^V0TtAs{*Q-j&aJ2~$}b%C4qe&_)&1KX*Z* zD)YQsqfu0qn^y^Q=*#6(2d`Y^Z$EPNjv<#<9@)6PPCs{af^S@TYPoscbH{S0IfDDg z&v`v5fA7m}+q0ARaSKkI%0I?o|K`+N=eJo5U*S4;3HQ3&`e&BaufKEorzf4A5!JPA z%GECFYAyB;*H=G!+^f`f^6bnK_39Q{UQa7+QC{7S*?VjO?4M2}^+%wIMnviE_FsuB zT=1<|(32lBh->N&KgnT%I}U)5by%u&^oFL==`qf?H%WF__Yh3F0{%f%sspZ)UtY!G46;hq)T z;6=B6RqBZprvMEK_qRYlZVRd>f&_$9NBo3ZZHr(jf~AXG;QAdP*ma#Ftva7ID>u-}h?>cj)re)?(OA;Q>=VFEQ-& z;$JqvJ6DZ^Q?LTc1xE0JfgIhq6c`b>6b#=O1q`4LWwg~$9z>i>!NkcF6~8d%{>c+o z-DyB#`|vts z8xex{%gk1>-BP9^%{X81M`XFI<^Cby?wUtOY=2r{JZS!*jh^)CNeQu^GK-bjf?PzxjHIpfLa#S40zoeNC>fp*;~IihXdHh?i3~YpUunM9ZHzGa_=ZUD zO5bC_c-3_a$#0*+@}WEShK~RnrCY2^zGin$7>48(?9rlpM_6%n&b8?Mmw*P;aBKWWi#chq{|&&ly*+?nzV^cP()X`l8$&JML*kcLR~dL1EI z?`7-t*2Hg|x;Wl=RO!-6?kOz`EiYI;gF+~G3aRX08s0v5fYkK9@hk)5dDt~!jcYEM zXkOf-o&Uq9dxLfRlS^C6xtS#(U4-Muj#9Wfj@6SuNr@;b@X3?CCb-WEB__CfMoa57 zBC3Fi5}#gt=hlX}Qhzku(a&Xi4h{Czwlqk)2})^e1vE^1gVN)KQWDY`ln(lnm~JR) zWC^I(yXL+&b=7Z5>lZ$;9OT#BYoG(X)%_DrFz_k-twU%@gzOk1cmw2(+BYB!HViJh zYgqRB@uguc=ej{TQM&!n$SLHSru}&r4KqN#nK%3s!K83?E8S>lRwnI!ZI4Ztjk0&? zs|#ju$%6cNKE5d^t!QiQEl%6oD483kEP({F9C6Wm7k$;W6vmT=Ga3_5PqJPC%6K9^ z9*T6@u3VLsRL_{bLNneA|GD59bWV=1)IN@W=$Sz>J2hI!BP zo3Tds*89(U``ovCZ=T3=&&K6nO$eW&2BF8Pghj3~X=^{F;m_k?t=BlR8|u=ubmKbmQy zvej>gh1D`lFpB9*K`}7o;A441;3JvFTtV|S_N9b<7%j{4s0aT1=7E3K}JTRg}!MiXz_^C8&U zdSpbPz~L$-mXZ_FHU^atG03j-tClVa@_5@AtQT~Rmio=fUZpn?{@m};`NPFPuVZ5W zI`(M-Z3BOR0hq;3`q-_1n0M@$CB|*ZX5Ku}p&P6Em^vAew=gmJsx682FFv12`V-U%W0B)Ime#9 zwsELpEqx`DfHPnTyHR5X^M`@`Flkx9i>HJD3?b`XIO5wf#sN7;3@qU5Mog56@18a% z1OC9_dhch`CX~?LJ)AsW2|_{OZ;mb~f_oViF1d&G3%wOEL}w>9cwO<&5x3<`{}14^ zJbf}!tlWVvntXwB6WpSnX~kGvAJBy%YGn-1>Ugh?{mx&C@x(kXpc0oT75G> zS{%-OdLJcIzWn9IL=x{&0Qq3Po+mZyxFeT+gVcc*hudR;cOmh%-1P%{URbn2Rl)kZ z(+%*~%kpw4_GQR=WcbKNpD3;QoOI~!^__B$uxC6FYD4c+qUO4Abn zX~EgS8$FO@P{)&8MQ-^hrT386flvLHd#&}3P?CDM;~?|-s;Cs3?GyzValdc+LI|2% zD1AqYc4sCxqXo}$FU8hcQo;D@6JhBRC0^VwVF^u6DWQ3l3gjQfIJWog-qmHipSL;X zE?tT}#)~!BuogO>$kk*5wUBL7pJ|S=}JVT zZl^3pJz*q?!Z;GGTlvtdV0mVs2Y;GPCkM(UP~xo(qq7@WJ24~b57c{VG`~^blvb}~ z(1O}quyy8-6dcC?NYLCmbLPm-oXu;X#@u_iG;V5@H%QthDHzxqp~lIhg=ej7iMIqP zCbqb(eg?rd;BTrw;BV5m^1h2jj~~P3$3FVt8UMETIEj^gK%7^KVSjwzJdD~{tsre4 zY5S=?kYM{yX4M4j`45c-M%YwZ5=d?yps zNZYJkn4kCS^gh740+dDeuVfM5e;UMAG4Y=pA`aL<9A5M(RQ)H%+khj?z&G^=Wt)f2 zXc4R(N_;ahiWyw065EysKNdDxhC_y8XNGomtY840yN4w8EJU~BAWRm8(zeBcmzr}B zW^XbArN%=)aLVAgbC7$5lvm{KCBYIM*cL$X)zrxkUeuimfn5ne{PCLx*rlN{Y&~WS zL+FcKW)%JP8Lw8jbEj46_RD&E>jn9Z9RX4|0Ks8!;W@UR@H-r8f-`W8(;bC#|TasO%U&L1f}7L0jq zrBGkVt}D16!CBlC|NH;*LV;v$@VLyCKS9RKK@D;&OY`rifpdi*(90SIe{Yw}E#_v-+aD z40E$Ti`Aq~tq!@e$s%y?R#v{2RV}fZ7{}RM3_tMz$Jug9 zed|F{^sD^^Pz&xzVrrZ8C)&OuV0MSTIAqrJ5dk)b9B z??I5VNcCUO(~Od8+mRsZxbt#RYGc*IubOV@NG%Ch00g^x351D8wz>FL)0|vTE>+w#3`%N#=U;6CG z4CO26T#lM1us3LkU;$Bch5gQyjXnI`Ccc1ui>1~e6bfkL^VW)Ry(rZQSvvzn*o}?Y z_OEPST*g;&5x<+czbU!CJ{P?Uq(UpJ87L#?!FOe(EsYlqIE4;H{RS5 z7Awh|7;N z-n!J~*>tD-?eLTMn!+y&*$+k+sDniNdzf1rX1#+lV$BuO`x#ybJIIH4)V)@egT?&p z&Qs4bxTsk`Tn`>vbB{|<5{1`)tQ>LOW4srSechLu_zOdt6*65lPixy^c9$f( zE$7XC>((dp&-HQHpo9CPWa$&D;eW$&azBXodsL3yw_gRHwv%2K=R~|Gv}d+r^@&XOIcIKuXsgdRZpYS zN**mjCwo0sY!MslHl0}a^6~z$Zj*v(-B%ZkiT~pMMj=H=XOUL*f{UAt%=WT*E(eQf zZ8a_M)sga~C@i-OJOdk+hEpNHpA{Jbu4jSUpb6{E6w0rw9*wNXYYO{B(2t=_tm1?J zdppIuQR5D&?|G?<*>wHT!r7jkDamS*C!0ko+*)F`Q)rJ5<*TQf34t78$ajTF=Tnm} z7g}P9-wfv5Mdpt8G`Y2JE#FQNV1J8f?OUPex}pe?ML$y8q-2q^3y5pj{GG<^e@;#sj-sbAKhu_Wd=i- z>fQxoAt2c@1D+SZ7--C)r-a5_m3-A}UV&n&u9h6OtL73zlR;`5S3$CYH=WNkQ=JBH z+kp?lF?{M#ZCL$#)qvNg5M>t7i$2WoI2X9Z!zf@KFvTDRqt)BoL<%aEkb-&yqhW2S z_|5||aC9?%U~D90Pc*}=i6?2$6M4su?2GZ2S>9Q|O0#CxpMu#Z2_=ML+qWaA#!2Gd zjH0}rwJdX>DSBx2E#>cyo(!V*~i7VyNyNRkb#uoii^ZW*{=(E`{6h<^=G4$dF z{DGO!7+O$HNo?h<|L#WbZeZ=Xm4*2P!cv^(<`qKlDqEgDarKY#2SQ@=?TLnhV;)C-c+NSGlExK!XTMgberspM%_s_tya+n2ODEWv zH2W69i|9M+p9XbDnzz>p^TU!Tffx%w^DVF5014#S$>O(PANFzJ$}79t=h9d16PhwR zkpwL{JUtYh{i6wAC`vmRStU{tkd&zsAj_RsyoF4BcEJ~+O=c^{T_P=u_4_&#I>2Vrg#?wJH}E^JFa1r{@^5UaC*n)MXcTsZu=IzQKbBYkN)<) zp2s&KL59a##P^JzUSxuIsgF^`4EQv2G`2!xSy1zqdAQHdfUj}gAH&nZQpPD;&6oDi zMX~|9T%QS{eJ9p|V^8qn0^gz?l<~*)_l)nT3ebU4{${dAaE;rExJ>N0Kor6vG|^V2 zRvAHBxwHF+*Q$chCTQiYl_2))2Zo%#a`J;Cxxq0UyXYs~BV{C6Zj|^WBO#79+2hyk z(jtKc+Gh@}vmM+$Q>`3;%VRQNov32FaSq9b+VY%!i z{t}v>OLYM%Wv`$AA-Eqny)Ym7?xLr=opFyRD@Pbp=%p_7ZeQgHx@=)9tsU~t=E7-I zoyT@Uu-J5?sVK>_x;@2H`0e<#Q|^uCDzqinmQd>|m_T==`C`1Uc>4(Y9#pXcNh= zt8S&tY3=5XD{V?ELS+v&Zc$ys<`jR%W}L+9dK_@ZxtX_dN9&`Adr#1#IF;p9qIOUk z*!MzSVc(Cujd>H@k7-x%DBKnHJUtH;GLS7P>~8{6sHj_2&l*JMqQS--ahGs%sk{#i z21by-0|Kxi9KZqsC3QqMxe)rIy2+2Uw$A)|&M11k`SX_%UX<>ve+jP-(?per}!EnkKCJn#Al9UNR5yqlS#nSz=Y@OCg2 zz=fa$?HK*NOUgNVd;tKT3)tK7$N4`J6z45zf-OgD^|Xg@y>Jyy8910Uk1>p9V?Vb4 zJG2r>cS*T^zM^wb+XH(}Yd^mRNPY5|rbH9}&$&NKL5whtovwxsP;c^~Mi|+^(DU>T zH3&s@8S?arkU2ljG7TCjP0De-&iBG_s^Qpq=Rw~0ZBi-j&5T?5sgYUN)0_X%^4Gq_ znnlg z=HSFDBD9c*)k{7_u1pwLKv&+5?4#J<8up!MoSNfh4SmayHW33gNyRQ-jrAu0r3W)n zq~_}%f%cP6A|{cuXc} zM6)y_f0;wBz#`I@2zYCcbxbFW-D-SO>emMuCBr&wo!~WnNFu5RzC4~kmMw|HemuQ^ zPlS2ky8=m8G>Nn`h-Q_lg(7aA%+B#Q1uZ~@B1qqHMj$Y@0sWT#;Bd}3S$%|>mXjv2 z40o0TgG|2;F%egjDbFm#O8Sh>bwLp#;;?!6H^Grh<~(@?X2wlkKav5a|9^*6)=O_} zPHHUcaIRji!%7_!b-fllOeR=VAUZI#_3oPnyw^?c8SFnic*mYU0kyPSx!~RGnYwHvr-fQZ z@N*#z^yx$SbszdDiu@{^cPCl%Y?zgbx0JWs$~HF*4@9!_x0iF!XEXCCN* zpe>dOmKhsKV1km9*C57U6`ia%G@WBw=Z7&7tR@)~$^Yl^=K`Ztr;fBF2ugTubXWMB z>PGxDKPS+2DAhY|(FU^w6&MpMZ==vToKO$WAqhZeWCX^i&(@rE+0B?Vb%9S6I3&xfgUl8 z$EMo4U4bWiE9QHB&qy~1&5xXAh}+cR0@weNO>^B6y3eC}{IKKm5xQ?8=`xz`vFiwT zQ(wSLph&>tLSx^&C)wo8*7Z9e8ln<8{h_&0i125g6|GltJ)cxEd!{583X)T^2CvRr z=OABB?OP(5^^1f3&T-cJedLRqAY$UG|J%qq>5^Z>2t2&kIY^M92pj2Um38upI%3N) zb#uX9_J}8_*<{MuK&5~Z%vFBG#B5NptGm4;K`%f0kag>{xYEX_h-0k$`!Gp=>|R+> z+9TzJ83&OehY))9w=Np=>?uq%5zCAR?&w2H~b zMxu;dRa}ELc|-HSh0DC7?=NgUWy8CH?i%~Y1`rAGO(h{j4$}ngFWeo_>N-E*a#liM z!{UpEdAz`+NDhH`fYFmI;rU-}LKH*)46iy3Q$YHe_C;^;>U=4sNsohnIc!i&Z)avV z?$INWK$MBG_UzfuiFFs_wYREO!FY0IvKNf3>OS#}O z>=vRsWGsA%EX#uOK$9Eu%`tqK;6R8c?j+N919C4K&p+XX@^C(a)nFrOLCSmJMQRd3>x43Go0I=gx zNo>RXF;B3FU}GriadzP}^|d2VzvTqpf^p6p;Vqh3{o+B^5NJg57g0-qXPwuc?0%N@ zAnzEO*7i#XSxk?>W0PUzz(O z-`y$J69M91w51katk{OpIKMOyE)Pj`r>;=%`)ua@m+;F)1Q=UJI4Xwj4?n%WWM-=k zYNBCM!QN{ap7xi{fCg*UPGJ&90yuvJE7%GE+>HxeXmhOH&sSN61q!7BoZZAN@#gbFaIzG^GfYi!_PeX38 zE@R5>>hwZQD!`B-QJAU(>jG2ZB|fCSW>*hELPYWxvM`TI+sQ;Qdfb2JnrG)tW{@dp ztI!N_aE`OeQZhm8z*nTTQmf0|!ZU1)GoYPFZ3}I@*Ed=4PV18gMU7liVtw0C?fJuE zVH^sV9yDwE(X``9hfjYZTv353OTy;kc#9h#cgkNyzRKfTAJV6(wapn8-!Lx{L{p|l zB5;~W@+?p2Tqw)!?wd*AXwmOG-RM@`v3__weN_JiXfFDW7c8@RdLsx;?m9m{8ER*98$pjr zghSck4kVE81a+fu(sAwVZotpWRyVV`+=!3#>I?ym5MZhiBEE~{1NEA6ogeR{FJ7Tv z@3+nu_?+36s;>L7YdFfWb$!P>QAOs$egnIpN-Cr7eXyX4TdZVBd4GQgJ>K(3%{$ST zth)t_zeWYH*G_65gxw-2xdS7Oe$^vcww1|nT@G|u-#uCM22ynPU$z2QNM%WVsz7hO z7hM`TzLbh93fZ<+fhw;A3=X_553QQA2SuB~+{yh^1Huo~kB~LkQYj=fsapPu`1k|4 z<&}!L3BC9g3zKMzZc3tEjwidhVcnsh>1zJ?EX-${H(kKi8{yCM*$UnIH|PB1lwld&sgj$ zc8cpMj>VqaNdD|;*)SM_Ztj_9U?eyw5(PORHoMMZHO)tq`ct2j&Cll#q}|LJ$M^MP zcW&9bc=5+(U zIKHU@cg3xzrl!~)nQ5MpFv!vYCcd0bq@JF`nzo8IST&?T+bb@u%_U|F4z}}S7X*Nu zMBd9xE^{JFLR1m%^}Se1w>7Z8v6!>0m%du>JG>eW$eSuv^lUT$mveKb(Dcz$UNq9A zkn^`aaen?B47Cx*NyKC;Xh;i;)JSdUu1V}EI99F)Ojk4x#H4npJDXY;jnijEU}vL4 zB5%`Yr!DJ&m*We@giU9ilJ5r>5rmjGC9Q#p674dH@pjok0a@LEN(||4bHgDIUefYE zMpG5oZe9fCisS<>gcI!vyrc>Nyxf%Nh?%=RK72QE< z#%OiGuXSttn-tLkpu2L?cjB3?U7rYC?7MhvnX{?R6Zg6wpPwGXza&PhxCVT{6KI=K z?3M1odx3UXucZGGM$nmuaB5eJLIOok;=dcE2@`0gp9mdmD5*z{-Krax<)KApj~|W8 z(!23E?hX?C`5|#(?;=D-u!V*LAwft%Ixf0gXW2Yl} z8}(7J?`6|oyTR{>&>#=Y(X@ML1ptSo-!-IsGupG3^+E!WXT)_vGN52nHCV~`Ub;eW zcVE*QWyVAIIuLRC!-)6eD#SC!@wddBx4W(el0y&?x}n{ck+d)zezf1o?uu)k3g+>V z^!1nZ?)Bfg`vya2N$A#zxvF^b|AIQ8t@<7jTK05npKo`tM{^>SF^os~moAE;k1Se# z*#@R-bTl1`$>0Kc#ELRPs7{O%jBU#v);~)CJAf%ufd&V${(GZ3{In%;Gvg$LRHnli z+yiW^QW8n_UX<+6DN6c@r{?T=#7h=#ghvw_*HHGlwFqsLb39FJ@9Q?S*<3;gQ5({& zl{6E>dLkrZ*<}GV!$-c(-T8@LY1>>_$-a(hq{X_^<9E5D(Hk7>a6p2EBUN>*l3;*U z1U$jgw6g@NLYw>}I$NTRG%7>;2riz3jX4f>gOgykS{wRPEpC-aJ@d!$NE*KpNEc{4 z{sjzQo`9J!flvjz@@!SEv0L4n(w^c7G?ZV0Q$9%9==JUqe#FhE$~xO=R;zDN2pe+m zp`F6_1Cx_@29?}0Hwf=}tH=0JGKoF~*d#@=!Rg|9>+LEJ&xyIgjN#HJg3C1!$fdsa ziT)?+1&q{V9hTr8>~-BAzh-AMeJY}St-pgKB2NQ<|CDPePG*;h^Q=Xd;TQ$VxTwkZ zn^L-UAi?~~#|+&i)>r6viDlz3j4Q!Ptw8FzZ&K_mFsST-DrBrC_@MQPYmt2ialp6W zw_tDda3z&77c$k;6e8$QZE0_vaiz`0arP_p*&(a_xIbh!o*-6md^}nfl4`G=3M}2wcN~jw_z>7~Hzf*ZUTqaIx0A3jo7hD?R~%g3LzsNqPr|E| z?uKRwNLwREix}4>jV)>}$u69nA1H>ayks5o&&X4EMW=2XoTKxWFl$`!^pXco@ngjD zEELPzfhip_V#6A`!qs{n?}dzT&!+UrH>NSd+XH8h!4SC7Y-vvyFu|z>$wUCf4Cp)w zBKf=flG2qwKm2<5=lcKS*t?>xW4oy+34_zjF+n5T$ALsFY26QU8rz8#=d_tq<7ulD z;SuBtfXbG5v*So-xxYXchl$@M z74sNx2@w3RWZ#gIX{CVxDWL%Z5*fBRwq++}grsiRPGQi*$t_2==9ZT~Jv-rk zQ;+*q2^^3YGr3uW4Gf5v;K2FekY9_8y)rM;*62m%u>8S_Gqzi@FOCY4!L2qqeHS-46sJ4Jco#{1Q$#AmrM~0CT3VSaB~|VHgjS)y zvjnG`N|Z576Ma3YaHa=@bO|pgGZRTC0K~}#oHWzTF7xRO?mor6Vo37F4!^KFY!mdT z1M<8f1N{uAao3ODGONt5T;zmXRv&}wnTmuP7==5*5-3~&#SlAen|USL0)4mUw|Pc4 zY_rE}Kjc26mxT#i2PSwxfH|Uzyto=UQ}6;svM&OjBPwp&u$)RLj4OW@O>bI>|^(h82HAH-G1?Hf)&7o5a=T zdnR_}AlOxs+@PJx{lIgpsH^Cp*m3swkZ~VSCV0=Wk;iNrJn45&DRK|RdyQTB=JJr4 z+%JB`a?25xySN*IHHu;QypMiC?-0!f2LA8@U0oow4kvP5GhJfFJE)-@rV63>Kd6k_ z1gCrkPrGOZ#4hY=AVmZg+<)4P`WtA$9Q0_rg9kJ^SO(FCkzdnKD}%uZ##)YM^CALj z0VM13On0}tT=teoIp}~=iNjp9RSOn?Dj?ABJeOiKuDp`N>mCwApiL&&TQH`!HsTc) z(_Ng)Ta#Skx@=(KVl6_QG!cfp z*l!)!G@DE?O>^pK(}HDt)1U;J-QC(GKZD~=1Otr9Ij@sSkx4>A2sYF-2D+Tp7&-bbG_8*xv--rD#@!i zWe!mGr_u3GtDrxABC{OKN{@VSNg2e zoKre(qn&ZKeavrX8$@6B-aj@xz`Rio<@dxXg2oHbEOr+J$eBqGq#0+2WT7UMg^-#M zzQWVD1dnqDe0P17(8h}TUG~QKMsOjjcWLmPTwZIyCyBM7*pff&Z_Ehu(fU9z5n`07Jy*Y%Z%a14bV{vkwu(g4b#bqWD{%eQOw@-5ml7g!+%0bpi&m^=vZHlT6aM=q3s4Bf(6Z|B^t(3SDw;lqyd)lX ze@wb4S<)&Ks$Y=ZIm;DG+8DvjGMC9VYKZVrv6%}}Q9^yxcEOczgDoQ^<7mH+`amT5^jxxFYde&8QydM?)2mc` zbdOVa-^U@y5lvqwrx*1`jH5FuVpf+?>u~-LBUG`4NJBp8|;VOo=BcY^3-s87LB91l_tX8PFFrep-CONSEQsOI8|t_WEtlMy#)SL z{^D%|ia#IYqjk3MC#)NHk4s@9NafZ|El7twgS3;{b`#P*k8z$`>OrNoga4V;pctp0 z`?S@rNHO!7+53$)u)~Ei^2s|-dk;&&gy4e4?s&eB3nb`M2~kG zSHux9;fBF+#bB!sgy|GF+S&n8)W9?nq(&I)v~Vt{`5qPS9s86eTyW|u!N}@&D7rEU z*^Uh*oshMyQA{>dlTdAl&92BwyaBa_cOqJJm%xW!cr7O}zzMn~+MQ*8a ziR(U?;?NnqVUeZw16No6oxQorbYCX~4_je_88}M}c9oWh?R+GH3 zYU!vcQ~W1ZzDAF^t7e-)Tv1{J;tJ-LWdjC3e-0Iz7bw!>ku~#RmbIjul5Da|S@DIS zcT3go7EKaKJ86bG^xnfu)!|HY*;ezDx1D(SDJs^E$-WKfFFN#~R@310&e}fP?cY&2_DyYLD-wq{_ z%i#f{T9E5GMlxGWEh6z=hexWO202Z}ROC86pcw#W4W&4Rh>LpWi^_pb28{csN%PoA zR?>{7Ew{*3L;_jZ{bsib#$nGlXuiccVC--TrWh z+@dlEy8hwi8}4Saj|}{SzkpTPe4}X>G)Jnzr#6l2OdTRw97@9nm1hQZsWY7W4JqNj zfD9>RO&59{%c7^guE{AdC-$NnhM9N%XWC)Ob8I#TyKGwNIRnJi{Dl z`FSol4ukWl$9Yb;zeHOp1=mQt6X`1=YZYnd3KikZXh!f4Y<3D_EP9&L4WpmAzqY2p z?xp}yc#|tx@X7*kjoY}sfNc2$XDHaFk&=2qddM5gB=MNQKX*mib;Q7^YR(H+j8RY@(lRK>wMhw<26o#AVa?OiExq~Krz~@wrv!R^ngc3 zu_%o8Lj<|#HPZKBmqaCr9J%_4WnRu5R$28Zkd${(Jfco7DPUZ|$+dx#q%LHdMACMO z*>tgBuAT$Fg&EA_D(@$jSBubbZ6+mcvaXV;$;qzd%B{4#Oa1hT^ZEh$MA{rYc5d(w z0WwLqNEyl(IoN{YfeSgbsGfOL0Uak?XFYl{_%Ng^V%bBOnSUTczLN(R&mk@l`s<{r* z+XGqdf+v4oK)+)J^cHY|+@`uekS+(CL!;)>{VA!_&*eD6ZwC(LUt5B!FK&nkyeyyI zeOPajwyi@Y$4n@zus~Dtme@sUwyACPf<07jdty4Tq<2|R@E1Z`W|7xPi^i!KGd%)^ z6yJHUg?);C=oEiC<$o}JQ)APyewy@9WltFAY~;nV>o(kRZ7bER%&**2re0Hq_8AF5 zrGoG$v{PoGKALC?lC!s~b{d}7K z4q@^jFNs~0`Wj`lOqcvi3K2y=Jlg|dg6m1;dd7u`eZZY<2DvhSToj12k4iK{V*7qK zxsWUybyv);cl7fq-U$MO0pk9VGp^H*!)FM^HflyY@nldnz~(ryjMi1DTHrHAMjL`h z(!*#;^N04+5%SlK!@9~9bWPEidyPVKEM^=3NBjj=M!_(%X-3M9vb)-Zk$JG=5GD0e zR7@wvM=Ku&;HGpvDZ?B)QtFoETymz6nRSgQnhpZrfR+K^HYbt|HhVV~DprV6gA1+p zS7`Zf?F@au+22_d3@$)VBG|^gJy|hhxi6c+M<&-b7g);_W9Poi&F}q3&wZkSZfR+i zrgcKI#w72do%TljGY$sFgh5hayjQ%$(iv<-=S^LRA-WcaS)ET=Z#oP}eQudqv@Vd@3_ZWVZS*g(TED-`&$FZeu2~V)+-WJLoG6(gQP@c;RjteA;$crNSUs25OsWIRK+|M zmPZ5KxJ!Ty1G6%;W+4ltZKE^AL*hvDx^|g@`b}j)FW!S$!~^b;42@HUH7g{kH=>1|>)RFhKtUki#lg{gU&uU?(eAD_;7lu!ovgOhKD+_sU9K1_IFI8`~cPvn(J;Ng_ zTQoic17mYXT}`9D{KgM9w4}<@PEa1S?f4zDrc#HIv#8F-&rBs0tD({R{^y$99ye9( zgwv+7m9xX^>&{1XSE^sm8vfZEYX_t}gI$=4U~6#bhxuQnW)47=&Y7b|Ty%MEzs`hx zJp6Ls@h_!rl{^hLK^sfAc5FiY2(}CLUxiak4uQMTVWtC&YhV*uKFWC*jRo%iTXTQP zBGpdk$4Vck{AmQ*b}9}KiRD38@^v7(bV><_cmOtoP6SU^jYD?FU=@cPfI%Atc0G0Y zN=}bpQA2;>ta{pooa9`cGQ#D8u2W|oj-&=T+|M`*IQyylJ1@XGjyz&lg6YR3G2imO zfU%R|iQklzuAJvM{&|BY5%guFhMhrrSii1>T~N>>#d*-7tq=`KV4x07ryl9c2Cc(? zyST?z*bYL6`g%AJv!UlUe+r_LQT;>4U}wyayI7mzLp^kA3>}R{N1|ugIDi1!8;1 zXB_R_n-zVzVJEvZ{l_jrjhH^^rQoZu9F`B-4bZMY7}jzZQ zZsK9JwqNvmhGnI3{NU%U)#!_&cHCrN0&I@VWm)f{7L@h<6t8Z%Vg~Ny(kqoKUL&I- zc2mRwh}2utzq`<@IY+@Yx7j5>bSLZ%T$DL~bT!Rq(YcD1A_C84oN*H~Uv&r}DPhJd z0S?g{;P8l2@huE#6~BNddp`W5t_U=2V8CCFKLQ(&NI#5oDW51U^TBO>ScA3Nxv!#{TT$uk6A-o! z%3RS<-V@p0_>Jp5^=Kc<5TiIo@}K#1SM1dNV= zU$X_3=%I?V?`?`_QhJT90X%qwAS?UqfCU0EJMmI1VrSVF0~WC23B(SfCgE*2w%W(& zkieV>o06wZ>%OUw7N6gkNd`wA%m5Lf7AGW==tKEhP32DmZz)eiubYDI=HyjU-`CIv z&%AvJZw07BBNHYON6cgevf-s4cq}O?JBe@PA%7)OSLiN_QuVt}lU<#-WHk(L@CG?H@+$G864^8`Vu4 zJt~!t^&>M)CsVwGc!6w34v3Pw+7u6(Bu(bL%n_!J!EYbojv(be!VtmuCI&xh>LM6pFb%6 zdJROf8C+sF8a%dV^{lK3jUK^HcJ{CF+C$_^>xt7|h#lh{(lPGs-+e(G$Z{`({ylQ- z;m!0VozaC;R{0C7FVw!MJSm8#?2M~vRjH}1Utz1Ggq9(|vv#s_hX1a$Xus@H;pfa3 z{1%ozuz0bUQbE3jU*DDu_1ChHw zm^B)L=i}Jp_n_}$-y;Ic9{0>eaBI@+bB;BJ0~tIOaU+w+nR|oq8>bngp`wV%bXSU_ z?{?mr<+}oGCoDb6|7~Ny{I!z^8pnvyx@C;prI*Bch1lV`&)^bhG&Au84JW9T@IPoM z$@7+=(sd_Ohm{iEIN^>7MkcUd;DiwGrLDg%Kf%;b3a9f1>cy6W6dqUjY2ao72J|a_Bl!8QkP@avjnI8O3XzgD*q3mFA&qsA6waM4f?#*=}W(DTh@W_JrvJAgr)e5Plh&&4an?I$;P#ODfCMjT_>d?YzY^V= zcjOg$-g{;pS!Jxny)rYA?a6@$kTgn2R@AyYC1pG~%yna%Sd$%m(KEbIpv0^p#eX)~ z!>^)ZX?OoUtorr~f7|^qZ9+I=?SiuyaLAXdnscOTM9%*9(mx1;1dw`*O{%k}VEnoP z5*o%)X^r1S&m?F$;SdiX^2RR7bFIHHJIF4L2(lxM_ydL{HN#B4n1Ccm7lS%~(A${wm62cB+B+h#JP3StZ=)h4})Y(Wsv&C=z}?B6dWkDu=<4NuY4MetZhOTM9hTdpG3pE9G3Irc$E!B2{z zy+`Ik?=hlf`d$j{4l`(j;g+vAwYUpfnR`Uo>bhSld&T2JN9k?uZwNG{-s)72k6H6h zbg2G$z61!mJS{Tkv!#5pWqfX)zi{f?Mlho zGu&oPzP-zAxcOtFC?hj878(OZd24q$iI}j@xfIIw(0l%c&}28^8wcyAW2HaZ3D?1# zlXV>LsDE13(D7*@04d28sPxW7(Sd_EHSn|0|HKu5wRL=E)(Z`=ffIkc{DZ=4eWw+qLJs|8P!;%S@G|E!lU^^ z6Y%cvGg{REY_)zGU2SmQA@R4x?66~pReA$b*|0N>46>VqCmvw&3tW(-kKr43Q0Q}4 z!4qKTCJ3|G#FsP!zS$+o%c?@MB!RfuLkyTgp!v+Q3!oj22ArmS*_f(-2xnNLahb@G#MWrS`uy;wkY3_7O}jS|T*_O^56gXB zyBCHSa+%!8s&_FrpQ#(HT-~U+;Ooc{kv-puWzBb@&>-7p`Zs96X|vMzL*2W6-REDr zLi`u;pbF}13zXYom{ zmTM;W&4+hwz^(wQ$U5iz9Swb5l=|?v-i@!Wp%07RVZqeLJfPqyecXBx`tluRTSa1G%S2%Qr1MBjD6q7LTn|%oYhR{0i)m6KDq6#pXyErm+ zRbG@pbStn*+V#(J)gf@lfWnx^b>X%c5sK?+KXIRW|LwdjezQ1$0y;--`L+&t)a7K& z96TZUJ%O3&)lx*ectFOS4r@;uLBy_mUONlS&Un;_Hm0@K_8o*y22bcLBnvM%Hc$i9 zK~W$0kwB9~uQ82P0Gv<(h@HPMxI$;;*iE;sxCFK=HX3FGNlRC6pBJUlX~G{T5ilP} zH+M5nH;yQ6E0!iOZVSsh3xvcXwDIJWtIDCH2^$ zaoli(!VuQUTzq3rK&wo$G`pLTHbtTA*YlAOSMv0XyO{B%SR#3I5T5a(H1zZw#s?__ zsXoPJxO;0Ur)HTZQf@E1vIo&`)H7s*RfYJdTiOJaFEBL>R|EdL*PR0sH36!-pa08T z5N$TWetTGsth}fR-9-NElNKLeZky%xHZYf(D*#JPOARE}!>@n9uLmAOh39sE}N z_TRjZbRRZ{IS7ezlON8iWtL|>*?Ju2RE~ke)7NV8-3C*@_q89U*ap1vbP3w**CU?0 zCdW~iM|2k7{P9KnuO?OTE$3AJ8B0p0c}|GbtZqlzRpS$m9J=b8dJe)~WDHHyC0|c< zUiP7h2#|DIAP zArW~8UTG}91VhVLCax0jV(Z^@EeO12wa)!0{u_#C^dt!l2+6VgOn!K?(e(!4QZDWM z9o18J1!>hrp?XB};~HNp1SE{a z*9yp+XK-U(i_h|ftQfsUDr?zYdIB0}@yw`=t$FHA*34DkO#PamS8-J$8~A#BDGxu_ z7xl#R6Z2uu&#f1Q zcf-x#w0E%1cd$?3I>;1r)zR|TYyMg;+fBb@O1_FLJWS0gx#c4BmK1qJO5EE|Nu`t8 z<<&{hrbE%>`Lr7{%WiQ6HhtXZge(8h8(F1qKVdjn06p!L)8jXm zyaNoIOxohkOis2S8=Rrvbt||rG=l%$OThVx>z=QByBi*U8hgIM zLb9vaa#XB(@yTwZ#Y64#u$h(L@&bSP_i)x}3^A z9aavB&bXUGj?}=SN2Cm!<-%y!1p&2r=G6r=+$i2(7r>75lD|Dvjf~bZ%c^V}p0t0) zcL&LleN*`037+1?Cxh zAHIGj@Bui@V}RU23k;W1w|x6oPg8cDE?GVRYx^Vh8nLTO>QMA}6ssDQ#5e6LoP=`2 z-`04Xa|Z_5RO@7Me@@^xc26%R2 zP^052DLJG9EN1pLNr^5iCS>!LSH5=Wh}w8iNJ_8-){D^Km>sW;&OvXpKxdJ+Z=<77 zr`boX&|4(q<4n(vOw)tW+4m3SCu+ERrAB6c^{JmYboe1cpw+s6BG?w8Kv&#M27=+= zplZvFpu5m`K_AST{#+oETU^sR*XHLizw_^yvM=Gu57tl^g9+F~`SQEjFuH)@(Tx6C zoP8Jy>(5)^15m(@nX03G2!1&L9HO1`xl%gj2_=doegJrj=Q<9@Ce6O#F{HBi9+3ci zhx=)s$KnE8Z*SHJ<5V^l-p+eZ6zz9H6R_V!%irdjYL%!q49Ud4^gPdjCZfnM&(tdg z)KQJ{rggBg49q(K-$)l+El1r~hdM)h-qL_z7Xuezf(C=g;dP{WholunyWX^Jie&Z_ zYEHo5e6!*)R=R%RiEj?-ux1?AInVynEFKi0r_14?x%nQB-aPUiAHidGjDZ{*tz}fo zJKQebfUN-m%Z5hp?GdH#^n!dlYU|aG{BUN>tA09Pg9n@y8Z+Fm~kjidY z3yE$*=_|Xq3oKZqtuh4{g}6t?9MBE4 z)K?~c{zuV(s4VNx4^Qax(d*z~I&3oRnw>plGMwX#ro^P2w85GW4+m1b(ZRj(YQBR< zk9GcCA~kmVzRo2n4xmoY-M(%4t6|OZL-E(Th^U(F-|Y5+Z3QYVf=@`_C#SvG7ww(_ zW`QSq^1?NpaA=;0pg_mVk@4Xt3| zN;iy_zX)&O>~#fiPVFP1TGheK6*~SoJMA#Ff{J|VBir}S{(S%pV-2i9;Nj_{T~yEe zZza8F4jS?rprILv6W$XxIx+y7=Bv!fLCXTKXaAPt)l@)ApOFL0E2P(+fV3r_3{8+U zP5*pb+J2WH`|VfM@&bCgE-xB-t-(~o8lL~umRX*Wv#A!p*-zZ>J`DA-!jntv`dM1w z5on)Pkl|v`0d&+7+{x?U?lWlywQK@wlk55sBI>YDe zs69vn*OsV;)%;w4hm$liL=*3CXo(pXj>Clt{o#0HXgCsNs|hKTD9!3Oo?!}TU|IK9 z%`K?Z-9gnIjppv=juV!FRBFZfAMnT;CNBX|3v%P>A-6~}=ON@+(nVZ4ab-fqK!Im9 zV&EH41SX-UY}0G}d|AszDIlC+njT`z{i6P3%Gs zL%5Upuai8xyXiKwf{;e!uTm%d6!RQZ@(e?eMF0vtujwy?H<@ljDVzp3_Xg~Bh>PV! z36!fs;c0pGfKTuiJP47aqv2$-@e6VFe#XPn^u9eQaZDTKGVF|!^)SNDn40E{v+mrr z>;r+(m1^2HDE6Xn1izkcnL{{NiP$+H@Ioo`KWS!$q|Dka)fZmGt@-!6eSOtL_^Hs7 z+?Xoqmf1p^w#n0TOl_pTK@1`x(%-}&hy7D4q-%jj)5YI(k7<8A!P(#k!-$$C)`~LIyu21EB`|Q2e+H3DaV6IBR2d=xZLVALx zXXca~WYBUQ596cF;f_zZsRizinyht5ig8cMpV0h2h@0Pr-bUk( zu0mAm2-$q`JgT~*g#p@!?#x6ARSxH{_r88sM3?`U&d0B*;*OkeBk(kNnk?B z-}-ey=N*Zby>0t9Tq2r&6%gj4x|=zlq&C>SgjmT9>aBfy(Yh0p61+=`AVJ+sv)zh5 z|2jpTvveGsR8x?j)a#v2)jQeHhfxa72e863f2UO#4%({`vi66pvpu~~;gku*HMk6& z(EPtL78qNqRU$9{!4NbgQ4;4s*Ie~!}u$`sMt1AqKeVbZO&D2Q6uG43f-A%nWxpI^qh42AfG9+*4Z)jhkZb_eTch;Rc9b*la^dueU zbBC$MYZj(=fo*>>{LB(yensUM9(ZJLlN45>WiZ_7U9iM&H zH7X&|Hy+^0p|OBe)L@m?seb|l*Rka@a^y%&C+*YZ{D4%D4-zT%Q6pVAkgFIGA2=lN zP>Gg8Fzm9XJ)JuSP1@5slGHn>17H%Ww0<=_te~`51eW~D)8F|FtDnqySoUBqT*dg& znVpevAJ|+ID}59-Mk0XCNe$8#Q~O>p=>@HxG3`=8e|DnM5k+G) zGWIM86cHb(&jH|rvbU6RHW#@KBC5;LcYIIr#LLwgCRJEBkH{lPS<}Vrpzqq_6YGWP z#uKG_R}b-vtXCxN85o%H+#%446IP4#=baNecBkXJTm*+O^5)+Ufm@YkMW2-af4x;u z$9e*xUCq%iAG-BFr6K?ksNV^;P3k|lz8Tct_c*rUyT&S60MXv;_WRQ0(Y_ZJV9%sQ^otUC7eG z;mIu@X03dSLFa7&3Eda{x78oj{J!YRhrOkRX3Xo3z>$;>{w{jmfi{BYH}uF}L$&%t zXJxnFSJ^ZsuKiG8C~F`sIfdZ2+aGg-i&j7+YJ%eknRZ;GZe`=Gf- zxf|ZY+hF`i`VLF*u&$(gz-CX=-GgBB#0&Yp`$C#S=t|%))H&9k>h3%C8?)wjC*)d{ zb-d=qFa4S>oNwPwlX0}Fk$PrHCG^@O^E;dm%A{`!@czFd38P$HNVyx6w2MH}M%I(C z8V7vX3q8>0N>f(l^=jb85SV(T;Lg|V@_FGUd=E~caG5@3)bEP8u)I)#$@GkbytHE7 zhm7oAOljXzr85taWxIyEdH{tNgG*U3aTWF+W|!0wDefLqP!c4{4x|28^e!3_O_e?_ zI6q`4!|7ba2#b^bkLM>L8f}M|MP@O9nhTW_*)Kln)Gv(LasJdYSFa&WVo=_Of}o9& z3??{0#MfGnzsAp8QQ8ja4RI|0lot0_+Xm!1eX6qS@YSe%(LsbG?JwK1`m(Jw`aoF;vZk z84LtZI&M@Pe8;L2SdjC76+dq3c&AATYx;xG$b#z!@(w9O(=%DE@gD%)qrPEn<&y5Z z8VtFneSC;|K6B?JtFh$ZvPzvq3Te-Yov|o$ZF1*-jTh4sNk^<7(_kRenqJct$y}Lj>t>w4=)KfA*|)^t-w)47JNoag z<;VGJ$*szFqn#-5vU%12bu0HNJKcpRcuu<67BOsqy#@UY!CcI z!Bt$0VQm$MC1&nDRh#OZI|Hl2Bn(U>coRI;%O542hedpB$!7>IG2~a9|8QhiFQZ46 zj3*Bi6$I#YWA5+yp5g_uTrz1m%9^rAYkHn?&a?jm99T+AF6pf-YN$E?FLzKmWGx6g zX*4swa-^jwOqi+~B;r;fTd|&wP`(ToVPLxFlcf8i2*@8UdL5YM4Te)bRsbA_Q}D}H zFPCY?HK_I!PmaAC0qy5C5%>8iZgdOjX4$Y`()3U6OvqXs*2|1+&HQt)z(O1`D500P zbxme0P@rdT*Vlkw+I(hOCE!dky<9i3ee!Tu%G+PegJm+Ep6QiTJ_Io}HfHaP8VHFy z?ESmvwJb_mQVCEVSgfTz+!svMZgpEL*E z@l=N@KA2yYddxh1YH-rQsS`6Hi16Wk^x7}R_fEX3F=~7o{hGAq2BsWKIpj{7EUQWF z6n}u2l%J0U_gEPJ0*E^&bUg6+9vTpCl+WYwOc7AbFEK+jM(r~ zHJ4j3qaFPgnW$JwG=TTl+Vqr3Yuknjv#C>N5*=mP@oOHm=8k zq0NFA{eG*`n>a1gDv6xKq~0{mwDE_28~aW6-uYBCNjy=78|^If14k^4ELG;C><)|{ zn`bTd#0x?btlqR_If9y7chA8UtIPv`c{qFaoqhe?+~XZP)YXPoleu|wb{;GOnPPmw^}HYCGORsCvt zo2p=^sYJ__qu*+aCu8~vH!vrj;ws!JYxNm8`Hn;Pr3`0wt3FwScVmHwrTWBSPHiF% z)YUH4oBQB+Tstv0dJfS{H)?(nneUfny7p)iFKdVu;u^$CvXtk>apvy3TYQ^qD%@|L zNw7fA%;5cqX~1cjIJjla!a2N*@lbR~488GvtM8^fhOGNh=KHLR@ptJOOV{91l79P> zX4ZLXCI-QyGCP|$XV^mZ3_tz|orftp$oqN{mAD8=SYeG+D7hX?56r%^*G5Y2oUYID zxY4Mpb6a=#$z(pF>cFIpvnYVLgPS^^SB;$-u%c8Z2;O2?k%~7J+Hct(SHuaA(MQ&| zOSH&4?{~h*wE^A*%I$F^US5avyOA0OT^~O5;lTP;^{X0aS7Ft7mNF#;5V9%)8_=X% zN@Hi-XK4Z}p#S1}Pbc8({a0~|Lkt$omI-Vdtj&tqZfoCoG#C1Z3*+LVj!{MwkIV*Zw}$B;fixCVmwJOi zn$8)A438^TVP7exWmM&R_IVfe8)H z@vQ4ngZ8;e2EYvN2rO4R%fsNba`jhsvX;tBU*S{LJ;!G=GzK1hR`MrmCsXPM-{*Ty zA&2=GvA!X>0gyZs>K{OJV_O_zg=yZePP=dt)kStO3}$!`NNFuWFxLbTo!yPu+(huX zujbMv+JRd>jlGEYgo}iIRHLhXzVzZ6CeMm;gNY;EKF1NVp96vz2^ZpMmOlckC+g~DEJDLo#xz&-{E)LazZbruUxMy zu|X%&4*h-bjMYz$ z3{-PKKo!`d)mAfd!73j>WYEmFq{R@I7zwaZqbt<}*#1}aA=}-u-(d%iZk|1pDPEPik`j`xb9@*cQ)-l8nJW>jLK+UgV$4J=|1WK`U$;&yz=PsN=VugHZ`XG z;lwrNkevoI-qo$rVi~Hy4;CStXGEk3OU^fvUY_Ljg~@yJ8NpchD|CMog<2j+4K_h!_;yHehDI#dcre%8K|4h4fpwQ+xT&j%_)RN%SXrkNC5+%XM#}rfludW#diG%YnCAw@|2=hX zfo?3SFCyzO8C4@MTTHbWBr9{klt7<}?)=<13JKct)REjtRTTEADkE(>>{CPf8OZ2> z@W~)tgq&0Ie?MqxvJq@-kS5$`mvTcM>jndO9L}x^en`k_RSsDtjA-iYF(Xd2@ACEV z%#%ga=PT%+d0}j$;n2j(fy+MENgBUqBC;KaEOftSqb$;F(y>~)&bWT8a=GURESX{K zW2RN1h4iTrDMP)V(LRc)P7_T{4UD?geQ8ujc12wD5M}r+_lnMglNbMtBK|Xa4xexh z$9_g(`Y95>!~kboauNN1Hql-S)Lk6?2Hl20O`+kNGiamSmrBN^U|~i<#)hCEmBM42 zjH=^=WPubGiqR#B=J@gdT(cUf9IE_Xj4HubGXV42=)?u|%?Rn%F9`R$RLu7fvSQSo z&W`Nz5L|geP>{|1EP)gtFytw)8m3kyT2@$kEm`VpF>-L@{@|9z<>kwfc`V&@NqK2` zJNW;SE>Jk{a4UqBHPe2=hAL4CoS&hrww5ohq|Y#OiEJB1cX@6B=lXFVWXkX&#F5iz zwitq^Wac?7(^u^J%b#D~ru|-a61?Selpj(AHLUiSUNHl>0JXK=~w*}=jqeG0xT~YWTs9wbF2t+!W+l6 z=QbChgiR6lred&=4|)Dzn z%+Sq`u%xdN2&v;me6e_9bph`4+GdmX7>B2#msQ=?W$gYmj^o<0=N7u5qCv1Yyt!b2 z=@l}%zr!{up6;SZiq?zEx<%_g>V{5a#=@vU^jqM&yy&EXrY|gMws?tV?9yEFHwkx{ zq1(6Dpt~l1NF9B{FweVSN*4-auRI9D7` z!o=(wag?aSLyVa95Sd5qGknwBwZ&C`x;Qh5QQfj z&=0V=GeZdqR!z8jC3b~P#M|8+nCH$Nc}&rGa`VNQ*FZS7lUYV4is5AvdWWv}SnCl< zp!&`vI?+9%m*H6J-=y|pcIJeSP(GaIruHnyhF%FDpxYh@O+ok)bR3I0RynQoU$RrS z?_P2()^wL9+7;4pbSKr4C=M=s(pY z$-c+}CwzKbxjjRAQvJ6ATPsimv}=B(ZJKs3$fyzxtj*Me2wzQ7PiqF5)mF}%4*%_} zuVTbWI*(U!`8NJnY^IbriG!9sS+a%Ix|d>5Gkx5~0l>|AV5kT!KPCLt#nS?Hn58lt zS|zr+Zx`6-43|-5Qd7Bd*;ixT3NA4@??%?8BpfAhuUI)%7{9%m%2?757l9>zTx><3 zDu{RgUWq|-Gk648CU45@3hTKj`<2mzW$Qn2Ex*klnm5%qnFYXPMy@S}u%wpGXbZZa zW>$&a%^rh;`28C4{opJB7^WlA5Jgl7jVSGdM-+^ma}|y3qS;{qNcD@#^ed+ss3;rx z{(ylqck!bS#vrXXv0~0HR%%lFyfB79kA$Qk5?+tU}(~%r- zC6*DK!#$|k#cU;kvOXkK8l=oBX zzUhtI=8>Ni%_3zA1?ZbA9OpEfg_E<0H1l?L$I9HW3de}*6UM9L@>XQR{-(PfB!E2i;+pTOpeOsClA~#Nwgp^XGrR=9DKn9PYuH$J{`G~U z1w265Cf99xBiLRihpwsmx4dv@)4Cgjilg#Zq>_GwaOlL*Z`H!i4atfmEqia+`x}wBSKb=0KW>b_4Hr>-7e!mB*`ax1LG&ld%pQ(ow6#l`BqCdu`xN( zb13p&yqPLuv4J-+?LT4nGxeP^ri#C!>h|@8Cn4)DrL2>}+6*v;U%OQt9)@l7eb;

NF65;fvbXxJm+&d?Ls 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 PR^f3N;#<#Vv!CwD)iMYk+T7^29ElbJe~)ie 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 z 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 literal 0 HcmV?d00001 From 914de3050817d5d36216f3105419363587bba115 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:30:49 -0300 Subject: [PATCH 142/150] Add LICENSE --- LICENSE | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From 30fddbfa11ba3655d25bcce347daaf6ee5364ee5 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:10:17 +0700 Subject: [PATCH 143/150] Add initLimitsAndControllerData --- deploy/PASInit.sol | 35 +++++++++++++++ test/Integration.t.sol | 96 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index e41effe..254ae29 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -67,6 +67,18 @@ interface PASMomLike { function setAuthority(address) external; } +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. @@ -158,6 +170,29 @@ library PASInit { } } + 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( DssInstance memory dss, PASInstance memory pasInstance, diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 25a5bb5..145d071 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -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 } from "deploy/PASInit.sol"; import { BeamState } from "src/BeamState.sol"; import { Configurator, RateLimitsLike } from "src/Configurator.sol"; import { Timelock } from "src/timelock/Timelock.sol"; @@ -265,6 +265,51 @@ contract IntegrationTest is DssTest { 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 // ============================================================================ @@ -872,6 +917,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) // ======================================== @@ -932,6 +1003,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) @@ -973,6 +1051,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 // ======================================== @@ -991,7 +1083,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); From 905cc215032e40f5a928d2392b6a542c0ee4408d Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:48:06 +0700 Subject: [PATCH 144/150] Add setCbeams calls to initExtras (#11) --- deploy/PASInit.sol | 23 ++++++++++++++++++----- test/Integration.t.sol | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 254ae29..0d39c70 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -67,6 +67,12 @@ interface PASMomLike { function setAuthority(address) external; } +struct InitCBeamConfig { + address cBeam; + address[] rateLimits; + address[] controllers; +} + struct InitRateLimitConfig { bytes32 key; address rateLimits; @@ -149,9 +155,9 @@ 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"); @@ -159,15 +165,22 @@ library PASInit { 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( diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 145d071..5d7dc78 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -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, InitRateLimitConfig, InitControllerActionConfig } 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"; @@ -234,18 +234,29 @@ 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"); @@ -253,11 +264,25 @@ contract IntegrationTest is DssTest { // 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 { From 0a7e2b60b54ca050ee6f00b55b59a3ed65668ad0 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:17:39 +0700 Subject: [PATCH 145/150] Add audit reports --- ... 20260506-ChainSecurity_Sky_PAS_audit.pdf} | Bin 1188261 -> 1202676 bytes audit/20260521-cantina-report-sky-pas.pdf | Bin 0 -> 441546 bytes 2 files changed, 0 insertions(+), 0 deletions(-) rename audit/{20260218-ChainSecurity_Sky_PAS_audit.pdf => 20260506-ChainSecurity_Sky_PAS_audit.pdf} (96%) create mode 100644 audit/20260521-cantina-report-sky-pas.pdf diff --git a/audit/20260218-ChainSecurity_Sky_PAS_audit.pdf b/audit/20260506-ChainSecurity_Sky_PAS_audit.pdf similarity index 96% rename from audit/20260218-ChainSecurity_Sky_PAS_audit.pdf rename to audit/20260506-ChainSecurity_Sky_PAS_audit.pdf index df1105728096544d469d74f41944f8dd2bafae9e..304ef6bd11e67d579ae4d6c4b7b81f13eeed8ec3 100644 GIT binary patch delta 26910 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 delta 15619 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 diff --git a/audit/20260521-cantina-report-sky-pas.pdf b/audit/20260521-cantina-report-sky-pas.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f2b39ba60c1fe367604357dab396cb5f8f35ca9a GIT binary patch literal 441546 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%0AW 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

uvGs&3={(!pcTJ5iX@ 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
L3Zrjl0I_QN2rZws1XO3h^{6WdlxjdD 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Ê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 Date: Fri, 10 Jul 2026 20:35:52 +0700 Subject: [PATCH 146/150] Remove Wrappers (#12) --- README.md | 8 - deploy/L2PASSpell.sol | 5 +- deploy/PASDeploy.sol | 28 - deploy/PASInit.sol | 26 - src/timelock/TimelockWrapperForeign.sol | 447 ---------- src/timelock/TimelockWrapperMainnet.sol | 511 ----------- test/Integration.t.sol | 181 ++-- test/timelock/TimelockWrapperForeign.t.sol | 824 ------------------ test/timelock/TimelockWrapperMainnet.t.sol | 936 --------------------- 9 files changed, 97 insertions(+), 2869 deletions(-) delete mode 100644 src/timelock/TimelockWrapperForeign.sol delete mode 100644 src/timelock/TimelockWrapperMainnet.sol delete mode 100644 test/timelock/TimelockWrapperForeign.t.sol delete mode 100644 test/timelock/TimelockWrapperMainnet.t.sol diff --git a/README.md b/README.md index e891fc8..060633f 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,6 @@ The operational interface used by cBeams to modify rate limits and execute contr Extended OpenZeppelin TimelockController with pausing support, permissionless execution, and operation tracking for keeper integration. Disables self-calls to prevent proposals from modifying admin settings. -### TimelockWrapperMainnet - -Convenience contract for proposers to schedule batched BeamState configuration changes through the timelock. Provides type-safe wrappers for common operations like adding rate limits, controllers, cBeams, and their initial configurations. Authorized users (`buds`) can schedule proposals that execute after the timelock delay. - -### TimelockWrapperForeign - -Variant of the timelock wrapper for foreign (non-mainnet) chain deployments. - ### PASMom Emergency governance contract that allows authorized parties to trigger circuit breakers. Can call `stop()` on BeamState to halt Configurator operations and `pause()` on Timelock to block scheduling and execution. Callable by the owner or via the Chief's hat through the authority. diff --git a/deploy/L2PASSpell.sol b/deploy/L2PASSpell.sol index 8705ee3..3d1cdbd 100644 --- a/deploy/L2PASSpell.sol +++ b/deploy/L2PASSpell.sol @@ -27,13 +27,11 @@ contract L2PASSpell { address public immutable beamState; address public immutable configurator; address public immutable timelock; - address public immutable wrapper; - constructor(address beamState_, address configurator_, address timelock_, address wrapper_) { + constructor(address beamState_, address configurator_, address timelock_) { beamState = beamState_; configurator = configurator_; timelock = timelock_; - wrapper = wrapper_; } function init( @@ -49,6 +47,5 @@ contract L2PASSpell { }); PASInit.init(pas, minDelay, coreCouncil, cancellers, pausers); - PASInit.initTimelockWrapper(pas, wrapper, coreCouncil); } } diff --git a/deploy/PASDeploy.sol b/deploy/PASDeploy.sol index 6a30b36..d366e82 100644 --- a/deploy/PASDeploy.sol +++ b/deploy/PASDeploy.sol @@ -21,8 +21,6 @@ import { PASInstance } from "./PASInstance.sol"; import { BeamState } from "src/BeamState.sol"; import { Configurator } from "src/Configurator.sol"; import { Timelock } from "src/timelock/Timelock.sol"; -import { TimelockWrapperMainnet } from "src/timelock/TimelockWrapperMainnet.sol"; -import { TimelockWrapperForeign } from "src/timelock/TimelockWrapperForeign.sol"; import { PASMom } from "src/PASMom.sol"; library PASDeploy { @@ -52,30 +50,4 @@ library PASDeploy { )); PASMom(mom).setOwner(owner); } - - function deployTimelockWrapperMainnet( - address deployer, - address owner, - address timelock, - address beamState - ) internal returns (address timelockWrapperMainnet) { - timelockWrapperMainnet = address(new TimelockWrapperMainnet( - timelock, - beamState - )); - ScriptTools.switchOwner(timelockWrapperMainnet, deployer, owner); - } - - function deployTimelockWrapperForeign( - address deployer, - address owner, - address timelock, - address beamState - ) internal returns (address timelockWrapperForeign) { - timelockWrapperForeign = address(new TimelockWrapperForeign( - timelock, - beamState - )); - ScriptTools.switchOwner(timelockWrapperForeign, deployer, owner); - } } diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 0d39c70..5892371 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -55,12 +55,6 @@ interface TimelockLike { function grantRole(bytes32, address) external; } -interface TimelockWrapperLike { - function timelock() external view returns (address); - function beamState() external view returns (address); - function kiss(address) external; -} - interface PASMomLike { function beamState() external view returns (address); function timelock() external view returns (address); @@ -246,24 +240,4 @@ library PASInit { dss.chainlog.setAddress(key, address(mom)); } - - function initTimelockWrapper( - PASInstance memory pasInstance, - address timelockWrapper_, - address coreCouncil - ) internal { - TimelockLike timelock = TimelockLike(pasInstance.timelock); - TimelockWrapperLike timelockWrapper = TimelockWrapperLike(timelockWrapper_); - - // --- Sanity checks --- - - require(timelockWrapper.timelock() == pasInstance.timelock, "PASInit/wrapper-timelock-mismatch"); - require(timelockWrapper.beamState() == pasInstance.beamState, "PASInit/wrapper-beamState-mismatch"); - - // --- Set permissions --- - - // Grant the coreCouncil as the proposer through the wrapper - timelock.grantRole(timelock.PROPOSER_ROLE(), address(timelockWrapper)); - timelockWrapper.kiss(coreCouncil); - } } diff --git a/src/timelock/TimelockWrapperForeign.sol b/src/timelock/TimelockWrapperForeign.sol deleted file mode 100644 index d5b96b9..0000000 --- a/src/timelock/TimelockWrapperForeign.sol +++ /dev/null @@ -1,447 +0,0 @@ -// SPDX-FileCopyrightText: © 2026 Dai Foundation -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.24; - -interface TimelockLike { - function scheduleBatch( - address[] calldata targets, - uint256[] calldata values, - bytes[] calldata payloads, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external; - - function hashOperationBatch( - address[] calldata targets, - uint256[] calldata values, - bytes[] calldata payloads, - bytes32 predecessor, - bytes32 salt - ) external view returns (bytes32); -} - -interface BeamStateLike { - function start() external; - function setHop(address rateLimits_, uint256 value) external; - function setMaxChange(address rateLimits_, uint256 value) external; - function addRateLimits(address rateLimits_) external; - function addController(address controller) external; - function addCBeam(address cBeam) external; - function addInitRateLimits(bytes32 key, address rateLimits_, uint256 maxAmount, uint256 slope) external; - function addInitControllerActions(bytes calldata data, address controller) external returns (bytes32 key); -} - -interface ControllerLike { - // Shared functions (Spark v1.8.0 & Grove v1.8.0) - function grantRole(bytes32 role, address account) external; - function revokeRole(bytes32 role, address account) external; - function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; - function setLayerZeroRecipient(uint32 destinationEndpointId, bytes32 layerZeroRecipient) external; - function setMaxSlippage(address pool, uint256 maxSlippage) external; - function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; - // Grove-only functions (v1.8.0) - function setUniswapV3PoolMaxTickDelta(address pool, uint24 maxTickDelta) external; - function setUniswapV3AddLiquidityLowerTickBound(address pool, int24 lowerTickBound) external; - function setUniswapV3AddLiquidityUpperTickBound(address pool, int24 upperTickBound) external; - function setUniswapV3TwapSecondsAgo(address pool, uint32 twapSecondsAgo) external; - function setCentrifugeRecipient(uint16 centrifugeId, bytes32 recipient) external; - function setMerklDistributor(address merklDistributor) external; -} - -struct RateLimitConfig { - bytes32 key; - address rateLimits; - uint256 maxAmount; - uint256 slope; -} - -// Spark Foreign controller aligned to v1.8.0: https://github.com/sparkdotfi/spark-alm-controller/blob/7be959378fe48117f7a06796f94e240345428982/src/ForeignController.sol -// Grove Foreign controller aligned to v1.8.0: https://github.com/grove-labs/grove-alm-controller/blob/2c6e3d4297d5f244894d05f3dbbe47bcada34712/src/ForeignController.sol - -// Notes: -// - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). -// - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. -// - The actual downstream changes only take effect when cBEAMs use the BeamState configurations, so atomicity in configurations can not be assumed (which is a known issue). -// - As part of a controller onboarding it might need to be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. -contract TimelockWrapperForeign { - // --- Auth --- - mapping(address => uint256) public wards; - mapping(address => uint256) public buds; - - modifier auth() { - require(wards[msg.sender] == 1, "TimelockWrapperForeign/not-authorized"); - _; - } - - modifier toll() { - require(buds[msg.sender] == 1, "TimelockWrapperForeign/not-whitelisted"); - _; - } - - function rely(address usr) external auth { - wards[usr] = 1; - emit Rely(usr); - } - - function deny(address usr) external auth { - wards[usr] = 0; - emit Deny(usr); - } - - function kiss(address usr) external auth { - buds[usr] = 1; - emit Kiss(usr); - } - - function diss(address usr) external auth { - buds[usr] = 0; - emit Diss(usr); - } - - // --- Events --- - event Rely(address indexed usr); - event Deny(address indexed usr); - event Kiss(address indexed usr); - event Diss(address indexed usr); - event ProposalSubmitted(bytes32 indexed operationId, string functionName); - - TimelockLike public immutable timelock; - BeamStateLike public immutable beamState; - - constructor(address timelock_, address beamState_) { - timelock = TimelockLike(payable(timelock_)); - beamState = BeamStateLike(beamState_); - - wards[msg.sender] = 1; - emit Rely(msg.sender); - } - - function _submitProposal(bytes memory payload, bytes32 predecessor, bytes32 salt, uint256 delay) internal returns (bytes32 operationId) { - address[] memory targets = new address[](1); - targets[0] = address(beamState); - - uint256[] memory values = new uint256[](1); - values[0] = 0; - - bytes[] memory payloads = new bytes[](1); - payloads[0] = payload; - - operationId = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); - timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); - } - - function start(bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.start, ()); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "start"); - } - - function setHop(address rateLimits, uint256 hop, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.setHop, (rateLimits, hop)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setHop"); - } - - function setMaxChange(address rateLimits, uint256 maxChange, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.setMaxChange, (rateLimits, maxChange)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMaxChange"); - } - - function addRateLimits(address rateLimits, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.addRateLimits, (rateLimits)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "addRateLimits"); - } - - function addController(address controller, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.addController, (controller)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "addController"); - } - - function addCBeam(address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.addCBeam, (cBeam)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "addCBeam"); - } - - function addInitRateLimits( - RateLimitConfig calldata config, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall( - BeamStateLike.addInitRateLimits, - (config.key, config.rateLimits, config.maxAmount, config.slope) - ); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "addInitRateLimits"); - } - - function batchAddInitRateLimits( - RateLimitConfig[] calldata configs, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - uint256 len = configs.length; - - address[] memory targets = new address[](len); - uint256[] memory values = new uint256[](len); - bytes[] memory payloads = new bytes[](len); - - for (uint256 i = 0; i < len; ++i) { - targets[i] = address(beamState); - payloads[i] = abi.encodeCall( - BeamStateLike.addInitRateLimits, - (configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope) - ); - } - - operationId = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); - timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); - - emit ProposalSubmitted(operationId, "batchAddInitRateLimits"); - } - - // --- Controller Actions --- - - // Shared Spark & Grove functions - - // Role bytes32 can be computed off-chain and passed as parameter, e.g keccak256("RELAYER"), keccak256("FREEZER") - function grantRole( - bytes32 role, - address account, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.grantRole, - (role, account) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "grantRole"); - } - - function revokeRole( - bytes32 role, - address account, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.revokeRole, - (role, account) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "revokeRole"); - } - - function setMintRecipient( - uint32 destinationDomain, - bytes32 mintRecipient, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setMintRecipient, - (destinationDomain, mintRecipient) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMintRecipient"); - } - - function setLayerZeroRecipient( - uint32 destinationEndpointId, - bytes32 layerZeroRecipient, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setLayerZeroRecipient, - (destinationEndpointId, layerZeroRecipient) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setLayerZeroRecipient"); - } - - function setMaxSlippage( - address pool, - uint256 maxSlippage, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setMaxSlippage, - (pool, maxSlippage) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMaxSlippage"); - } - - function setMaxExchangeRate( - address token, - uint256 shares, - uint256 maxExpectedAssets, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setMaxExchangeRate, - (token, shares, maxExpectedAssets) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMaxExchangeRate"); - } - - // Grove-only functions - - function setUniswapV3PoolMaxTickDelta( - address pool, - uint24 maxTickDelta, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setUniswapV3PoolMaxTickDelta, - (pool, maxTickDelta) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3PoolMaxTickDelta"); - } - - function setUniswapV3AddLiquidityLowerTickBound( - address pool, - int24 lowerTickBound, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setUniswapV3AddLiquidityLowerTickBound, - (pool, lowerTickBound) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityLowerTickBound"); - } - - function setUniswapV3AddLiquidityUpperTickBound( - address pool, - int24 upperTickBound, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setUniswapV3AddLiquidityUpperTickBound, - (pool, upperTickBound) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityUpperTickBound"); - } - - function setUniswapV3TwapSecondsAgo( - address pool, - uint32 twapSecondsAgo, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setUniswapV3TwapSecondsAgo, - (pool, twapSecondsAgo) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3TwapSecondsAgo"); - } - - function setCentrifugeRecipient( - uint16 centrifugeId, - bytes32 recipient, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setCentrifugeRecipient, - (centrifugeId, recipient) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setCentrifugeRecipient"); - } - - function setMerklDistributor( - address merklDistributor, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setMerklDistributor, - (merklDistributor) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMerklDistributor"); - } -} diff --git a/src/timelock/TimelockWrapperMainnet.sol b/src/timelock/TimelockWrapperMainnet.sol deleted file mode 100644 index 460fcfa..0000000 --- a/src/timelock/TimelockWrapperMainnet.sol +++ /dev/null @@ -1,511 +0,0 @@ -// SPDX-FileCopyrightText: © 2026 Dai Foundation -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.24; - -interface TimelockLike { - function scheduleBatch( - address[] calldata targets, - uint256[] calldata values, - bytes[] calldata payloads, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external; - - function hashOperationBatch( - address[] calldata targets, - uint256[] calldata values, - bytes[] calldata payloads, - bytes32 predecessor, - bytes32 salt - ) external view returns (bytes32); -} - -interface BeamStateLike { - function start() external; - function setHop(address rateLimits_, uint256 value) external; - function setMaxChange(address rateLimits_, uint256 value) external; - function addRateLimits(address rateLimits_) external; - function addController(address controller) external; - function addCBeam(address cBeam) external; - function addInitRateLimits(bytes32 key, address rateLimits_, uint256 maxAmount, uint256 slope) external; - function addInitControllerActions(bytes calldata data, address controller) external returns (bytes32 key); -} - -interface ControllerLike { - // Shared functions (Spark v1.9.0 & Grove v1.8.0) - function grantRole(bytes32 role, address account) external; - function revokeRole(bytes32 role, address account) external; - function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external; - function setLayerZeroRecipient(uint32 destinationEndpointId, bytes32 layerZeroRecipient) external; - function setMaxSlippage(address pool, uint256 maxSlippage) external; - function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets) external; - // Spark-only functions (v1.9.0) - function setOTCBuffer(address exchange, address otcBuffer) external; - function setOTCRechargeRate(address exchange, uint256 rechargeRate18) external; - function setOTCWhitelistedAsset(address exchange, address asset, bool isWhitelisted) external; - function setUniswapV4TickLimits(bytes32 poolId, int24 tickLowerMin, int24 tickUpperMax, uint24 maxTickSpacing) external; - // Grove-only functions (v1.8.0) - function setUniswapV3PoolMaxTickDelta(address pool, uint24 maxTickDelta) external; - function setUniswapV3AddLiquidityLowerTickBound(address pool, int24 lowerTickBound) external; - function setUniswapV3AddLiquidityUpperTickBound(address pool, int24 upperTickBound) external; - function setUniswapV3TwapSecondsAgo(address pool, uint32 twapSecondsAgo) external; - function setCentrifugeRecipient(uint16 centrifugeId, bytes32 recipient) external; -} - -struct RateLimitConfig { - bytes32 key; - address rateLimits; - uint256 maxAmount; - uint256 slope; -} - -// Spark Mainnet controller aligned to v1.9.0: https://github.com/sparkdotfi/spark-alm-controller/blob/3dbc7cb01739e91dad61a75cda8d7c84b4474e0b/src/MainnetController.sol -// Grove Mainnet controller aligned to v1.8.0: https://github.com/grove-labs/grove-alm-controller/blob/2c6e3d4297d5f244894d05f3dbbe47bcada34712/src/MainnetController.sol - -// Notes: -// - This wrapper is assumed as a helper only, and can be bypassed by submitting payloads directly to the Timelock (for an authorised proposer). -// - The wrapper is assumed to be frequently replaced/improved, depending on downstream contracts changes or other needs. -// - The actual downstream changes only take effect when cBEAMs use the BeamState configurations, so atomicity in configurations can not be assumed (which is a known issue). -// - As part of a controller onboarding it might need to be `kiss`ed on the PSM. That is assumed to be orchestrated without the wrapper. -contract TimelockWrapperMainnet { - // --- Auth --- - mapping(address => uint256) public wards; - mapping(address => uint256) public buds; - - modifier auth() { - require(wards[msg.sender] == 1, "TimelockWrapperMainnet/not-authorized"); - _; - } - - modifier toll() { - require(buds[msg.sender] == 1, "TimelockWrapperMainnet/not-whitelisted"); - _; - } - - function rely(address usr) external auth { - wards[usr] = 1; - emit Rely(usr); - } - - function deny(address usr) external auth { - wards[usr] = 0; - emit Deny(usr); - } - - function kiss(address usr) external auth { - buds[usr] = 1; - emit Kiss(usr); - } - - function diss(address usr) external auth { - buds[usr] = 0; - emit Diss(usr); - } - - // --- Events --- - event Rely(address indexed usr); - event Deny(address indexed usr); - event Kiss(address indexed usr); - event Diss(address indexed usr); - event ProposalSubmitted(bytes32 indexed operationId, string functionName); - - TimelockLike public immutable timelock; - BeamStateLike public immutable beamState; - - constructor(address timelock_, address beamState_) { - timelock = TimelockLike(payable(timelock_)); - beamState = BeamStateLike(beamState_); - - wards[msg.sender] = 1; - emit Rely(msg.sender); - } - - function _submitProposal(bytes memory payload, bytes32 predecessor, bytes32 salt, uint256 delay) internal returns (bytes32 operationId) { - address[] memory targets = new address[](1); - targets[0] = address(beamState); - - uint256[] memory values = new uint256[](1); - values[0] = 0; - - bytes[] memory payloads = new bytes[](1); - payloads[0] = payload; - - operationId = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); - timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); - } - - function start(bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.start, ()); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "start"); - } - - function setHop(address rateLimits, uint256 hop, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.setHop, (rateLimits, hop)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setHop"); - } - - function setMaxChange(address rateLimits, uint256 maxChange, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.setMaxChange, (rateLimits, maxChange)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMaxChange"); - } - - function addRateLimits(address rateLimits, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.addRateLimits, (rateLimits)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "addRateLimits"); - } - - function addController(address controller, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.addController, (controller)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "addController"); - } - - function addCBeam(address cBeam, bytes32 predecessor, bytes32 salt, uint256 delay) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall(BeamStateLike.addCBeam, (cBeam)); - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "addCBeam"); - } - - function addInitRateLimits( - RateLimitConfig calldata config, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory payload = abi.encodeCall( - BeamStateLike.addInitRateLimits, - (config.key, config.rateLimits, config.maxAmount, config.slope) - ); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "addInitRateLimits"); - } - - function batchAddInitRateLimits( - RateLimitConfig[] calldata configs, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - uint256 len = configs.length; - - address[] memory targets = new address[](len); - uint256[] memory values = new uint256[](len); - bytes[] memory payloads = new bytes[](len); - - for (uint256 i = 0; i < len; ++i) { - targets[i] = address(beamState); - payloads[i] = abi.encodeCall( - BeamStateLike.addInitRateLimits, - (configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope) - ); - } - - operationId = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); - timelock.scheduleBatch(targets, values, payloads, predecessor, salt, delay); - - emit ProposalSubmitted(operationId, "batchAddInitRateLimits"); - } - - // --- Controller Actions --- - - // Shared Spark & Grove functions - - // Role bytes32 can be computed off-chain and passed as parameter, e.g keccak256("RELAYER"), keccak256("FREEZER") - function grantRole( - bytes32 role, - address account, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.grantRole, - (role, account) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "grantRole"); - } - - function revokeRole( - bytes32 role, - address account, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.revokeRole, - (role, account) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "revokeRole"); - } - - function setMintRecipient( - uint32 destinationDomain, - bytes32 mintRecipient, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setMintRecipient, - (destinationDomain, mintRecipient) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMintRecipient"); - } - - function setLayerZeroRecipient( - uint32 destinationEndpointId, - bytes32 layerZeroRecipient, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setLayerZeroRecipient, - (destinationEndpointId, layerZeroRecipient) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setLayerZeroRecipient"); - } - - function setMaxSlippage( - address pool, - uint256 maxSlippage, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setMaxSlippage, - (pool, maxSlippage) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMaxSlippage"); - } - - function setMaxExchangeRate( - address token, - uint256 shares, - uint256 maxExpectedAssets, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setMaxExchangeRate, - (token, shares, maxExpectedAssets) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setMaxExchangeRate"); - } - - // Spark-only functions - - function setOTCBuffer( - address exchange, - address otcBuffer, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setOTCBuffer, - (exchange, otcBuffer) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setOTCBuffer"); - } - - function setOTCRechargeRate( - address exchange, - uint256 rechargeRate18, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setOTCRechargeRate, - (exchange, rechargeRate18) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setOTCRechargeRate"); - } - - function setOTCWhitelistedAsset( - address exchange, - address asset, - bool isWhitelisted, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setOTCWhitelistedAsset, - (exchange, asset, isWhitelisted) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setOTCWhitelistedAsset"); - } - - function setUniswapV4TickLimits( - bytes32 poolId, - int24 tickLowerMin, - int24 tickUpperMax, - uint24 maxTickSpacing, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setUniswapV4TickLimits, - (poolId, tickLowerMin, tickUpperMax, maxTickSpacing) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV4TickLimits"); - } - - // Grove-only functions - - function setUniswapV3PoolMaxTickDelta( - address pool, - uint24 maxTickDelta, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setUniswapV3PoolMaxTickDelta, - (pool, maxTickDelta) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3PoolMaxTickDelta"); - } - - function setUniswapV3AddLiquidityLowerTickBound( - address pool, - int24 lowerTickBound, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setUniswapV3AddLiquidityLowerTickBound, - (pool, lowerTickBound) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityLowerTickBound"); - } - - function setUniswapV3AddLiquidityUpperTickBound( - address pool, - int24 upperTickBound, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setUniswapV3AddLiquidityUpperTickBound, - (pool, upperTickBound) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3AddLiquidityUpperTickBound"); - } - - function setUniswapV3TwapSecondsAgo( - address pool, - uint32 twapSecondsAgo, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setUniswapV3TwapSecondsAgo, - (pool, twapSecondsAgo) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setUniswapV3TwapSecondsAgo"); - } - - function setCentrifugeRecipient( - uint16 centrifugeId, - bytes32 recipient, - address controller, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external toll returns (bytes32 operationId) { - bytes memory controllerData = abi.encodeCall( - ControllerLike.setCentrifugeRecipient, - (centrifugeId, recipient) - ); - bytes memory payload = abi.encodeCall(BeamStateLike.addInitControllerActions, (controllerData, controller)); - - operationId = _submitProposal(payload, predecessor, salt, delay); - emit ProposalSubmitted(operationId, "setCentrifugeRecipient"); - } -} diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 5d7dc78..83c5256 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -24,7 +24,6 @@ import { PASInit, InitRateLimitConfig, InitControllerActionConfig, InitCBeamConf import { BeamState } from "src/BeamState.sol"; import { Configurator, RateLimitsLike } from "src/Configurator.sol"; import { Timelock } from "src/timelock/Timelock.sol"; -import { TimelockWrapperMainnet, RateLimitConfig } from "src/timelock/TimelockWrapperMainnet.sol"; import { PASMom } from "src/PASMom.sol"; interface ChiefLike { @@ -66,14 +65,13 @@ contract IntegrationTest is DssTest { // Fetched from controller address SPARK_RATE_LIMITS; - DssInstance dss; - PASInstance pas; - BeamState beamState; - Configurator configurator; - Timelock timelock; - TimelockWrapperMainnet wrapper; - PASMom mom; - ChiefLike chief; + DssInstance dss; + PASInstance pas; + BeamState beamState; + Configurator configurator; + Timelock timelock; + PASMom mom; + ChiefLike chief; address pauseProxy; // Timelock admin address coreCouncil; // Has IMMEDIATE role on BeamState, proposer/canceller on Timelock @@ -107,9 +105,8 @@ contract IntegrationTest is DssTest { beamState = BeamState(pas.beamState); configurator = Configurator(pas.configurator); timelock = Timelock(payable(pas.timelock)); - // Deploy Mom and TimelockWrapper separately - mom = PASMom(PASDeploy.deployMom(pauseProxy, pas.beamState, pas.timelock)); - wrapper = TimelockWrapperMainnet(PASDeploy.deployTimelockWrapperMainnet(address(this), pauseProxy, pas.timelock, pas.beamState)); + // Deploy Mom separately + mom = PASMom(PASDeploy.deployMom(pauseProxy, pas.beamState, pas.timelock)); chief = ChiefLike(dss.chainlog.getAddress("MCD_ADM")); @@ -124,7 +121,6 @@ contract IntegrationTest is DssTest { PASInit.init(pas, MIN_DELAY, coreCouncil, cancellers, pausers); PASInit.addCoreToChainlog(dss, pas, "PAS_STATE", "PAS_CONFIGURATOR", "PAS_TIMELOCK"); PASInit.initMom(dss, pas, address(mom), "PAS_MOM"); - PASInit.initTimelockWrapper(pas, address(wrapper), coreCouncil); vm.stopPrank(); } @@ -137,7 +133,6 @@ contract IntegrationTest is DssTest { assertTrue(pas.configurator != address(0), "configurator should be deployed"); assertTrue(pas.timelock != address(0), "timelock should be deployed"); assertTrue(address(mom) != address(0), "mom should be deployed"); - assertTrue(address(wrapper) != address(0), "wrapper should be deployed"); } function testConfiguratorLinkedToBeamState() public view { @@ -150,11 +145,6 @@ contract IntegrationTest is DssTest { assertEq(mom.owner(), pauseProxy, "mom should be owned by pauseProxy"); } - function testTimelockWrapperLinkedCorrectly() public view { - assertEq(address(wrapper.timelock()), address(timelock), "wrapper should reference timelock"); - assertEq(address(wrapper.beamState()), address(beamState), "wrapper should reference beamState"); - } - function testChainlogEntriesAfterInit() public view { assertEq(dss.chainlog.getAddress("PAS_STATE"), address(beamState), "PAS_STATE should be set in chainlog"); assertEq(dss.chainlog.getAddress("PAS_CONFIGURATOR"), address(configurator), "PAS_CONFIGURATOR should be set in chainlog"); @@ -193,14 +183,9 @@ contract IntegrationTest is DssTest { assertTrue(beamState.hasUserRole(coreCouncil, uint8(PASInit.Role.IMMEDIATE)), "coreCouncil should have IMMEDIATE role"); } - function testTimelockWrapperBudsAfterInit() public view { - assertEq(wrapper.buds(coreCouncil), 1, "coreCouncil should be whitelisted on wrapper"); - } - function testTimelockRolesAfterInit() public view { assertTrue(timelock.hasRole(timelock.DEFAULT_ADMIN_ROLE(), pauseProxy), "pauseProxy should be admin"); assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should be proposer"); - assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), address(wrapper)), "wrapper should be proposer"); assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), coreCouncil), "coreCouncil should be canceller"); assertTrue(timelock.hasRole(timelock.CANCELLER_ROLE(), canceller), "canceller should be canceller"); assertTrue(timelock.hasRole(timelock.PAUSER_ROLE(), pauser), "pauser should be pauser"); @@ -573,36 +558,17 @@ contract IntegrationTest is DssTest { assertTrue(beamState.isControllerActionEnabled(key, SPARK_CONTROLLER), "action should be enabled via timelock"); } - // ============================================================================ - // TimelockWrapperMainnet Tests - // ============================================================================ - - function testWrapperCanSchedule() public { - // First stop via coreCouncil - vm.prank(coreCouncil); - beamState.stop(); - assertTrue(beamState.stopped(), "should be stopped"); - - // Schedule start via wrapper - vm.prank(coreCouncil); - bytes32 operationId = wrapper.start(bytes32(0), SALT, MIN_DELAY); - - vm.warp(block.timestamp + MIN_DELAY); - - Timelock.Operation memory op = timelock.getOperation(operationId); - timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); - - assertFalse(beamState.stopped(), "should be started via wrapper"); - } - // ============================================================================ // Cancellation Tests // ============================================================================ function testCoreCouncilCanCancelOperation() public { // Schedule operation - vm.prank(coreCouncil); - bytes32 operationId = wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = _scheduleDirect( + abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), + bytes32(0), + SALT + ); assertTrue(timelock.isOperationPending(operationId), "operation should be pending"); @@ -615,8 +581,11 @@ contract IntegrationTest is DssTest { } function testCancellerCanCancelOperation() public { - vm.prank(coreCouncil); - bytes32 operationId = wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = _scheduleDirect( + abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), + bytes32(0), + SALT + ); vm.prank(canceller); timelock.cancel(operationId); @@ -639,15 +608,24 @@ contract IntegrationTest is DssTest { vm.prank(pauser); timelock.pause(); + address[] memory targets = new address[](1); + targets[0] = address(beamState); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam); + vm.prank(coreCouncil); vm.expectRevert(); - wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); } function testPausedTimelockBlocksExecution() public { // Schedule first - vm.prank(coreCouncil); - bytes32 operationId = wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = _scheduleDirect( + abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), + bytes32(0), + SALT + ); vm.warp(block.timestamp + MIN_DELAY); @@ -725,17 +703,26 @@ contract IntegrationTest is DssTest { // ============================================================================ function testFullOnboardingWorkflow() public { - // 1. CoreCouncil schedules adding a cBeam via wrapper - vm.prank(coreCouncil); - bytes32 addCBeamOp = wrapper.addCBeam(cBeam, bytes32(0), keccak256("step1"), MIN_DELAY); + // 1. CoreCouncil schedules adding a cBeam via timelock + bytes32 addCBeamOp = _scheduleDirect( + abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), + bytes32(0), + keccak256("step1") + ); // 2. Schedule addRateLimits with predecessor - vm.prank(coreCouncil); - bytes32 addRateLimitsOp = wrapper.addRateLimits(SPARK_RATE_LIMITS, addCBeamOp, keccak256("step2"), MIN_DELAY); + bytes32 addRateLimitsOp = _scheduleDirect( + abi.encodeWithSelector(BeamState.addRateLimits.selector, SPARK_RATE_LIMITS), + addCBeamOp, + keccak256("step2") + ); // 3. Schedule setHop with predecessor - vm.prank(coreCouncil); - bytes32 setHopOp = wrapper.setHop(SPARK_RATE_LIMITS, 4 hours, addRateLimitsOp, keccak256("step3"), MIN_DELAY); + bytes32 setHopOp = _scheduleDirect( + abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 4 hours), + addRateLimitsOp, + keccak256("step3") + ); // Wait for delay vm.warp(block.timestamp + MIN_DELAY); @@ -764,8 +751,11 @@ contract IntegrationTest is DssTest { assertTrue(beamState.stopped(), "system stopped"); // 7. Restart requires timelock - vm.prank(coreCouncil); - bytes32 startOp = wrapper.start(bytes32(0), keccak256("restart"), MIN_DELAY); + bytes32 startOp = _scheduleDirect( + abi.encodeWithSelector(BeamState.start.selector), + bytes32(0), + keccak256("restart") + ); vm.warp(block.timestamp + MIN_DELAY); @@ -890,16 +880,25 @@ contract IntegrationTest is DssTest { vm.prank(hat); mom.pause(); - // Scheduling via wrapper is blocked + // Scheduling via timelock is blocked + address[] memory targets = new address[](1); + targets[0] = address(beamState); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam); + vm.prank(coreCouncil); vm.expectRevert(); - wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + timelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); } function testMomPauseBlocksTimelockExecution() public { // Schedule an operation first - vm.prank(coreCouncil); - bytes32 operationId = wrapper.addCBeam(cBeam, bytes32(0), SALT, MIN_DELAY); + bytes32 operationId = _scheduleDirect( + abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), + bytes32(0), + SALT + ); vm.warp(block.timestamp + MIN_DELAY); @@ -974,33 +973,42 @@ contract IntegrationTest is DssTest { bytes32[] memory opIds = new bytes32[](7); { // 1a. Schedule addCBeam - vm.prank(coreCouncil); - opIds[0] = wrapper.addCBeam(cBeam, bytes32(0), keccak256("addCBeam"), MIN_DELAY); + opIds[0] = _scheduleDirect( + abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam), + bytes32(0), keccak256("addCBeam") + ); // 1b. Schedule addRateLimits for SPARK_RATE_LIMITS - vm.prank(coreCouncil); - opIds[1] = wrapper.addRateLimits(SPARK_RATE_LIMITS, opIds[0], keccak256("addRateLimits"), MIN_DELAY); + opIds[1] = _scheduleDirect( + abi.encodeWithSelector(BeamState.addRateLimits.selector, SPARK_RATE_LIMITS), + opIds[0], keccak256("addRateLimits") + ); // 1c. Schedule addController for SPARK_CONTROLLER - vm.prank(coreCouncil); - opIds[2] = wrapper.addController(SPARK_CONTROLLER, opIds[1], keccak256("addController"), MIN_DELAY); + opIds[2] = _scheduleDirect( + abi.encodeWithSelector(BeamState.addController.selector, SPARK_CONTROLLER), + opIds[1], keccak256("addController") + ); // 1d. Schedule setHop for rateLimits - vm.prank(coreCouncil); - opIds[3] = wrapper.setHop(SPARK_RATE_LIMITS, 1 hours, opIds[2], keccak256("setHop"), MIN_DELAY); + opIds[3] = _scheduleDirect( + abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 1 hours), + opIds[2], keccak256("setHop") + ); // 1e. Schedule setMaxChange for rateLimits - vm.prank(coreCouncil); - opIds[4] = wrapper.setMaxChange(SPARK_RATE_LIMITS, 2 ether, opIds[3], keccak256("setMaxChange"), MIN_DELAY); - - // 1f. Schedule addInitRateLimits (via wrapper with RateLimitConfig) - vm.prank(coreCouncil); - opIds[5] = wrapper.addInitRateLimits( - RateLimitConfig({key: rateLimitKey, rateLimits: SPARK_RATE_LIMITS, maxAmount: 1_000_000e18, slope: 100_000e18}), - opIds[4], keccak256("addInitRateLimits"), MIN_DELAY + opIds[4] = _scheduleDirect( + abi.encodeWithSelector(BeamState.setMaxChange.selector, SPARK_RATE_LIMITS, 2 ether), + opIds[3], keccak256("setMaxChange") ); - // 1g. Schedule addInitControllerActions (directly via timelock - no wrapper for generic actions) + // 1f. Schedule addInitRateLimits + opIds[5] = _scheduleDirect( + abi.encodeWithSelector(BeamState.addInitRateLimits.selector, rateLimitKey, SPARK_RATE_LIMITS, uint256(1_000_000e18), uint256(100_000e18)), + opIds[4], keccak256("addInitRateLimits") + ); + + // 1g. Schedule addInitControllerActions opIds[6] = _scheduleDirect( abi.encodeWithSelector(BeamState.addInitControllerActions.selector, setMintRecipientAction, SPARK_CONTROLLER), opIds[5], keccak256("addInitControllerActions") @@ -1128,8 +1136,11 @@ contract IntegrationTest is DssTest { configurator.callControllerAction(SPARK_CONTROLLER, setMintRecipientAction); // 4e. Operations resume after restart (via timelock) - vm.prank(coreCouncil); - bytes32 startOp = wrapper.start(bytes32(0), keccak256("restart"), MIN_DELAY); + bytes32 startOp = _scheduleDirect( + abi.encodeWithSelector(BeamState.start.selector), + bytes32(0), + keccak256("restart") + ); vm.warp(block.timestamp + MIN_DELAY); { diff --git a/test/timelock/TimelockWrapperForeign.t.sol b/test/timelock/TimelockWrapperForeign.t.sol deleted file mode 100644 index e5fc986..0000000 --- a/test/timelock/TimelockWrapperForeign.t.sol +++ /dev/null @@ -1,824 +0,0 @@ -// SPDX-FileCopyrightText: © 2026 Dai Foundation -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.24; - -import "dss-test/DssTest.sol"; -import { TimelockWrapperForeign, RateLimitConfig } from "src/timelock/TimelockWrapperForeign.sol"; -import { Timelock } from "src/timelock/Timelock.sol"; -import { BeamState } from "src/BeamState.sol"; -import { Configurator } from "src/Configurator.sol"; -import { PASDeploy } from "deploy/PASDeploy.sol"; -import { PASInstance } from "deploy/PASInstance.sol"; -import { L2PASSpell } from "deploy/L2PASSpell.sol"; - -interface ControllerLike { - function hasRole(bytes32 role, address account) external view returns (bool); - function RELAYER() external view returns (bytes32); - function mintRecipients(uint32) external view returns (bytes32); - function rateLimits() external view returns (address); - // Shared getters - function layerZeroRecipients(uint32) external view returns (bytes32); - function maxSlippages(address) external view returns (uint256); - function maxExchangeRates(address) external view returns (uint256); - // Grove-specific getters - function centrifugeRecipients(uint16) external view returns (bytes32); - // uniswapV3PoolParams returns: (swapMaxTickDelta, addLiquidityLowerTick, addLiquidityUpperTick, twapSecondsAgo) - function uniswapV3PoolParams(address) external view returns (uint24, int24, int24, uint32); - function merklDistributor() external view returns (address); - - function grantRole(bytes32 role, address account) external; -} - -interface RateLimitsLike { - function grantRole(bytes32 role, address account) external; - function getRateLimitData(bytes32 key) external view returns (uint256, uint256, uint256, uint256); -} - -contract GovRelayMock { - function relay(address target, bytes calldata targetData) external { - (bool success, bytes memory result) = target.delegatecall(targetData); - if (!success) { - if (result.length == 0) revert("L2GovernanceRelay/delegatecall-error"); - assembly ("memory-safe") { - revert(add(32, result), mload(result)) - } - } - } -} - -contract TimelockWrapperForeignTest is DssTest { - // --- Events --- - event Kiss(address indexed usr); - event Diss(address indexed usr); - event ProposalSubmitted(bytes32 indexed operationId, string functionName); - - // --- Base Addresses --- - // https://github.com/sparkdotfi/spark-address-registry/blob/master/src/Base.sol - address constant SPARK_CONTROLLER = 0x86036CE5d2f792367C0AA43164e688d13c5A60A8; - address constant SPARK_PROXY = 0xF93B7122450A50AF3e5A76E1d546e95Ac1d0F579; - - // https://github.com/grove-labs/grove-address-registry/blob/master/src/Base.sol - address constant GROVE_CONTROLLER = 0x7f8408eBbBC3504F83eeDa52910dd75Eba92C955; - address constant GROVE_PROXY = 0x491EDFB0B8b608044e227225C715981a30F3A44E; - - uint256 constant MIN_DELAY = 1 days; - - bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); - - // --- Fetched from controllers --- - address SPARK_RATE_LIMITS; - address GROVE_RATE_LIMITS; - - // --- PAS Instance --- - BeamState beamState; - Configurator configurator; - Timelock timelock; - TimelockWrapperForeign wrapper; - - GovRelayMock govRelay; - address coreCouncil; - address cBeam; - - function setUp() public { - vm.createSelectFork(vm.envString("BASE_RPC_URL")); - - // Fetch rate limits from controllers - SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); - GROVE_RATE_LIMITS = ControllerLike(GROVE_CONTROLLER).rateLimits(); - - govRelay = new GovRelayMock(); - coreCouncil = makeAddr("coreCouncil"); - cBeam = makeAddr("cBeam"); - - PASInstance memory pas = PASDeploy.deploy(address(this), address(govRelay), MIN_DELAY); - beamState = BeamState(pas.beamState); - configurator = Configurator(pas.configurator); - timelock = Timelock(payable(pas.timelock)); - wrapper = TimelockWrapperForeign(PASDeploy.deployTimelockWrapperForeign(address(this), address(govRelay), pas.timelock, pas.beamState)); - - L2PASSpell spell = new L2PASSpell(pas.beamState, pas.configurator, pas.timelock, address(wrapper)); - govRelay.relay( - address(spell), - abi.encodeWithSelector(spell.init.selector, MIN_DELAY, coreCouncil, new address[](0), new address[](0)) - ); - - // Grant configurator admin role on Base controllers and rate limiters - vm.startPrank(SPARK_PROXY); - ControllerLike(SPARK_CONTROLLER).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); - RateLimitsLike(SPARK_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); - vm.stopPrank(); - - vm.startPrank(GROVE_PROXY); - ControllerLike(GROVE_CONTROLLER).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); - RateLimitsLike(GROVE_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); - vm.stopPrank(); - - // Setup controllers, rate limiters, and cBeam in BeamState - vm.startPrank(coreCouncil); - bytes32 id; - id = wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("spark-ctrl"), MIN_DELAY); - _execute(id); - id = wrapper.addController(GROVE_CONTROLLER, bytes32(0), keccak256("grove-ctrl"), MIN_DELAY); - _execute(id); - id = wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("spark-rl"), MIN_DELAY); - _execute(id); - id = wrapper.addRateLimits(GROVE_RATE_LIMITS, bytes32(0), keccak256("grove-rl"), MIN_DELAY); - _execute(id); - id = wrapper.addCBeam(cBeam, bytes32(0), keccak256("cbeam"), MIN_DELAY); - _execute(id); - - // Verify wrapper correctly configured BeamState - assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "Spark controller not added"); - assertEq(beamState.controllers(GROVE_CONTROLLER), 1, "Grove controller not added"); - assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1, "Spark rate limits not added"); - assertEq(beamState.rateLimits(GROVE_RATE_LIMITS), 1, "Grove rate limits not added"); - assertEq(beamState.cBeams(cBeam), 1, "cBeam not added"); - - // Link cBeam to controllers/rate limiters - beamState.setCBeamForController(SPARK_CONTROLLER, cBeam); - beamState.setCBeamForController(GROVE_CONTROLLER, cBeam); - beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); - beamState.setCBeamForRateLimits(GROVE_RATE_LIMITS, cBeam); - vm.stopPrank(); - - // Set hop for rate limiters (required for setRateLimit to work on increases) - vm.startPrank(coreCouncil); - bytes32 hopId; - hopId = wrapper.setHop(SPARK_RATE_LIMITS, 1 hours, bytes32(0), keccak256("spark-hop"), MIN_DELAY); - _execute(hopId); - hopId = wrapper.setHop(GROVE_RATE_LIMITS, 1 hours, bytes32(0), keccak256("grove-hop"), MIN_DELAY); - _execute(hopId); - vm.stopPrank(); - } - - // ============================================================================ - // Helpers - // ============================================================================ - - function _execute(bytes32 id) internal { - vm.warp(block.timestamp + MIN_DELAY); - Timelock.Operation memory op = timelock.getOperation(id); - timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); - } - - function _getControllerAction(bytes32 id) internal view returns (bytes memory data, address controller) { - Timelock.Operation memory op = timelock.getOperation(id); - // Decode payload: selector (4 bytes) || abi.encode(data, controller) - bytes memory payload = op.payloads[0]; - assembly { - payload := add(payload, 4) - } - (data, controller) = abi.decode(payload, (bytes, address)); - } - - function _expectedOperationId(bytes memory payload, bytes32 predecessor, bytes32 salt) internal view returns (bytes32) { - address[] memory targets = new address[](1); - targets[0] = address(beamState); - uint256[] memory values = new uint256[](1); - bytes[] memory payloads = new bytes[](1); - payloads[0] = payload; - return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); - } - - // ============================================================================ - // Authorization Tests - // ============================================================================ - - function testConstructor() public { - vm.expectEmit(); - emit Rely(address(this)); - TimelockWrapperForeign newWrapper = new TimelockWrapperForeign(address(timelock), address(beamState)); - - assertEq(address(newWrapper.timelock()), address(timelock), "Timelock set correctly"); - assertEq(address(newWrapper.beamState()), address(beamState), "BeamState set correctly"); - assertEq(newWrapper.wards(address(this)), 1, "Deployer is ward"); - } - - function testAuth() public { - checkAuth(address(wrapper), "TimelockWrapperForeign"); - } - - function testKissDiss() public { - address usr = makeAddr("usr"); - - vm.startPrank(address(govRelay)); - assertEq(wrapper.buds(usr), 0); - vm.expectEmit(); - emit Kiss(usr); - wrapper.kiss(usr); - assertEq(wrapper.buds(usr), 1); - vm.expectEmit(); - emit Diss(usr); - wrapper.diss(usr); - assertEq(wrapper.buds(usr), 0); - vm.stopPrank(); - } - - function testAuthModifiers() public { - bytes4[] memory authedMethods = new bytes4[](2); - authedMethods[0] = wrapper.kiss.selector; - authedMethods[1] = wrapper.diss.selector; - - vm.startPrank(address(0xBEEF)); - checkModifier(address(wrapper), "TimelockWrapperForeign/not-authorized", authedMethods); - vm.stopPrank(); - } - - function testTollModifiers() public { - bytes4[] memory tolledMethods = new bytes4[](20); - tolledMethods[0] = wrapper.start.selector; - tolledMethods[1] = wrapper.setHop.selector; - tolledMethods[2] = wrapper.setMaxChange.selector; - tolledMethods[3] = wrapper.addRateLimits.selector; - tolledMethods[4] = wrapper.addController.selector; - tolledMethods[5] = wrapper.addCBeam.selector; - tolledMethods[6] = wrapper.addInitRateLimits.selector; - tolledMethods[7] = wrapper.batchAddInitRateLimits.selector; - tolledMethods[8] = wrapper.grantRole.selector; - tolledMethods[9] = wrapper.revokeRole.selector; - tolledMethods[10] = wrapper.setMintRecipient.selector; - tolledMethods[11] = wrapper.setLayerZeroRecipient.selector; - tolledMethods[12] = wrapper.setMaxSlippage.selector; - tolledMethods[13] = wrapper.setMaxExchangeRate.selector; - tolledMethods[14] = wrapper.setUniswapV3PoolMaxTickDelta.selector; - tolledMethods[15] = wrapper.setUniswapV3AddLiquidityLowerTickBound.selector; - tolledMethods[16] = wrapper.setUniswapV3AddLiquidityUpperTickBound.selector; - tolledMethods[17] = wrapper.setUniswapV3TwapSecondsAgo.selector; - tolledMethods[18] = wrapper.setCentrifugeRecipient.selector; - tolledMethods[19] = wrapper.setMerklDistributor.selector; - - vm.startPrank(address(0xBEEF)); - checkModifier(address(wrapper), "TimelockWrapperForeign/not-whitelisted", tolledMethods); - vm.stopPrank(); - } - - // ============================================================================ - // BeamState Configuration Tests - // ============================================================================ - - function testStart() public { - vm.prank(coreCouncil); - beamState.stop(); - assertTrue(beamState.stopped()); - - bytes32 salt = keccak256("start"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.start.selector), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "start"); - vm.prank(coreCouncil); - bytes32 id = wrapper.start(bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertFalse(beamState.stopped()); - } - - function testSetHop() public { - bytes32 salt = keccak256("hop"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 3600), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setHop"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.getHop(SPARK_RATE_LIMITS), 3600); - } - - function testSetMaxChange() public { - bytes32 salt = keccak256("mc"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.setMaxChange.selector, GROVE_RATE_LIMITS, 2e18), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setMaxChange"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.maxChange(GROVE_RATE_LIMITS), 2e18); - } - - function testAddRateLimits() public { - address rateLimits = makeAddr("rateLimits"); - bytes32 salt = keccak256("rl"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addRateLimits.selector, rateLimits), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "addRateLimits"); - vm.prank(coreCouncil); - bytes32 id = wrapper.addRateLimits(rateLimits, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.rateLimits(rateLimits), 1); - } - - function testAddController() public { - address controller = makeAddr("controller"); - bytes32 salt = keccak256("ctrl"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addController.selector, controller), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "addController"); - vm.prank(coreCouncil); - bytes32 id = wrapper.addController(controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.controllers(controller), 1); - } - - function testAddCBeam() public { - address beam = makeAddr("beam"); - bytes32 salt = keccak256("cbeam"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addCBeam.selector, beam), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "addCBeam"); - vm.prank(coreCouncil); - bytes32 id = wrapper.addCBeam(beam, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.cBeams(beam), 1); - } - - function _checkAddInitRateLimits(address rateLimits, bytes32 key, bytes32 salt) internal { - RateLimitConfig memory config = RateLimitConfig({ - key: key, - rateLimits: rateLimits, - maxAmount: 10_000_000e18, - slope: 1_000_000e18 - }); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits, config.maxAmount, config.slope), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "addInitRateLimits"); - vm.prank(coreCouncil); - bytes32 id = wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - // Verify stored in BeamState - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(config.key, rateLimits); - assertEq(limits.maxAmount, config.maxAmount); - assertEq(limits.slope, config.slope); - - // Execute on real rate limiter via Configurator - vm.prank(cBeam); - configurator.setRateLimit(rateLimits, config.key, config.maxAmount, config.slope); - - // Verify set on real rate limiter - (uint256 setMax, uint256 setSlope,,) = RateLimitsLike(rateLimits).getRateLimitData(config.key); - assertEq(setMax, config.maxAmount); - assertEq(setSlope, config.slope); - } - - function testAddInitRateLimitsOnSpark() public { - _checkAddInitRateLimits(SPARK_RATE_LIMITS, keccak256("spark-deposit"), keccak256("initS")); - } - - function testAddInitRateLimitsOnGrove() public { - _checkAddInitRateLimits(GROVE_RATE_LIMITS, keccak256("grove-deposit"), keccak256("initG")); - } - - function _checkBatchAddInitRateLimits(address rateLimits, bytes32 key1, bytes32 key2, bytes32 salt) internal { - RateLimitConfig[] memory configs = new RateLimitConfig[](2); - configs[0] = RateLimitConfig(key1, rateLimits, 5_000_000e18, 500_000e18); - configs[1] = RateLimitConfig(key2, rateLimits, 3_000_000e18, 300_000e18); - - // Pre-compute expected operationId for batch - address[] memory targets = new address[](2); - uint256[] memory values = new uint256[](2); - bytes[] memory payloads = new bytes[](2); - for (uint256 i = 0; i < 2; i++) { - targets[i] = address(beamState); - payloads[i] = abi.encodeWithSelector(BeamState.addInitRateLimits.selector, configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope); - } - bytes32 expectedId = timelock.hashOperationBatch(targets, values, payloads, bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "batchAddInitRateLimits"); - vm.prank(coreCouncil); - bytes32 id = wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - assertEq(timelock.getOperationsCount(), 1); - - vm.warp(block.timestamp + MIN_DELAY); - timelock.executeBatch(targets, values, payloads, bytes32(0), salt); - - // Verify stored in BeamState - assertEq(beamState.getInitRateLimits(configs[0].key, rateLimits).maxAmount, configs[0].maxAmount); - assertEq(beamState.getInitRateLimits(configs[1].key, rateLimits).maxAmount, configs[1].maxAmount); - - // Execute on real rate limiter via Configurator - vm.startPrank(cBeam); - configurator.setRateLimit(rateLimits, configs[0].key, configs[0].maxAmount, configs[0].slope); - configurator.setRateLimit(rateLimits, configs[1].key, configs[1].maxAmount, configs[1].slope); - vm.stopPrank(); - - // Verify set on real rate limiter - (uint256 max0, uint256 slope0,,) = RateLimitsLike(rateLimits).getRateLimitData(configs[0].key); - assertEq(max0, configs[0].maxAmount); - assertEq(slope0, configs[0].slope); - - (uint256 max1, uint256 slope1,,) = RateLimitsLike(rateLimits).getRateLimitData(configs[1].key); - assertEq(max1, configs[1].maxAmount); - assertEq(slope1, configs[1].slope); - } - - function testBatchAddInitRateLimitsOnSpark() public { - _checkBatchAddInitRateLimits(SPARK_RATE_LIMITS, keccak256("spark-batch-1"), keccak256("spark-batch-2"), keccak256("batchS")); - } - - function testBatchAddInitRateLimitsOnGrove() public { - _checkBatchAddInitRateLimits(GROVE_RATE_LIMITS, keccak256("grove-batch-1"), keccak256("grove-batch-2"), keccak256("batchG")); - } - - // ============================================================================ - // Controller Action Tests - // ============================================================================ - - // --- grantRole (Spark & Grove) --- - - function _checkGrantRole(address controller, bytes32 salt) internal { - bytes32 role = ControllerLike(controller).RELAYER(); - address account = makeAddr("relayer"); - - assertFalse(ControllerLike(controller).hasRole(role, account)); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("grantRole(bytes32,address)", role, account), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "grantRole"); - vm.prank(coreCouncil); - bytes32 id = wrapper.grantRole(role, account, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertTrue(ControllerLike(controller).hasRole(role, account)); - } - - function testGrantRoleOnSparkController() public { - _checkGrantRole(SPARK_CONTROLLER, keccak256("grantS")); - } - - function testGrantRoleOnGroveController() public { - _checkGrantRole(GROVE_CONTROLLER, keccak256("grantG")); - } - - // --- revokeRole (Spark & Grove) --- - - function _checkRevokeRole(address controller, address admin, bytes32 salt) internal { - bytes32 role = ControllerLike(controller).RELAYER(); - address account = makeAddr("toRevoke"); - - vm.prank(admin); - ControllerLike(controller).grantRole(role, account); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("revokeRole(bytes32,address)", role, account), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "revokeRole"); - vm.prank(coreCouncil); - bytes32 id = wrapper.revokeRole(role, account, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertFalse(ControllerLike(controller).hasRole(role, account)); - } - - function testRevokeRoleOnSparkController() public { - _checkRevokeRole(SPARK_CONTROLLER, SPARK_PROXY, keccak256("revokeS")); - } - - function testRevokeRoleOnGroveController() public { - _checkRevokeRole(GROVE_CONTROLLER, GROVE_PROXY, keccak256("revokeG")); - } - - // --- setMintRecipient (Spark & Grove) --- - - function _checkSetMintRecipient(address controller, bytes32 salt) internal { - uint32 domain = 6; - bytes32 recipient = bytes32(uint256(uint160(makeAddr("recipient")))); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMintRecipient(uint32,bytes32)", domain, recipient), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setMintRecipient"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertEq(ControllerLike(controller).mintRecipients(domain), recipient); - } - - function testSetMintRecipientOnSparkController() public { - _checkSetMintRecipient(SPARK_CONTROLLER, keccak256("mintS")); - } - - function testSetMintRecipientOnGroveController() public { - _checkSetMintRecipient(GROVE_CONTROLLER, keccak256("mintG")); - } - - // --- setLayerZeroRecipient (Spark & Grove) --- - - function _checkSetLayerZeroRecipient(address controller, bytes32 salt) internal { - uint32 endpointId = 111; - bytes32 recipient = bytes32(uint256(uint160(makeAddr("lzRecipient")))); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setLayerZeroRecipient(uint32,bytes32)", endpointId, recipient), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setLayerZeroRecipient"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertEq(ControllerLike(controller).layerZeroRecipients(endpointId), recipient); - } - - function testSetLayerZeroRecipientOnSparkController() public { - _checkSetLayerZeroRecipient(SPARK_CONTROLLER, keccak256("lzS")); - } - - function testSetLayerZeroRecipientOnGroveController() public { - _checkSetLayerZeroRecipient(GROVE_CONTROLLER, keccak256("lzG")); - } - - // --- setMaxSlippage (Spark & Grove) --- - - function _checkSetMaxSlippage(address controller, bytes32 salt) internal { - address pool = makeAddr("pool"); - uint256 slippage = 100; - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMaxSlippage(address,uint256)", pool, slippage), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setMaxSlippage"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertEq(ControllerLike(controller).maxSlippages(pool), slippage); - } - - function testSetMaxSlippageOnSparkController() public { - _checkSetMaxSlippage(SPARK_CONTROLLER, keccak256("slipS")); - } - - function testSetMaxSlippageOnGroveController() public { - _checkSetMaxSlippage(GROVE_CONTROLLER, keccak256("slipG")); - } - - // --- setMaxExchangeRate (Spark & Grove) --- - - function _checkSetMaxExchangeRate(address controller, bytes32 salt) internal { - address sUSDS = 0x5875eEE11Cf8398102FdAd704C9E96607675467a; - uint256 shares = 1e18; - uint256 maxExpectedAssets = 1.1e18; - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMaxExchangeRate(address,uint256,uint256)", sUSDS, shares, maxExpectedAssets), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setMaxExchangeRate"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setMaxExchangeRate(sUSDS, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - // maxExchangeRates stores value in 1e36 precision: maxExpectedAssets * 1e18 / shares * 1e18 = 1.1e36 - assertEq(ControllerLike(controller).maxExchangeRates(sUSDS), 1.1e36); - } - - function testSetMaxExchangeRateOnSparkController() public { - _checkSetMaxExchangeRate(SPARK_CONTROLLER, keccak256("exrS")); - } - - function testSetMaxExchangeRateOnGroveController() public { - _checkSetMaxExchangeRate(GROVE_CONTROLLER, keccak256("exrG")); - } - - // --- setUniswapV3PoolMaxTickDelta (Grove Only) --- - - function testSetUniswapV3PoolMaxTickDeltaOnGroveController() public { - address pool = makeAddr("pool"); - bytes32 salt = keccak256("tick"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3PoolMaxTickDelta(address,uint24)", pool, uint24(1000)), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setUniswapV3PoolMaxTickDelta"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - (uint24 swapMaxTickDelta,,,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); - assertEq(swapMaxTickDelta, 1000); - } - - // --- setUniswapV3AddLiquidityLowerTickBound (Grove Only) --- - - function testSetUniswapV3AddLiquidityLowerTickBoundOnGroveController() public { - address pool = makeAddr("pool"); - bytes32 salt = keccak256("lower"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3AddLiquidityLowerTickBound(address,int24)", pool, int24(-887220)), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setUniswapV3AddLiquidityLowerTickBound"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - (, int24 lowerTick,,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); - assertEq(lowerTick, -887220); - } - - // --- setUniswapV3AddLiquidityUpperTickBound (Grove Only) --- - - function testSetUniswapV3AddLiquidityUpperTickBoundOnGroveController() public { - address pool = makeAddr("pool"); - bytes32 salt = keccak256("upper"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3AddLiquidityUpperTickBound(address,int24)", pool, int24(887220)), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setUniswapV3AddLiquidityUpperTickBound"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - (,, int24 upperTick,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); - assertEq(upperTick, 887220); - } - - // --- setUniswapV3TwapSecondsAgo (Grove Only) --- - - function testSetUniswapV3TwapSecondsAgoOnGroveController() public { - address pool = makeAddr("pool"); - bytes32 salt = keccak256("twap"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3TwapSecondsAgo(address,uint32)", pool, uint32(1800)), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setUniswapV3TwapSecondsAgo"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - (,,, uint32 twapSecondsAgo) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); - assertEq(twapSecondsAgo, 1800); - } - - // --- setCentrifugeRecipient (Grove Only) --- - - function testSetCentrifugeRecipientOnGroveController() public { - uint16 centrifugeId = 1; - bytes32 recipient = bytes32(uint256(uint160(makeAddr("centrifuge")))); - bytes32 salt = keccak256("cent"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setCentrifugeRecipient(uint16,bytes32)", centrifugeId, recipient), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setCentrifugeRecipient"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - assertEq(ControllerLike(GROVE_CONTROLLER).centrifugeRecipients(centrifugeId), recipient); - } - - // --- setMerklDistributor (Grove Only) --- - - function testSetMerklDistributorOnGroveController() public { - address distributor = makeAddr("merklDistributor"); - bytes32 salt = keccak256("merkl"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMerklDistributor(address)", distributor), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setMerklDistributor"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setMerklDistributor(distributor, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - assertEq(ControllerLike(GROVE_CONTROLLER).merklDistributor(), distributor); - } -} diff --git a/test/timelock/TimelockWrapperMainnet.t.sol b/test/timelock/TimelockWrapperMainnet.t.sol deleted file mode 100644 index 65232fc..0000000 --- a/test/timelock/TimelockWrapperMainnet.t.sol +++ /dev/null @@ -1,936 +0,0 @@ -// SPDX-FileCopyrightText: © 2026 Dai Foundation -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.24; - -import "dss-test/DssTest.sol"; -import { MCD, DssInstance } from "dss-test/MCD.sol"; -import { TimelockWrapperMainnet, RateLimitConfig } from "src/timelock/TimelockWrapperMainnet.sol"; -import { Timelock } from "src/timelock/Timelock.sol"; -import { BeamState } from "src/BeamState.sol"; -import { Configurator } from "src/Configurator.sol"; -import { PASDeploy } from "deploy/PASDeploy.sol"; -import { PASInit } from "deploy/PASInit.sol"; -import { PASInstance } from "deploy/PASInstance.sol"; - -interface ControllerLike { - function hasRole(bytes32 role, address account) external view returns (bool); - function RELAYER() external view returns (bytes32); - function mintRecipients(uint32) external view returns (bytes32); - function rateLimits() external view returns (address); - // Shared getters - function layerZeroRecipients(uint32) external view returns (bytes32); - function maxSlippages(address) external view returns (uint256); - function maxExchangeRates(address) external view returns (uint256); - // Spark-specific getters - // otcs returns: (buffer, rechargeRate18, sent18, sentTimestamp, claimed18) - function otcs(address) external view returns (address, uint256, uint256, uint256, uint256); - function otcWhitelistedAssets(address, address) external view returns (bool); - // uniswapV4TickLimits returns: (tickLowerMin, tickUpperMax, maxTickSpacing) - function uniswapV4TickLimits(bytes32) external view returns (int24, int24, uint24); - // Grove-specific getters - function centrifugeRecipients(uint16) external view returns (bytes32); - // uniswapV3PoolParams returns: (swapMaxTickDelta, addLiquidityLowerTick, addLiquidityUpperTick, twapSecondsAgo) - function uniswapV3PoolParams(address) external view returns (uint24, int24, int24, uint32); - - function grantRole(bytes32 role, address account) external; -} - -interface RateLimitsLike { - function grantRole(bytes32 role, address account) external; - function getRateLimitData(bytes32 key) external view returns (uint256, uint256, uint256, uint256); -} - -contract TimelockWrapperMainnetTest is DssTest { - // --- Events --- - event Kiss(address indexed usr); - event Diss(address indexed usr); - event ProposalSubmitted(bytes32 indexed operationId, string functionName); - - // --- Mainnet Addresses --- - // https://github.com/sparkdotfi/spark-address-registry/blob/7ed39b0c31043bed076dc8734be65afab4cabec1/src/Ethereum.sol - address constant SPARK_CONTROLLER = 0xc9ff605003A1b389980f650e1aEFA1ef25C8eE32; - address constant SPARK_PROXY = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; - - // https://github.com/grove-labs/grove-address-registry/blob/dd625925ab44e89eadce0cb4258e32aae2dfa73e/src/Ethereum.sol - address constant GROVE_CONTROLLER = 0xfd9dEA9a8D5B955649579Af482DB7198A392A9F5; - address constant GROVE_PROXY = 0x1369f7b2b38c76B6478c0f0E66D94923421891Ba; - - address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; - - uint256 constant MIN_DELAY = 1 days; - - bytes32 constant OZ_DEFAULT_ADMIN_ROLE = bytes32(0); - - DssInstance dss; - - // --- Fetched from controllers --- - address SPARK_RATE_LIMITS; - address GROVE_RATE_LIMITS; - - // --- PAS Instance --- - BeamState beamState; - Configurator configurator; - Timelock timelock; - TimelockWrapperMainnet wrapper; - - address pauseProxy; - address coreCouncil; - address cBeam; - - function setUp() public { - vm.createSelectFork(vm.envString("ETH_RPC_URL")); - - // Load DssInstance from chainlog - dss = MCD.loadFromChainlog(CHAINLOG); - - // Fetch rate limits from controllers - SPARK_RATE_LIMITS = ControllerLike(SPARK_CONTROLLER).rateLimits(); - GROVE_RATE_LIMITS = ControllerLike(GROVE_CONTROLLER).rateLimits(); - - coreCouncil = makeAddr("coreCouncil"); - cBeam = makeAddr("cBeam"); - - pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); - - PASInstance memory pas = PASDeploy.deploy(address(this), pauseProxy, MIN_DELAY); - beamState = BeamState(pas.beamState); - configurator = Configurator(pas.configurator); - timelock = Timelock(payable(pas.timelock)); - wrapper = TimelockWrapperMainnet(PASDeploy.deployTimelockWrapperMainnet(address(this), pauseProxy, pas.timelock, pas.beamState)); - - vm.startPrank(pauseProxy); - PASInit.init(pas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); - PASInit.initTimelockWrapper(pas, address(wrapper), coreCouncil); - vm.stopPrank(); - - // Grant configurator admin role on mainnet controllers and rate limiters - vm.startPrank(SPARK_PROXY); - ControllerLike(SPARK_CONTROLLER).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); - RateLimitsLike(SPARK_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); - vm.stopPrank(); - - vm.startPrank(GROVE_PROXY); - ControllerLike(GROVE_CONTROLLER).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); - RateLimitsLike(GROVE_RATE_LIMITS).grantRole(OZ_DEFAULT_ADMIN_ROLE, address(configurator)); - vm.stopPrank(); - - // Setup controllers, rate limiters, and cBeam in BeamState - vm.startPrank(coreCouncil); - bytes32 id; - id = wrapper.addController(SPARK_CONTROLLER, bytes32(0), keccak256("spark-ctrl"), MIN_DELAY); - _execute(id); - id = wrapper.addController(GROVE_CONTROLLER, bytes32(0), keccak256("grove-ctrl"), MIN_DELAY); - _execute(id); - id = wrapper.addRateLimits(SPARK_RATE_LIMITS, bytes32(0), keccak256("spark-rl"), MIN_DELAY); - _execute(id); - id = wrapper.addRateLimits(GROVE_RATE_LIMITS, bytes32(0), keccak256("grove-rl"), MIN_DELAY); - _execute(id); - id = wrapper.addCBeam(cBeam, bytes32(0), keccak256("cbeam"), MIN_DELAY); - _execute(id); - - // Verify wrapper correctly configured BeamState - assertEq(beamState.controllers(SPARK_CONTROLLER), 1, "Spark controller not added"); - assertEq(beamState.controllers(GROVE_CONTROLLER), 1, "Grove controller not added"); - assertEq(beamState.rateLimits(SPARK_RATE_LIMITS), 1, "Spark rate limits not added"); - assertEq(beamState.rateLimits(GROVE_RATE_LIMITS), 1, "Grove rate limits not added"); - assertEq(beamState.cBeams(cBeam), 1, "cBeam not added"); - - // Link cBeam to controllers/rate limiters - beamState.setCBeamForController(SPARK_CONTROLLER, cBeam); - beamState.setCBeamForController(GROVE_CONTROLLER, cBeam); - beamState.setCBeamForRateLimits(SPARK_RATE_LIMITS, cBeam); - beamState.setCBeamForRateLimits(GROVE_RATE_LIMITS, cBeam); - vm.stopPrank(); - - // Set hop for rate limiters (required for setRateLimit to work on increases) - vm.startPrank(coreCouncil); - bytes32 hopId; - hopId = wrapper.setHop(SPARK_RATE_LIMITS, 1 hours, bytes32(0), keccak256("spark-hop"), MIN_DELAY); - _execute(hopId); - hopId = wrapper.setHop(GROVE_RATE_LIMITS, 1 hours, bytes32(0), keccak256("grove-hop"), MIN_DELAY); - _execute(hopId); - vm.stopPrank(); - } - - // ============================================================================ - // Helpers - // ============================================================================ - - function _execute(bytes32 id) internal { - vm.warp(block.timestamp + MIN_DELAY); - Timelock.Operation memory op = timelock.getOperation(id); - timelock.executeBatch(op.targets, op.values, op.payloads, op.predecessor, op.salt); - } - - function _getControllerAction(bytes32 id) internal view returns (bytes memory data, address controller) { - Timelock.Operation memory op = timelock.getOperation(id); - // Decode payload: selector (4 bytes) || abi.encode(data, controller) - bytes memory payload = op.payloads[0]; - assembly { - payload := add(payload, 4) - } - (data, controller) = abi.decode(payload, (bytes, address)); - } - - function _expectedOperationId(bytes memory payload, bytes32 predecessor, bytes32 salt) internal view returns (bytes32) { - address[] memory targets = new address[](1); - targets[0] = address(beamState); - uint256[] memory values = new uint256[](1); - bytes[] memory payloads = new bytes[](1); - payloads[0] = payload; - return timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); - } - - // ============================================================================ - // Authorization Tests - // ============================================================================ - - function testConstructor() public { - vm.expectEmit(); - emit Rely(address(this)); - TimelockWrapperMainnet newWrapper = new TimelockWrapperMainnet(address(timelock), address(beamState)); - - assertEq(address(newWrapper.timelock()), address(timelock), "Timelock set correctly"); - assertEq(address(newWrapper.beamState()), address(beamState), "BeamState set correctly"); - assertEq(newWrapper.wards(address(this)), 1, "Deployer is ward"); - } - - function testAuth() public { - checkAuth(address(wrapper), "TimelockWrapperMainnet"); - } - - function testKissDiss() public { - address usr = makeAddr("usr"); - - vm.startPrank(pauseProxy); - assertEq(wrapper.buds(usr), 0); - vm.expectEmit(); - emit Kiss(usr); - wrapper.kiss(usr); - assertEq(wrapper.buds(usr), 1); - vm.expectEmit(); - emit Diss(usr); - wrapper.diss(usr); - assertEq(wrapper.buds(usr), 0); - vm.stopPrank(); - } - - function testAuthModifiers() public { - bytes4[] memory authedMethods = new bytes4[](2); - authedMethods[0] = wrapper.kiss.selector; - authedMethods[1] = wrapper.diss.selector; - - vm.startPrank(address(0xBEEF)); - checkModifier(address(wrapper), "TimelockWrapperMainnet/not-authorized", authedMethods); - vm.stopPrank(); - } - - function testTollModifiers() public { - bytes4[] memory tolledMethods = new bytes4[](23); - tolledMethods[0] = wrapper.start.selector; - tolledMethods[1] = wrapper.setHop.selector; - tolledMethods[2] = wrapper.setMaxChange.selector; - tolledMethods[3] = wrapper.addRateLimits.selector; - tolledMethods[4] = wrapper.addController.selector; - tolledMethods[5] = wrapper.addCBeam.selector; - tolledMethods[6] = wrapper.addInitRateLimits.selector; - tolledMethods[7] = wrapper.batchAddInitRateLimits.selector; - tolledMethods[8] = wrapper.grantRole.selector; - tolledMethods[9] = wrapper.revokeRole.selector; - tolledMethods[10] = wrapper.setMintRecipient.selector; - tolledMethods[11] = wrapper.setLayerZeroRecipient.selector; - tolledMethods[12] = wrapper.setMaxSlippage.selector; - tolledMethods[13] = wrapper.setOTCBuffer.selector; - tolledMethods[14] = wrapper.setOTCRechargeRate.selector; - tolledMethods[15] = wrapper.setOTCWhitelistedAsset.selector; - tolledMethods[16] = wrapper.setUniswapV4TickLimits.selector; - tolledMethods[17] = wrapper.setMaxExchangeRate.selector; - tolledMethods[18] = wrapper.setUniswapV3PoolMaxTickDelta.selector; - tolledMethods[19] = wrapper.setUniswapV3AddLiquidityLowerTickBound.selector; - tolledMethods[20] = wrapper.setUniswapV3AddLiquidityUpperTickBound.selector; - tolledMethods[21] = wrapper.setUniswapV3TwapSecondsAgo.selector; - tolledMethods[22] = wrapper.setCentrifugeRecipient.selector; - - vm.startPrank(address(0xBEEF)); - checkModifier(address(wrapper), "TimelockWrapperMainnet/not-whitelisted", tolledMethods); - vm.stopPrank(); - } - - // ============================================================================ - // BeamState Configuration Tests - // ============================================================================ - - function testStart() public { - vm.prank(coreCouncil); - beamState.stop(); - assertTrue(beamState.stopped()); - - bytes32 salt = keccak256("start"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.start.selector), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "start"); - vm.prank(coreCouncil); - bytes32 id = wrapper.start(bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertFalse(beamState.stopped()); - } - - function testSetHop() public { - bytes32 salt = keccak256("hop"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.setHop.selector, SPARK_RATE_LIMITS, 3600), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setHop"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setHop(SPARK_RATE_LIMITS, 3600, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.getHop(SPARK_RATE_LIMITS), 3600); - } - - function testSetMaxChange() public { - bytes32 salt = keccak256("mc"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.setMaxChange.selector, GROVE_RATE_LIMITS, 2e18), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setMaxChange"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setMaxChange(GROVE_RATE_LIMITS, 2e18, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.maxChange(GROVE_RATE_LIMITS), 2e18); - } - - function testAddRateLimits() public { - address rateLimits = makeAddr("rateLimits"); - bytes32 salt = keccak256("rl"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addRateLimits.selector, rateLimits), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "addRateLimits"); - vm.prank(coreCouncil); - bytes32 id = wrapper.addRateLimits(rateLimits, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.rateLimits(rateLimits), 1); - } - - function testAddController() public { - address controller = makeAddr("controller"); - bytes32 salt = keccak256("ctrl"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addController.selector, controller), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "addController"); - vm.prank(coreCouncil); - bytes32 id = wrapper.addController(controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.controllers(controller), 1); - } - - function testAddCBeam() public { - address beam = makeAddr("beam"); - bytes32 salt = keccak256("cbeam"); - bytes32 expectedId = _expectedOperationId(abi.encodeWithSelector(BeamState.addCBeam.selector, beam), bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "addCBeam"); - vm.prank(coreCouncil); - bytes32 id = wrapper.addCBeam(beam, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - assertEq(beamState.cBeams(beam), 1); - } - - function _checkAddInitRateLimits(address rateLimits, bytes32 key, bytes32 salt) internal { - RateLimitConfig memory config = RateLimitConfig({ - key: key, - rateLimits: rateLimits, - maxAmount: 10_000_000e18, - slope: 1_000_000e18 - }); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitRateLimits.selector, config.key, config.rateLimits, config.maxAmount, config.slope), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "addInitRateLimits"); - vm.prank(coreCouncil); - bytes32 id = wrapper.addInitRateLimits(config, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - _execute(id); - - // Verify stored in BeamState - BeamState.DefaultRateLimits memory limits = beamState.getInitRateLimits(config.key, rateLimits); - assertEq(limits.maxAmount, config.maxAmount); - assertEq(limits.slope, config.slope); - - // Execute on real rate limiter via Configurator - vm.prank(cBeam); - configurator.setRateLimit(rateLimits, config.key, config.maxAmount, config.slope); - - // Verify set on real rate limiter - (uint256 setMax, uint256 setSlope,,) = RateLimitsLike(rateLimits).getRateLimitData(config.key); - assertEq(setMax, config.maxAmount); - assertEq(setSlope, config.slope); - } - - function testAddInitRateLimitsOnSpark() public { - _checkAddInitRateLimits(SPARK_RATE_LIMITS, keccak256("spark-deposit"), keccak256("initS")); - } - - function testAddInitRateLimitsOnGrove() public { - _checkAddInitRateLimits(GROVE_RATE_LIMITS, keccak256("grove-deposit"), keccak256("initG")); - } - - function _checkBatchAddInitRateLimits(address rateLimits, bytes32 key1, bytes32 key2, bytes32 salt) internal { - RateLimitConfig[] memory configs = new RateLimitConfig[](2); - configs[0] = RateLimitConfig(key1, rateLimits, 5_000_000e18, 500_000e18); - configs[1] = RateLimitConfig(key2, rateLimits, 3_000_000e18, 300_000e18); - - // Pre-compute expected operationId for batch - address[] memory targets = new address[](2); - uint256[] memory values = new uint256[](2); - bytes[] memory payloads = new bytes[](2); - for (uint256 i = 0; i < 2; i++) { - targets[i] = address(beamState); - payloads[i] = abi.encodeWithSelector(BeamState.addInitRateLimits.selector, configs[i].key, configs[i].rateLimits, configs[i].maxAmount, configs[i].slope); - } - bytes32 expectedId = timelock.hashOperationBatch(targets, values, payloads, bytes32(0), salt); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "batchAddInitRateLimits"); - vm.prank(coreCouncil); - bytes32 id = wrapper.batchAddInitRateLimits(configs, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - assertEq(timelock.getOperationsCount(), 1); - - vm.warp(block.timestamp + MIN_DELAY); - timelock.executeBatch(targets, values, payloads, bytes32(0), salt); - - // Verify stored in BeamState - assertEq(beamState.getInitRateLimits(configs[0].key, rateLimits).maxAmount, configs[0].maxAmount); - assertEq(beamState.getInitRateLimits(configs[1].key, rateLimits).maxAmount, configs[1].maxAmount); - - // Execute on real rate limiter via Configurator - vm.startPrank(cBeam); - configurator.setRateLimit(rateLimits, configs[0].key, configs[0].maxAmount, configs[0].slope); - configurator.setRateLimit(rateLimits, configs[1].key, configs[1].maxAmount, configs[1].slope); - vm.stopPrank(); - - // Verify set on real rate limiter - (uint256 max0, uint256 slope0,,) = RateLimitsLike(rateLimits).getRateLimitData(configs[0].key); - assertEq(max0, configs[0].maxAmount); - assertEq(slope0, configs[0].slope); - - (uint256 max1, uint256 slope1,,) = RateLimitsLike(rateLimits).getRateLimitData(configs[1].key); - assertEq(max1, configs[1].maxAmount); - assertEq(slope1, configs[1].slope); - } - - function testBatchAddInitRateLimitsOnSpark() public { - _checkBatchAddInitRateLimits(SPARK_RATE_LIMITS, keccak256("spark-batch-1"), keccak256("spark-batch-2"), keccak256("batchS")); - } - - function testBatchAddInitRateLimitsOnGrove() public { - _checkBatchAddInitRateLimits(GROVE_RATE_LIMITS, keccak256("grove-batch-1"), keccak256("grove-batch-2"), keccak256("batchG")); - } - - // ============================================================================ - // Controller Action Tests - // ============================================================================ - - // --- grantRole (Spark & Grove) --- - - function _checkGrantRole(address controller, bytes32 salt) internal { - bytes32 role = ControllerLike(controller).RELAYER(); - address account = makeAddr("relayer"); - - assertFalse(ControllerLike(controller).hasRole(role, account)); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("grantRole(bytes32,address)", role, account), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "grantRole"); - vm.prank(coreCouncil); - bytes32 id = wrapper.grantRole(role, account, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertTrue(ControllerLike(controller).hasRole(role, account)); - } - - function testGrantRoleOnSparkController() public { - _checkGrantRole(SPARK_CONTROLLER, keccak256("grantS")); - } - - function testGrantRoleOnGroveController() public { - _checkGrantRole(GROVE_CONTROLLER, keccak256("grantG")); - } - - // --- revokeRole (Spark & Grove) --- - - function _checkRevokeRole(address controller, address admin, bytes32 salt) internal { - bytes32 role = ControllerLike(controller).RELAYER(); - address account = makeAddr("toRevoke"); - - vm.prank(admin); - ControllerLike(controller).grantRole(role, account); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("revokeRole(bytes32,address)", role, account), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "revokeRole"); - vm.prank(coreCouncil); - bytes32 id = wrapper.revokeRole(role, account, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertFalse(ControllerLike(controller).hasRole(role, account)); - } - - function testRevokeRoleOnSparkController() public { - _checkRevokeRole(SPARK_CONTROLLER, SPARK_PROXY, keccak256("revokeS")); - } - - function testRevokeRoleOnGroveController() public { - _checkRevokeRole(GROVE_CONTROLLER, GROVE_PROXY, keccak256("revokeG")); - } - - // --- setMintRecipient (Spark & Grove) --- - - function _checkSetMintRecipient(address controller, bytes32 salt) internal { - uint32 domain = 6; - bytes32 recipient = bytes32(uint256(uint160(makeAddr("recipient")))); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMintRecipient(uint32,bytes32)", domain, recipient), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setMintRecipient"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setMintRecipient(domain, recipient, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertEq(ControllerLike(controller).mintRecipients(domain), recipient); - } - - function testSetMintRecipientOnSparkController() public { - _checkSetMintRecipient(SPARK_CONTROLLER, keccak256("mintS")); - } - - function testSetMintRecipientOnGroveController() public { - _checkSetMintRecipient(GROVE_CONTROLLER, keccak256("mintG")); - } - - // --- setLayerZeroRecipient (Spark & Grove) --- - - function _checkSetLayerZeroRecipient(address controller, bytes32 salt) internal { - uint32 endpointId = 111; - bytes32 recipient = bytes32(uint256(uint160(makeAddr("lzRecipient")))); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setLayerZeroRecipient(uint32,bytes32)", endpointId, recipient), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setLayerZeroRecipient"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setLayerZeroRecipient(endpointId, recipient, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertEq(ControllerLike(controller).layerZeroRecipients(endpointId), recipient); - } - - function testSetLayerZeroRecipientOnSparkController() public { - _checkSetLayerZeroRecipient(SPARK_CONTROLLER, keccak256("lzS")); - } - - function testSetLayerZeroRecipientOnGroveController() public { - _checkSetLayerZeroRecipient(GROVE_CONTROLLER, keccak256("lzG")); - } - - // --- setMaxSlippage (Spark & Grove) --- - - function _checkSetMaxSlippage(address controller, bytes32 salt) internal { - address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; - uint256 slippage = 100; - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMaxSlippage(address,uint256)", pool, slippage), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setMaxSlippage"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setMaxSlippage(pool, slippage, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - assertEq(ControllerLike(controller).maxSlippages(pool), slippage); - } - - function testSetMaxSlippageOnSparkController() public { - _checkSetMaxSlippage(SPARK_CONTROLLER, keccak256("slipS")); - } - - function testSetMaxSlippageOnGroveController() public { - _checkSetMaxSlippage(GROVE_CONTROLLER, keccak256("slipG")); - } - - // --- setOTCBuffer (Spark Only) --- - - function testSetOTCBufferOnSparkController() public { - address exchange = makeAddr("exchange"); - address buffer = makeAddr("buffer"); - bytes32 salt = keccak256("buf"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setOTCBuffer(address,address)", exchange, buffer), SPARK_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setOTCBuffer"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(SPARK_CONTROLLER, data); - (address setBuffer,,,,) = ControllerLike(SPARK_CONTROLLER).otcs(exchange); - assertEq(setBuffer, buffer); - } - - // --- setOTCRechargeRate (Spark Only) --- - - function testSetOTCRechargeRateOnSparkController() public { - address exchange = makeAddr("exchange"); - bytes32 salt = keccak256("rate"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setOTCRechargeRate(address,uint256)", exchange, 1e18), SPARK_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setOTCRechargeRate"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setOTCRechargeRate(exchange, 1e18, SPARK_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(SPARK_CONTROLLER, data); - (, uint256 rechargeRate,,,) = ControllerLike(SPARK_CONTROLLER).otcs(exchange); - assertEq(rechargeRate, 1e18); - } - - // --- setOTCWhitelistedAsset (Spark Only) --- - - function testSetOTCWhitelistedAssetOnSparkController() public { - address exchange = makeAddr("exchange"); - address buffer = makeAddr("buffer"); - address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - - // First set the buffer (required before whitelisting assets) - { - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setOTCBuffer(address,address)", exchange, buffer), SPARK_CONTROLLER), - bytes32(0), - keccak256("wlbuf") - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setOTCBuffer"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setOTCBuffer(exchange, buffer, SPARK_CONTROLLER, bytes32(0), keccak256("wlbuf"), MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - vm.prank(cBeam); - configurator.callControllerAction(SPARK_CONTROLLER, data); - } - - // Now whitelist the asset - { - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setOTCWhitelistedAsset(address,address,bool)", exchange, usdc, true), SPARK_CONTROLLER), - bytes32(0), - keccak256("wl") - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setOTCWhitelistedAsset"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setOTCWhitelistedAsset(exchange, usdc, true, SPARK_CONTROLLER, bytes32(0), keccak256("wl"), MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(SPARK_CONTROLLER, data); - } - - assertTrue(ControllerLike(SPARK_CONTROLLER).otcWhitelistedAssets(exchange, usdc)); - } - - // --- setUniswapV4TickLimits (Spark Only) --- - - function testSetUniswapV4TickLimitsOnSparkController() public { - bytes32 poolId = keccak256("test-pool"); - int24 tickLowerMin = -887220; - int24 tickUpperMax = 887220; - uint24 maxTickSpacing = 200; - bytes32 salt = keccak256("v4tick"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV4TickLimits(bytes32,int24,int24,uint24)", poolId, tickLowerMin, tickUpperMax, maxTickSpacing), SPARK_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setUniswapV4TickLimits"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV4TickLimits(poolId, tickLowerMin, tickUpperMax, maxTickSpacing, SPARK_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(SPARK_CONTROLLER, data); - (int24 setTickLowerMin, int24 setTickUpperMax, uint24 setMaxTickSpacing) = ControllerLike(SPARK_CONTROLLER).uniswapV4TickLimits(poolId); - assertEq(setTickLowerMin, tickLowerMin); - assertEq(setTickUpperMax, tickUpperMax); - assertEq(setMaxTickSpacing, maxTickSpacing); - } - - // --- setMaxExchangeRate (Spark & Grove) --- - - function _checkSetMaxExchangeRate(address controller, bytes32 salt) internal { - address sDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA; - uint256 shares = 1e18; - uint256 maxExpectedAssets = 1.1e18; - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setMaxExchangeRate(address,uint256,uint256)", sDAI, shares, maxExpectedAssets), controller), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setMaxExchangeRate"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setMaxExchangeRate(sDAI, shares, maxExpectedAssets, controller, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(controller, data); - // maxExchangeRates stores value in 1e36 precision: maxExpectedAssets * 1e18 / shares * 1e18 = 1.1e36 - assertEq(ControllerLike(controller).maxExchangeRates(sDAI), 1.1e36); - } - - function testSetMaxExchangeRateOnSparkController() public { - _checkSetMaxExchangeRate(SPARK_CONTROLLER, keccak256("exrS")); - } - - function testSetMaxExchangeRateOnGroveController() public { - _checkSetMaxExchangeRate(GROVE_CONTROLLER, keccak256("exrG")); - } - - // --- setUniswapV3PoolMaxTickDelta (Grove Only) --- - - function testSetUniswapV3PoolMaxTickDeltaOnGroveController() public { - address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; - bytes32 salt = keccak256("tick"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3PoolMaxTickDelta(address,uint24)", pool, uint24(1000)), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setUniswapV3PoolMaxTickDelta"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3PoolMaxTickDelta(pool, 1000, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - (uint24 swapMaxTickDelta,,,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); - assertEq(swapMaxTickDelta, 1000); - } - - // --- setUniswapV3AddLiquidityLowerTickBound (Grove Only) --- - - function testSetUniswapV3AddLiquidityLowerTickBoundOnGroveController() public { - address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; - bytes32 salt = keccak256("lower"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3AddLiquidityLowerTickBound(address,int24)", pool, int24(-887220)), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setUniswapV3AddLiquidityLowerTickBound"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3AddLiquidityLowerTickBound(pool, -887220, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - (, int24 lowerTick,,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); - assertEq(lowerTick, -887220); - } - - // --- setUniswapV3AddLiquidityUpperTickBound (Grove Only) --- - - function testSetUniswapV3AddLiquidityUpperTickBoundOnGroveController() public { - address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; - bytes32 salt = keccak256("upper"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3AddLiquidityUpperTickBound(address,int24)", pool, int24(887220)), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setUniswapV3AddLiquidityUpperTickBound"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3AddLiquidityUpperTickBound(pool, 887220, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - (,, int24 upperTick,) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); - assertEq(upperTick, 887220); - } - - // --- setUniswapV3TwapSecondsAgo (Grove Only) --- - - function testSetUniswapV3TwapSecondsAgoOnGroveController() public { - address pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; - bytes32 salt = keccak256("twap"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setUniswapV3TwapSecondsAgo(address,uint32)", pool, uint32(1800)), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setUniswapV3TwapSecondsAgo"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setUniswapV3TwapSecondsAgo(pool, 1800, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - (,,, uint32 twapSecondsAgo) = ControllerLike(GROVE_CONTROLLER).uniswapV3PoolParams(pool); - assertEq(twapSecondsAgo, 1800); - } - - // --- setCentrifugeRecipient (Grove Only) --- - - function testSetCentrifugeRecipientOnGroveController() public { - uint16 centrifugeId = 1; - bytes32 recipient = bytes32(uint256(uint160(makeAddr("centrifuge")))); - bytes32 salt = keccak256("cent"); - - bytes32 expectedId = _expectedOperationId( - abi.encodeWithSelector(BeamState.addInitControllerActions.selector, abi.encodeWithSignature("setCentrifugeRecipient(uint16,bytes32)", centrifugeId, recipient), GROVE_CONTROLLER), - bytes32(0), - salt - ); - - vm.expectEmit(true, true, true, true, address(wrapper)); - emit ProposalSubmitted(expectedId, "setCentrifugeRecipient"); - vm.prank(coreCouncil); - bytes32 id = wrapper.setCentrifugeRecipient(centrifugeId, recipient, GROVE_CONTROLLER, bytes32(0), salt, MIN_DELAY); - assertEq(id, expectedId); - (bytes memory data,) = _getControllerAction(id); - _execute(id); - - vm.prank(cBeam); - configurator.callControllerAction(GROVE_CONTROLLER, data); - assertEq(ControllerLike(GROVE_CONTROLLER).centrifugeRecipients(centrifugeId), recipient); - } -} From 78d771d23dad4219c2ef378f5b3ffef5b8fdc0a6 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 20 Jul 2026 21:19:13 +0700 Subject: [PATCH 147/150] Add PASAuthorizeInPAU Lib and Option to Start Paused (#13) * Add PASAuthorizeInPAU * Support starting paused (#14) * Support starting with timelock paused * Minor comment fixes, more testing * Update test/PASAuthorizeInPAU.t.sol Co-authored-by: telome <130504305+telome@users.noreply.github.com> --------- Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- deploy/L2PASSpell.sol | 4 +- deploy/PASAuthorizeInPAU.sol | 51 ++++++++++++++++++ deploy/PASInit.sol | 14 +++++ test/Integration.t.sol | 37 +++++++++++++ test/L2PASSpell.t.sol | 85 +++++++++++++++++++++++++++++ test/PASAuthorizeInPAU.t.sol | 101 +++++++++++++++++++++++++++++++++++ 6 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 deploy/PASAuthorizeInPAU.sol create mode 100644 test/L2PASSpell.t.sol create mode 100644 test/PASAuthorizeInPAU.t.sol diff --git a/deploy/L2PASSpell.sol b/deploy/L2PASSpell.sol index 3d1cdbd..31323ff 100644 --- a/deploy/L2PASSpell.sol +++ b/deploy/L2PASSpell.sol @@ -38,7 +38,8 @@ contract L2PASSpell { uint256 minDelay, address coreCouncil, address[] memory cancellers, - address[] memory pausers + address[] memory pausers, + bool startPaused ) external { PASInstance memory pas = PASInstance({ beamState: beamState, @@ -47,5 +48,6 @@ contract L2PASSpell { }); PASInit.init(pas, minDelay, coreCouncil, cancellers, pausers); + if (startPaused) PASInit.pauseTimelock(timelock, address(this)); } } diff --git a/deploy/PASAuthorizeInPAU.sol b/deploy/PASAuthorizeInPAU.sol new file mode 100644 index 0000000..f9ca571 --- /dev/null +++ b/deploy/PASAuthorizeInPAU.sol @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +interface AccessControlLike { + function grantRole(bytes32 role, address account) external; +} + +// Authorizes the PAS Configurator within a Star's PAU access-control system. +// +// Counterpart to PASInit: whereas PASInit configures the PAS-owned contracts (BeamState, +// Configurator, Timelock), this is run by a Star's governance (the current DEFAULT_ADMIN_ROLE +// holder on its PAU controller stack) to grant the PAS Configurator the admin access it needs +// on the Star's own contracts. +// +// The two grants cover everything the Configurator calls: +// - accessControls: gates every facet admin setter dispatched via `callControllerAction` +// (the controller has no roles of its own; it delegates to AccessControls). +// - rateLimits: gates `setRateLimit` (setRateLimitData / setUnlimitedRateLimitData). +// +// NOTE: DEFAULT_ADMIN_ROLE is the OZ role-admin, so each grant makes the Configurator a full +// role-superadmin of that contract (it can grant/revoke any role, including allocators). This +// is the intended trust model but has a wide blast radius; the security of these roles rests +// entirely on the timelock/cBeam/BeamState gating in front of the Configurator. +library PASAuthorizeInPAU { + + bytes32 internal constant DEFAULT_ADMIN_ROLE = 0x00; + + function authorize( + address configurator, + address accessControls, + address rateLimits + ) internal { + AccessControlLike(accessControls).grantRole(DEFAULT_ADMIN_ROLE, configurator); + AccessControlLike(rateLimits).grantRole(DEFAULT_ADMIN_ROLE, configurator); + } +} diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 5892371..5980f7e 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -53,6 +53,8 @@ interface TimelockLike { function CANCELLER_ROLE() external view returns (bytes32); function PAUSER_ROLE() external view returns (bytes32); function grantRole(bytes32, address) external; + function revokeRole(bytes32, address) external; + function pause() external; } interface PASMomLike { @@ -145,6 +147,18 @@ library PASInit { } } + // Call after `init` for starting with spell-only configurations of `DELAYED` actions + function pauseTimelock( + address timelock_, + address admin + ) internal { + TimelockLike timelock = TimelockLike(timelock_); + + timelock.grantRole(timelock.PAUSER_ROLE(), admin); + timelock.pause(); + timelock.revokeRole(timelock.PAUSER_ROLE(), admin); + } + function initExtras( PASInstance memory pasInstance, uint256 hop, diff --git a/test/Integration.t.sol b/test/Integration.t.sol index 83c5256..c2d2cda 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -198,6 +198,43 @@ contract IntegrationTest is DssTest { assertEq(beamState.wards(address(mom)), 1, "mom should have wards on beamState"); } + + function testPauseThenUnpauseTimelock() public { + PASInstance memory freshPas = PASDeploy.deploy(address(this), pauseProxy, MIN_DELAY); + Timelock freshTimelock = Timelock(payable(freshPas.timelock)); + + vm.startPrank(pauseProxy); + PASInit.init(freshPas, MIN_DELAY, coreCouncil, new address[](0), new address[](0)); + // init alone does not pause the timelock + assertFalse(freshTimelock.paused(), "timelock should not be paused by init alone"); + PASInit.pauseTimelock(freshPas.timelock, pauseProxy); + vm.stopPrank(); + + assertTrue(freshTimelock.paused(), "timelock should be paused after pauseTimelock"); + assertFalse(freshTimelock.hasRole(freshTimelock.PAUSER_ROLE(), pauseProxy), "admin should not retain PAUSER_ROLE"); + assertTrue(freshTimelock.hasRole(freshTimelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should still be configured as proposer"); + + // Configured but frozen: scheduling blocked while paused + address[] memory targets = new address[](1); + targets[0] = freshPas.beamState; + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector(BeamState.addCBeam.selector, cBeam); + vm.prank(coreCouncil); + vm.expectRevert(); + freshTimelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + + // unpause() resumes operations (needs only DEFAULT_ADMIN_ROLE, so the admin calls it directly) + vm.prank(pauseProxy); + freshTimelock.unpause(); + assertFalse(freshTimelock.paused(), "timelock should be unpaused"); + + vm.prank(coreCouncil); + freshTimelock.scheduleBatch(targets, values, payloads, bytes32(0), SALT, MIN_DELAY); + bytes32 opId = freshTimelock.hashOperationBatch(targets, values, payloads, bytes32(0), SALT); + assertTrue(freshTimelock.isOperationPending(opId), "operation should schedule after unpause"); + } + function testInitExtras() public { // Deploy a fresh PAS instance for this test PASInstance memory freshPas = PASDeploy.deploy(address(this), pauseProxy, MIN_DELAY); diff --git a/test/L2PASSpell.t.sol b/test/L2PASSpell.t.sol new file mode 100644 index 0000000..6b623e6 --- /dev/null +++ b/test/L2PASSpell.t.sol @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +import "dss-test/DssTest.sol"; +import { PASInstance } from "deploy/PASInstance.sol"; +import { PASDeploy } from "deploy/PASDeploy.sol"; +import { L2PASSpell } from "deploy/L2PASSpell.sol"; +import { BeamState } from "src/BeamState.sol"; +import { Timelock } from "src/timelock/Timelock.sol"; + +// Minimal stand-in for the governance proxy that the L2GovernanceRelay executes spells through. +// It holds the admin/ward roles and delegatecalls into the spell, so inside the spell `address(this)` +// (and the msg.sender of its calls) is this proxy - mirroring real spell execution. +contract MockGovProxy { + function exec(address spell, bytes memory data) external { + (bool ok, bytes memory ret) = spell.delegatecall(data); + if (!ok) { + assembly { revert(add(ret, 0x20), mload(ret)) } + } + } +} + +contract L2PASSpellTest is DssTest { + + PASInstance pas; + MockGovProxy proxy; + L2PASSpell spell; + Timelock timelock; + + address coreCouncil = address(0x1); + + uint256 constant MIN_DELAY = 1 days; + + function setUp() public { + proxy = new MockGovProxy(); + + // Deploy PAS owned by the proxy: switchOwner relies the proxy on BeamState and the Timelock + // is constructed with the proxy as admin - exactly the roles the spell relies on at execution. + pas = PASDeploy.deploy(address(this), address(proxy), MIN_DELAY); + timelock = Timelock(payable(pas.timelock)); + + spell = new L2PASSpell(pas.beamState, pas.configurator, pas.timelock); + } + + // Runs the spell the way the relay would: the proxy delegatecalls into it. + function _init(bool startPaused) internal { + proxy.exec( + address(spell), + abi.encodeCall(L2PASSpell.init, (MIN_DELAY, coreCouncil, new address[](0), new address[](0), startPaused)) + ); + } + + function testInitOperational() public { + _init(false); + + assertFalse(timelock.paused(), "timelock should not be paused"); + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should be proposer"); + assertTrue(BeamState(pas.beamState).hasUserRole(address(timelock), uint8(1)), "timelock should have DELAYED role"); + } + + function testInitStartPaused() public { + _init(true); + + assertTrue(timelock.paused(), "timelock should be paused"); + // The proxy temporarily held PAUSER_ROLE to pause; it must not retain it. + assertFalse(timelock.hasRole(timelock.PAUSER_ROLE(), address(proxy)), "proxy should not retain PAUSER_ROLE"); + // Still fully configured despite being paused. + assertTrue(timelock.hasRole(timelock.PROPOSER_ROLE(), coreCouncil), "coreCouncil should be proposer"); + } +} diff --git a/test/PASAuthorizeInPAU.t.sol b/test/PASAuthorizeInPAU.t.sol new file mode 100644 index 0000000..5750296 --- /dev/null +++ b/test/PASAuthorizeInPAU.t.sol @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: © 2026 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.24; + +import "dss-test/DssTest.sol"; + +import { PASAuthorizeInPAU } from "deploy/PASAuthorizeInPAU.sol"; + +import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; +import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; + +// Minimal stand-in for a PAU AccessControls / RateLimits contract: both are stock OZ +// AccessControl to this library, so one mock serves for both roles. +contract MockAccessControlled is AccessControl { + constructor(address admin) { + _grantRole(DEFAULT_ADMIN_ROLE, admin); + } +} + +// Stands in for the Star's governance spell: `authorize` is an internal library function, so it +// inlines here and `grantRole` is called with msg.sender == this harness. The harness must hold +// DEFAULT_ADMIN_ROLE on the targets, mirroring the Star proxy in production. +contract Authorizer { + function run(address configurator, address accessControls, address rateLimits) external { + PASAuthorizeInPAU.authorize(configurator, accessControls, rateLimits); + } +} + +contract PASAuthorizeInPAUTest is DssTest { + bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; + + Authorizer authorizer; + address configurator; + + function setUp() public { + authorizer = new Authorizer(); + configurator = makeAddr("configurator"); + } + + function testAuthorizeGrantsBothRoles() public { + MockAccessControlled accessControls = new MockAccessControlled(address(authorizer)); + MockAccessControlled rateLimits = new MockAccessControlled(address(authorizer)); + + // Pre-state: the configurator holds nothing. + assertFalse(accessControls.hasRole(DEFAULT_ADMIN_ROLE, configurator)); + assertFalse(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, configurator)); + + authorizer.run(configurator, address(accessControls), address(rateLimits)); + + // Both targets are granted, not just one. + assertTrue(accessControls.hasRole(DEFAULT_ADMIN_ROLE, configurator)); + assertTrue(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, configurator)); + } + + function testAuthorizeRevertsWhenGrantorNotAdminOnAccessControls() public { + // accessControls admin is someone else -> the first grant reverts. + MockAccessControlled accessControls = new MockAccessControlled(makeAddr("otherAdmin")); + MockAccessControlled rateLimits = new MockAccessControlled(address(authorizer)); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + address(authorizer), + DEFAULT_ADMIN_ROLE + ) + ); + authorizer.run(configurator, address(accessControls), address(rateLimits)); + } + + function testAuthorizeRevertsWhenGrantorNotAdminOnRateLimits() public { + // accessControls grant succeeds, then the rateLimits grant reverts (and rolls back). + MockAccessControlled accessControls = new MockAccessControlled(address(authorizer)); + MockAccessControlled rateLimits = new MockAccessControlled(makeAddr("otherAdmin")); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + address(authorizer), + DEFAULT_ADMIN_ROLE + ) + ); + authorizer.run(configurator, address(accessControls), address(rateLimits)); + + // The whole call reverted, so the earlier accessControls grant did not persist. + assertFalse(accessControls.hasRole(DEFAULT_ADMIN_ROLE, configurator)); + } +} From efd0492835247b46235b1930cc70c94be63cd9f8 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:53:01 +0700 Subject: [PATCH 148/150] Add clarification that the admin is not a pauser --- deploy/PASInit.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/PASInit.sol b/deploy/PASInit.sol index 5980f7e..a0dcc12 100644 --- a/deploy/PASInit.sol +++ b/deploy/PASInit.sol @@ -148,6 +148,7 @@ library PASInit { } // Call after `init` for starting with spell-only configurations of `DELAYED` actions + // The admin is assumed not to be listed in the pausers array, so we do not mistakenly revoke its role function pauseTimelock( address timelock_, address admin From f499088c42fd44ac786b7e85e6599f2cfebfe1a1 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:09:00 +0700 Subject: [PATCH 149/150] Add note about unlimited rate limits --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 060633f..b4e04a5 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,7 @@ Extended OpenZeppelin TimelockController with pausing support, permissionless ex ### PASMom Emergency governance contract that allows authorized parties to trigger circuit breakers. Can call `stop()` on BeamState to halt Configurator operations and `pause()` on Timelock to block scheduling and execution. Callable by the owner or via the Chief's hat through the authority. + +## Unlimited rate limits + +Any rate limit meant to stay unlimited must be registered in BeamState as `(max, 0)` (via `addInitRateLimits`). Otherwise the Configurator treats it as a normal bounded limit that a cBeam can lower. This applies both when first enabling the Configurator on an already-active PAU (every existing key meant to be unlimited must be registered) and when later opting into a facet that adds such a key. In both cases stars are expected to make sure to register it as `(max, 0)` (via a core spell or the Timelock, depending on whether it is coordinated through a star spell or the Timelock). From 947e71cd5dbaaf9c5b3840dd1b23e8e99d9a564d Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:22:03 -0300 Subject: [PATCH 150/150] =?UTF-8?q?Add=20protection=20to=20existing=20unli?= =?UTF-8?q?mited=20rate=20limit=20keys=20to=20not=20be=20mistak=E2=80=A6?= =?UTF-8?q?=20(#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add protection to existing unlimited rate limit keys to not be mistakenly erased * Additions to README --- README.md | 12 ++++++++- src/Configurator.sol | 10 ++++--- test/Configurator.t.sol | 60 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b4e04a5..ba68244 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,14 @@ Emergency governance contract that allows authorized parties to trigger circuit ## Unlimited rate limits -Any rate limit meant to stay unlimited must be registered in BeamState as `(max, 0)` (via `addInitRateLimits`). Otherwise the Configurator treats it as a normal bounded limit that a cBeam can lower. This applies both when first enabling the Configurator on an already-active PAU (every existing key meant to be unlimited must be registered) and when later opting into a facet that adds such a key. In both cases stars are expected to make sure to register it as `(max, 0)` (via a core spell or the Timelock, depending on whether it is coordinated through a star spell or the Timelock). +A rate limit key is treated as unlimited — the Configurator forces any cBeam call to keep it at `(max, 0)` and rejects any attempt to lower it — in either of these cases: + +- It is registered in BeamState as `(max, 0)` (via `addInitRateLimits`), or +- It is currently `(max, 0)` in RateLimits and has no BeamState default for that key. + +The second case protects keys that are already unlimited (e.g. withdrawal keys kept unlimited for security) even when they were never registered: a cBeam cannot unilateraly reset such a key to `0`. This holds both when first enabling the Configurator on an already-active PAU and when later opting into a facet that adds such a key. + +To make an existing unlimited key adjustable by a cBeam (e.g. to bound it), a `roleAuth` caller must register a bounded default for it via `addInitRateLimits` (through a star spell or the Timelock, depending on how the action is routed); the key then follows the normal bounded-limit rules. +Note that the bounded default must be nonzero in at least one field as (0, 0) is indistinguishable from an unregistered key. + +**Warning:** unlocking a protected unlimited key (by registering a bounded default) hands control of it to the cBeam — which can then set it to any value down to `0`, immediately. Treat it as a deliberate governance decision: use a **key-rateLimits** specific default (never the general `address(0)` slot, which applies to that key across all RateLimits contracts), and only for keys you intend to make cBeam-adjustable. diff --git a/src/Configurator.sol b/src/Configurator.sol index 7224dd2..bb37fad 100644 --- a/src/Configurator.sol +++ b/src/Configurator.sol @@ -92,13 +92,17 @@ contract Configurator { function setRateLimit(address rateLimits, bytes32 key, uint256 maxAmount, uint256 slope) external notStopped authRateLimits(rateLimits) { (uint256 defMaxAmount, uint256 defSlope) = beamState.getInitRateLimits(key, rateLimits); - if (defMaxAmount == type(uint256).max && defSlope == 0) { - // If maximal rate limits are defined in beamState the cBEAM has to use those + RateLimitsLike.RateLimitData memory current = RateLimitsLike(rateLimits).getRateLimitData(key); + + if (defMaxAmount == type(uint256).max && defSlope == 0 || + current.maxAmount == type(uint256).max && current.slope == 0 && defMaxAmount == 0 && defSlope == 0) { + // The key is locked as unlimited — and the cBEAM must keep it at (max, 0) — when it is + // either registered as unlimited in BeamState, or currently unlimited in the RateLimits with no + // BeamState default for it. This prevents lowering a pre-existing unlimited key. require(maxAmount == type(uint256).max && slope == 0, "Configurator/unlimited-incorrect-params"); RateLimitsLike(rateLimits).setUnlimitedRateLimitData(key); emit SetRateLimit(rateLimits, key, type(uint256).max, 0); } else { - RateLimitsLike.RateLimitData memory current = RateLimitsLike(rateLimits).getRateLimitData(key); uint256 maxChange = beamState.getMaxChange(rateLimits); // Ceiling is the max of (current * maxChange) and default diff --git a/test/Configurator.t.sol b/test/Configurator.t.sol index f87d0f1..1f05b36 100644 --- a/test/Configurator.t.sol +++ b/test/Configurator.t.sol @@ -387,6 +387,66 @@ contract ConfiguratorTest is DssTest { configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); } + function testRevertLowerWhenCurrentUnlimitedAndUnregistered() public { + bytes32 key = keccak256("current-unlimited-key"); + _setupCBeam(address(target1), CBEAM1); + // Key is currently unlimited on-chain but NOT registered in beamState (no defaults) + _setupRateLimitData(target1, key, type(uint256).max, 0, type(uint256).max, block.timestamp); + + // cBeam must not be able to lower an existing unlimited key + vm.prank(CBEAM1); + vm.expectRevert("Configurator/unlimited-incorrect-params"); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 10 * WAD); + } + + function testSetUnlimitedWhenCurrentUnlimitedAndUnregistered() public { + bytes32 key = keccak256("current-unlimited-idempotent-key"); + _setupCBeam(address(target1), CBEAM1); + // Currently unlimited on-chain, not registered in beamState + _setupRateLimitData(target1, key, type(uint256).max, 0, type(uint256).max, block.timestamp); + + // Passing unlimited is accepted (the only value allowed while locked) + vm.prank(CBEAM1); + vm.expectEmit(); + emit SetRateLimit(address(target1), key, type(uint256).max, 0); + configurator.setRateLimit(address(target1), key, type(uint256).max, 0); + + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); + assertEq(data.maxAmount, type(uint256).max, "maxAmount should remain unlimited"); + assertEq(data.slope, 0, "slope should remain 0"); + } + + function testLowerAllowedWhenCurrentUnlimitedButGeneralDefaultSet() public { + bytes32 key = keccak256("current-unlimited-with-general-key"); + _setupCBeam(address(target1), CBEAM1); + // Currently unlimited on-chain + _setupRateLimitData(target1, key, type(uint256).max, 0, type(uint256).max, block.timestamp); + // A general (address(0)) bounded default exists for this key -> the lock does not apply + _setupDefaultRateLimits(key, address(0), 500 * WAD, 5 * WAD); + + // cBeam can lower it (no increase, so no hop needed) + vm.prank(CBEAM1); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 0); + + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); + assertEq(data.maxAmount, 1_000 * WAD, "maxAmount should be lowered from unlimited"); + assertEq(data.slope, 0, "slope should be 0"); + } + + function testLowerAllowedWhenCurrentMaxAmountUnlimitedButSlopeNonZero() public { + bytes32 key = keccak256("current-maxamount-only-key"); + _setupCBeam(address(target1), CBEAM1); + // maxAmount is max but slope is non-zero -> not the canonical unlimited encoding, so not locked + _setupRateLimitData(target1, key, type(uint256).max, 10 * WAD, type(uint256).max, block.timestamp); + + vm.prank(CBEAM1); + configurator.setRateLimit(address(target1), key, 1_000 * WAD, 0); + + RateLimitsLike.RateLimitData memory data = target1.getRateLimitData(key); + assertEq(data.maxAmount, 1_000 * WAD, "maxAmount should be lowered"); + assertEq(data.slope, 0, "slope should be 0"); + } + // --- Overflow Protection Tests --- function testSetRateLimitNoOverflowWhenCurrentMaxAmountIsMax() public {