Skip to content

Commit 3de581a

Browse files
authored
Add Safe Module for permissioned rebase (#2888)
* Add Safe Module for permissioned rebase * update address checksum * Deploy modules * Add sonic deployment * redeploy sonic contract
1 parent b26695a commit 3de581a

18 files changed

Lines changed: 3304 additions & 26 deletions
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.0;
3+
4+
import { AbstractSafeModule } from "./AbstractSafeModule.sol";
5+
import { IVault } from "../interfaces/IVault.sol";
6+
7+
/**
8+
* @title PermissionedRebaseModule
9+
* @notice Safe module that lets a permissioned operator drive a `rebase()`
10+
* on vaults that are kept in the rebase-paused state. For each
11+
* configured vault, the module calls (in order, via the Safe):
12+
* `unpauseRebase()` -> `rebase()` -> `pauseRebase()`.
13+
* If any sub-call fails, the whole transaction reverts so a vault
14+
* can never be left unpaused by a partial run.
15+
*/
16+
contract PermissionedRebaseModule is AbstractSafeModule {
17+
mapping(address => bool) public isVaultWhitelisted;
18+
address[] public vaults;
19+
20+
event VaultAdded(address vault);
21+
event VaultRemoved(address vault);
22+
event PermissionedRebaseExecuted(address vault);
23+
24+
constructor(
25+
address _safeAddress,
26+
address _operator,
27+
address[] memory _vaults
28+
) AbstractSafeModule(_safeAddress) {
29+
_grantRole(OPERATOR_ROLE, _operator);
30+
31+
for (uint256 i = 0; i < _vaults.length; i++) {
32+
_addVault(_vaults[i]);
33+
}
34+
}
35+
36+
/**
37+
* @notice For every whitelisted vault, sequentially call
38+
* `unpauseRebase()`, `rebase()`, then `pauseRebase()` via the
39+
* Safe. Reverts atomically on any sub-call failure.
40+
*/
41+
function permissionedRebase() external onlyRole(OPERATOR_ROLE) {
42+
uint256 vaultsLength = vaults.length;
43+
for (uint256 i = 0; i < vaultsLength; i++) {
44+
address vault = vaults[i];
45+
_execOnVault(vault, IVault.unpauseRebase.selector);
46+
_execOnVault(vault, IVault.rebase.selector);
47+
_execOnVault(vault, IVault.pauseRebase.selector);
48+
emit PermissionedRebaseExecuted(vault);
49+
}
50+
}
51+
52+
function _execOnVault(address vault, bytes4 selector) internal {
53+
bool success = safeContract.execTransactionFromModule(
54+
vault,
55+
0, // Value
56+
abi.encodeWithSelector(selector),
57+
0 // Call
58+
);
59+
require(success, "Vault call failed");
60+
}
61+
62+
/**
63+
* @notice Add a vault to the whitelist. Only the Safe can call.
64+
*/
65+
function addVault(address _vault) external onlyRole(DEFAULT_ADMIN_ROLE) {
66+
_addVault(_vault);
67+
}
68+
69+
function _addVault(address _vault) internal {
70+
require(_vault != address(0), "Vault is zero address");
71+
require(!isVaultWhitelisted[_vault], "Vault already whitelisted");
72+
isVaultWhitelisted[_vault] = true;
73+
vaults.push(_vault);
74+
emit VaultAdded(_vault);
75+
}
76+
77+
/**
78+
* @notice Remove a vault from the whitelist. Only the Safe can call.
79+
*/
80+
function removeVault(address _vault) external onlyRole(DEFAULT_ADMIN_ROLE) {
81+
require(isVaultWhitelisted[_vault], "Vault not whitelisted");
82+
isVaultWhitelisted[_vault] = false;
83+
84+
for (uint256 i = 0; i < vaults.length; i++) {
85+
if (vaults[i] == _vault) {
86+
vaults[i] = vaults[vaults.length - 1];
87+
vaults.pop();
88+
break;
89+
}
90+
}
91+
92+
emit VaultRemoved(_vault);
93+
}
94+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const { deployOnBase } = require("../../utils/deploy-l2");
2+
const addresses = require("../../utils/addresses");
3+
const { isFork } = require("../../utils/hardhat-helpers");
4+
const { impersonateAndFund } = require("../../utils/signers");
5+
6+
module.exports = deployOnBase(
7+
{
8+
deployName: "049_permissioned_rebase_module",
9+
},
10+
async ({ deployWithConfirmation, withConfirmation }) => {
11+
const safeAddress = addresses.multichainStrategist;
12+
13+
const cOETHBaseVaultProxy = await ethers.getContract("OETHBaseVaultProxy");
14+
15+
await deployWithConfirmation("PermissionedRebaseModule", [
16+
safeAddress,
17+
addresses.permissionedRebaseRelayer,
18+
[cOETHBaseVaultProxy.address],
19+
]);
20+
const cPermissionedRebaseModule = await ethers.getContract(
21+
"PermissionedRebaseModule"
22+
);
23+
24+
console.log(
25+
`PermissionedRebaseModule (for ${safeAddress}) deployed to`,
26+
cPermissionedRebaseModule.address
27+
);
28+
29+
if (isFork) {
30+
const safeSigner = await impersonateAndFund(safeAddress);
31+
32+
const cSafe = await ethers.getContractAt(
33+
["function enableModule(address module) external"],
34+
safeAddress
35+
);
36+
37+
await withConfirmation(
38+
cSafe
39+
.connect(safeSigner)
40+
.enableModule(cPermissionedRebaseModule.address)
41+
);
42+
43+
console.log("Enabled module on fork");
44+
}
45+
46+
return {
47+
actions: [],
48+
};
49+
}
50+
);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const addresses = require("../../utils/addresses");
2+
const {
3+
deploymentWithGovernanceProposal,
4+
deployWithConfirmation,
5+
} = require("../../utils/deploy");
6+
7+
module.exports = deploymentWithGovernanceProposal(
8+
{
9+
deployName: "193_permissioned_rebase_module",
10+
forceDeploy: false,
11+
reduceQueueTime: true,
12+
deployerIsProposer: false,
13+
proposalId: "",
14+
},
15+
async () => {
16+
const safeAddress = addresses.multichainStrategist;
17+
18+
const cVaultProxy = await ethers.getContract("VaultProxy");
19+
const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy");
20+
21+
await deployWithConfirmation("PermissionedRebaseModule", [
22+
safeAddress,
23+
addresses.permissionedRebaseRelayer,
24+
[cVaultProxy.address, cOETHVaultProxy.address],
25+
]);
26+
const cPermissionedRebaseModule = await ethers.getContract(
27+
"PermissionedRebaseModule"
28+
);
29+
console.log(
30+
`PermissionedRebaseModule deployed to ${cPermissionedRebaseModule.address}`
31+
);
32+
33+
// TODO: After deployment, the Multichain Strategist Safe must call
34+
// enableModule(cPermissionedRebaseModule.address) for this module to be
35+
// able to act on the OUSD and OETH vaults.
36+
37+
return {
38+
actions: [],
39+
};
40+
}
41+
);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const { deployOnSonic } = require("../../utils/deploy-l2");
2+
const addresses = require("../../utils/addresses");
3+
const { isFork } = require("../../utils/hardhat-helpers");
4+
const { impersonateAndFund } = require("../../utils/signers");
5+
6+
module.exports = deployOnSonic(
7+
{
8+
deployName: "028_permissioned_rebase_module",
9+
},
10+
async ({ deployWithConfirmation, withConfirmation }) => {
11+
const safeAddress = addresses.multichainStrategist;
12+
13+
const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy");
14+
15+
await deployWithConfirmation("PermissionedRebaseModule", [
16+
safeAddress,
17+
addresses.permissionedRebaseRelayer,
18+
[cOSonicVaultProxy.address],
19+
]);
20+
const cPermissionedRebaseModule = await ethers.getContract(
21+
"PermissionedRebaseModule"
22+
);
23+
24+
console.log(
25+
`PermissionedRebaseModule (for ${safeAddress}) deployed to`,
26+
cPermissionedRebaseModule.address
27+
);
28+
29+
if (isFork) {
30+
const safeSigner = await impersonateAndFund(safeAddress);
31+
32+
const cSafe = await ethers.getContractAt(
33+
["function enableModule(address module) external"],
34+
safeAddress
35+
);
36+
37+
await withConfirmation(
38+
cSafe
39+
.connect(safeSigner)
40+
.enableModule(cPermissionedRebaseModule.address)
41+
);
42+
43+
console.log("Enabled module on fork");
44+
}
45+
46+
return {
47+
actions: [],
48+
};
49+
}
50+
);

contracts/deployments/base/.migrations.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,6 @@
4444
"044_bridge_helper_module": 1772010908,
4545
"045_deploy_pool_booster_merkl_factory": 1772622327,
4646
"046_crosschain_upgrade_remote": 1772750064,
47-
"047_merkl_pb_bribes_module": 1773930124
47+
"047_merkl_pb_bribes_module": 1773930124,
48+
"049_permissioned_rebase_module": 1778095983
4849
}

0 commit comments

Comments
 (0)