Skip to content

Commit

Permalink
feat: clinic deploy scripts + audit (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
sakulstra authored Mar 3, 2025
1 parent 3c6dfa5 commit 4a1bfd9
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 6 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Compiler files
cache/
out/
zkout/

# Ignores development broadcast logs
!/broadcast
/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

Expand All @@ -13,4 +14,4 @@ docs/
# Dotenv file
.env

.vscode
.vscode
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# include .env file and export its env vars
# (-include to ignore error if it does not exist)
-include .env

# deps
update:; forge update

# Build & test
build :; forge build --sizes
test :; forge test -vvv

test-contract :; forge test --match-contract ${filter} -vv

# Deploy
deploy-ledger :; FOUNDRY_PROFILE=${chain} forge script $(if $(filter zksync,${chain}),--zksync) ${contract} --rpc-url ${chain} $(if ${dry},--sender 0x25F2226B597E8F9514B3F68F00f494cF4f286491 -vvvv, --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv --slow --broadcast)
deploy-pk :; FOUNDRY_PROFILE=${chain} forge script $(if $(filter zksync,${chain}),--zksync) ${contract} --rpc-url ${chain} $(if ${dry},--sender 0x25F2226B597E8F9514B3F68F00f494cF4f286491 -vvvv, --private-key ${PRIVATE_KEY} --verify -vvvv --slow --broadcast)
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Aave stewards are utility contracts that allow a permissioned entity to perform tasks on behalf of the Aave DAO.

### Audits and security reviews:

- [ClinicSteward](./audits/ClinicSteward.md)
- [PoolExposureSteward](./audits/PoolExposureSteward.md)

**Stewards**:

- [Generalized Risk Stewards](https://github.com/aave-dao/aave-v3-risk-stewards)
Expand Down
Binary file modified audits/2025_02_17_ClinicSteward_Certora.pdf
Binary file not shown.
70 changes: 70 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,59 @@
src = "src"
out = "out"
test = "tests"
script = "scripts"
libs = ["lib"]
optimizer = true
optimizer_runs = 200
solc = '0.8.22'
evm_version = 'shanghai'
bytecode_hash = 'none'

[profile.zksync]
libs = ['lib']
solc = '0.8.27'
evm_version = 'cancun'
optimizer = true
optimizer_runs = 200
bytecode_hash = 'ipfs'

[profile.zksync.zksync]
fallback_oz = true
mode = "3"
zksolc = "1.5.10"

[profile.metis]
evm_version = 'shanghai'

[profile.mainnet]
evm_version = 'shanghai'

[profile.arbitrum]
evm_version = 'shanghai'

[profile.optimism]
evm_version = 'shanghai'

[profile.base]
evm_version = 'shanghai'

[profile.polygon]
evm_version = 'shanghai'

[profile.avalanche]
evm_version = 'shanghai'

[profile.gnosis]
evm_version = 'shanghai'

[profile.bnb]
evm_version = 'shanghai'

[profile.linea]
evm_version = 'london'

[profile.sonic]
evm_version = 'cancun'

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
[rpc_endpoints]
Expand All @@ -29,6 +81,24 @@ gnosis = "${RPC_GNOSIS}"
zkEVM = "${RPC_ZKEVM}"
celo = "${RPC_CELO}"
zksync = "${RPC_ZKSYNC}"
linea = "${RPC_LINEA}"

[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY_MAINNET}", chain = 1 }
optimism = { key = "${ETHERSCAN_API_KEY_OPTIMISM}", chain = 10 }
avalanche = { key = "${ETHERSCAN_API_KEY_AVALANCHE}", chain = 43114 }
polygon = { key = "${ETHERSCAN_API_KEY_POLYGON}", chain = 137 }
arbitrum = { key = "${ETHERSCAN_API_KEY_ARBITRUM}", chain = 42161 }
fantom = { key = "${ETHERSCAN_API_KEY_FANTOM}", chain = 250 }
metis = { key = "any", chainId = 1088, url = 'https://andromeda-explorer.metis.io/' }
base = { key = "${ETHERSCAN_API_KEY_BASE}", chainId = 8453 }
zkevm = { key = "${ETHERSCAN_API_KEY_ZKEVM}", chainId = 1101 }
gnosis = { key = "${ETHERSCAN_API_KEY_GNOSIS}", chainId = 100 }
bnb = { key = "${ETHERSCAN_API_KEY_BNB}", chainId = 56, url = 'https://api.bscscan.com/api' }
scroll = { key = "${ETHERSCAN_API_KEY_SCROLL}", chainId = 534352 }
zksync = { key = "${ETHERSCAN_API_KEY_ZKSYNC}", chain = 324 }
linea = { key = "${ETHERSCAN_API_KEY_LINEA}", chain = 59144 }
sonic = { key = "${ETHERSCAN_API_KEY_SONIC}", chain = 146 }

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
[fmt]
Expand Down
118 changes: 118 additions & 0 deletions scripts/DeployClinicSteward.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ClinicSteward} from "src/maintenance/ClinicSteward.sol";
import {IPool} from "aave-v3-origin/contracts/interfaces/IPool.sol";
import {ICollector} from "aave-v3-origin/contracts/treasury/ICollector.sol";
import {Script, Create2Utils} from "solidity-utils/contracts/utils/ScriptUtils.sol";
import {ChainIds} from "solidity-utils/contracts/utils/ChainHelpers.sol";

import {AaveV3Polygon} from "aave-address-book/AaveV3Polygon.sol";
import {GovernanceV3Polygon} from "aave-address-book/GovernanceV3Polygon.sol";

import {AaveV3Avalanche} from "aave-address-book/AaveV3Avalanche.sol";
import {GovernanceV3Avalanche} from "aave-address-book/GovernanceV3Avalanche.sol";

import {AaveV3Optimism} from "aave-address-book/AaveV3Optimism.sol";
import {GovernanceV3Optimism} from "aave-address-book/GovernanceV3Optimism.sol";

import {AaveV3Arbitrum} from "aave-address-book/AaveV3Arbitrum.sol";
import {GovernanceV3Arbitrum} from "aave-address-book/GovernanceV3Arbitrum.sol";

import {AaveV3Ethereum} from "aave-address-book/AaveV3Ethereum.sol";
import {GovernanceV3Ethereum} from "aave-address-book/GovernanceV3Ethereum.sol";

import {AaveV3BNB} from "aave-address-book/AaveV3BNB.sol";
import {GovernanceV3BNB} from "aave-address-book/GovernanceV3BNB.sol";

import {AaveV3Gnosis} from "aave-address-book/AaveV3Gnosis.sol";
import {GovernanceV3Gnosis} from "aave-address-book/GovernanceV3Gnosis.sol";

import {AaveV3Scroll} from "aave-address-book/AaveV3Scroll.sol";
import {GovernanceV3Scroll} from "aave-address-book/GovernanceV3Scroll.sol";

import {AaveV3Base} from "aave-address-book/AaveV3Base.sol";
import {GovernanceV3Base} from "aave-address-book/GovernanceV3Base.sol";

import {AaveV3Metis} from "aave-address-book/AaveV3Metis.sol";
import {GovernanceV3Metis} from "aave-address-book/GovernanceV3Metis.sol";

import {AaveV3Linea} from "aave-address-book/AaveV3Linea.sol";
import {GovernanceV3Linea} from "aave-address-book/GovernanceV3Linea.sol";

import {AaveV3EthereumLido} from "aave-address-book/AaveV3EthereumLido.sol";
import {AaveV3EthereumEtherFi} from "aave-address-book/AaveV3EthereumEtherFi.sol";

import {AaveV3ZkSync} from "aave-address-book/AaveV3ZkSync.sol";
import {GovernanceV3ZkSync} from "aave-address-book/GovernanceV3ZkSync.sol";

address constant MULTISIG = 0xdeadD8aB03075b7FBA81864202a2f59EE25B312b;
address constant MULTISIG_ZK = 0x77CC0A0582475bfD74CD838610e817d05c181E11;

library DeploymentLibrary {
function _deploy(IPool pool, ICollector collector, address admin, uint256 budget) internal {
Create2Utils.create2Deploy(
"v1", type(ClinicSteward).creationCode, abi.encode(address(pool), address(collector), admin, MULTISIG, budget)
);
}

function _deployZk(IPool pool, ICollector collector, address admin, uint256 budget) internal {
new ClinicSteward{salt: "v1"}(address(pool), address(collector), admin, MULTISIG_ZK, budget);
}
}

contract Deploy is Script {
function run() external {
vm.startBroadcast();
if (block.chainid == ChainIds.MAINNET) {
DeploymentLibrary._deploy(
AaveV3Ethereum.POOL, AaveV3Ethereum.COLLECTOR, GovernanceV3Ethereum.EXECUTOR_LVL_1, 30_000e8
);
DeploymentLibrary._deploy(
AaveV3EthereumLido.POOL, AaveV3Ethereum.COLLECTOR, GovernanceV3Ethereum.EXECUTOR_LVL_1, 5_000e8
);
// _deploy(AaveV3EthereumEtherFi.POOL, AaveV3Ethereum.COLLECTOR, GovernanceV3Mainnet.EXECUTOR_LVL_1);
}
if (block.chainid == ChainIds.POLYGON) {
DeploymentLibrary._deploy(
AaveV3Polygon.POOL, AaveV3Polygon.COLLECTOR, GovernanceV3Polygon.EXECUTOR_LVL_1, 30_000e8
);
}
if (block.chainid == ChainIds.AVALANCHE) {
DeploymentLibrary._deploy(
AaveV3Avalanche.POOL, AaveV3Avalanche.COLLECTOR, GovernanceV3Avalanche.EXECUTOR_LVL_1, 350_000e8
);
}
if (block.chainid == ChainIds.OPTIMISM) {
DeploymentLibrary._deploy(
AaveV3Optimism.POOL, AaveV3Optimism.COLLECTOR, GovernanceV3Optimism.EXECUTOR_LVL_1, 5_000e8
);
}
if (block.chainid == ChainIds.ARBITRUM) {
DeploymentLibrary._deploy(
AaveV3Arbitrum.POOL, AaveV3Arbitrum.COLLECTOR, GovernanceV3Arbitrum.EXECUTOR_LVL_1, 60_000e8
);
}
if (block.chainid == ChainIds.SCROLL) {
DeploymentLibrary._deploy(AaveV3Scroll.POOL, AaveV3Scroll.COLLECTOR, GovernanceV3Scroll.EXECUTOR_LVL_1, 1_000e8);
}
if (block.chainid == ChainIds.BASE) {
DeploymentLibrary._deploy(AaveV3Base.POOL, AaveV3Base.COLLECTOR, GovernanceV3Base.EXECUTOR_LVL_1, 15_000e8);
}
if (block.chainid == ChainIds.ZKSYNC) {
DeploymentLibrary._deployZk(AaveV3ZkSync.POOL, AaveV3ZkSync.COLLECTOR, GovernanceV3ZkSync.EXECUTOR_LVL_1, 1_000e8);
}
if (block.chainid == ChainIds.BNB) {
DeploymentLibrary._deploy(AaveV3BNB.POOL, AaveV3BNB.COLLECTOR, GovernanceV3BNB.EXECUTOR_LVL_1, 2_000e8);
}
if (block.chainid == ChainIds.GNOSIS) {
DeploymentLibrary._deploy(AaveV3Gnosis.POOL, AaveV3Gnosis.COLLECTOR, GovernanceV3Gnosis.EXECUTOR_LVL_1, 1_000e8);
}
if (block.chainid == ChainIds.METIS) {
DeploymentLibrary._deploy(AaveV3Metis.POOL, AaveV3Metis.COLLECTOR, GovernanceV3Metis.EXECUTOR_LVL_1, 1_000e8);
}
if (block.chainid == ChainIds.LINEA) {
DeploymentLibrary._deploy(AaveV3Linea.POOL, AaveV3Linea.COLLECTOR, GovernanceV3Linea.EXECUTOR_LVL_1, 1_000e8);
}
}
}
6 changes: 3 additions & 3 deletions src/maintenance/ClinicSteward.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
import {IPool, DataTypes, IPoolAddressesProvider, IPriceOracleGetter} from "aave-address-book/AaveV3.sol";

import {UserConfiguration} from "aave-v3-origin/contracts/protocol/libraries/configuration/UserConfiguration.sol";
import {ICollector, IERC20 as IERC20Col} from "aave-v3-origin/contracts/treasury/ICollector.sol";
import {ICollector} from "aave-v3-origin/contracts/treasury/ICollector.sol";

import {IRescuableBase} from "solidity-utils/contracts/utils/interfaces/IRescuableBase.sol";
import {RescuableBase} from "solidity-utils/contracts/utils/RescuableBase.sol";
Expand Down Expand Up @@ -217,10 +217,10 @@ contract ClinicSteward is IClinicSteward, RescuableBase, Multicall, AccessContro
if (useAToken) {
address aToken = POOL.getReserveAToken(asset);
// 1 wei surplus to account for rounding on multiple operations
ICollector(COLLECTOR).transfer(IERC20Col(aToken), address(this), amount + 1);
ICollector(COLLECTOR).transfer(IERC20(aToken), address(this), amount + 1);
POOL.withdraw(asset, type(uint256).max, address(this));
} else {
ICollector(COLLECTOR).transfer(IERC20Col(asset), address(this), amount);
ICollector(COLLECTOR).transfer(IERC20(asset), address(this), amount);
}
}

Expand Down

3 comments on commit 4a1bfd9

@sakulstra
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔮 Coverage report
File Line Coverage Function Coverage Branch Coverage
src/maintenance/ClinicSteward.sol ${\color{orange}93.67\%}$
$74 / 79$
94, 115, 116, 156, 157
${\color{orange}93.33\%}$
$14 / 15$
ClinicSteward.renewAllowance
${\color{orange}87.5\%}$
$7 / 8$

@sakulstra
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌈 Test Results
No files changed, compilation skipped

Ran 28 tests for tests/gas/maintenance/ClinicSteward.gas.t.sol:ClinicStewardGasTest
[PASS] test_batchLiquidate_five_users() (gas: 1790961)
[PASS] test_batchLiquidate_four_users() (gas: 1466277)
[PASS] test_batchLiquidate_one_user() (gas: 700611)
[PASS] test_batchLiquidate_six_users() (gas: 2009858)
[PASS] test_batchLiquidate_three_users() (gas: 1247423)
[PASS] test_batchLiquidate_two_users() (gas: 1028507)
[PASS] test_batchLiquidate_zero_users() (gas: 256311)
[PASS] test_batchRepayBadDebt_five_users() (gas: 631701)
[PASS] test_batchRepayBadDebt_four_users() (gas: 568873)
[PASS] test_batchRepayBadDebt_one_user() (gas: 383617)
[PASS] test_batchRepayBadDebt_six_users() (gas: 694463)
[PASS] test_batchRepayBadDebt_three_users() (gas: 506048)
[PASS] test_batchRepayBadDebt_two_users() (gas: 443290)
[PASS] test_batchRepayBadDebt_zero_users() (gas: 236342)
[PASS] test_getBadDebtAmount_five_users() (gas: 108457)
[PASS] test_getBadDebtAmount_four_users() (gas: 94411)
[PASS] test_getBadDebtAmount_one_user() (gas: 52255)
[PASS] test_getBadDebtAmount_six_users() (gas: 122568)
[PASS] test_getBadDebtAmount_three_users() (gas: 80389)
[PASS] test_getBadDebtAmount_two_users() (gas: 66363)
[PASS] test_getBadDebtAmount_zero_users() (gas: 23218)
[PASS] test_getDebtAmount_five_users() (gas: 89846)
[PASS] test_getDebtAmount_four_users() (gas: 79506)
[PASS] test_getDebtAmount_one_user() (gas: 48559)
[PASS] test_getDebtAmount_six_users() (gas: 100140)
[PASS] test_getDebtAmount_three_users() (gas: 69169)
[PASS] test_getDebtAmount_two_users() (gas: 66333)
[PASS] test_getDebtAmount_zero_users() (gas: 23143)
Suite result: ok. 28 passed; 0 failed; 0 skipped; finished in 319.80ms (146.71ms CPU time)

Ran 16 tests for tests/maintenance/ClinicSteward.t.sol:ClinicStewardTest
[PASS] test_batchLiquidate() (gas: 1974454)
[PASS] test_batchLiquidateUseAToken() (gas: 2155594)
[PASS] test_batchRepayBadDebt() (gas: 718983)
[PASS] test_batchRepayBadDebtUseAToken() (gas: 603389)
[PASS] test_getBadDebtAmount() (gas: 142263)
[PASS] test_getDebtAmount() (gas: 119888)
[PASS] test_maxRescue() (gas: 185526)
[PASS] test_rescueEth() (gas: 28299)
[PASS] test_rescueToken() (gas: 198558)
[PASS] test_reverts_batchLiquidate_caller_not_cleaner(address) (runs: 256, μ: 36728, ~: 36728)
[PASS] test_reverts_batchLiquidate_exceeded_pull_limit() (gas: 3912325)
[PASS] test_reverts_batchRepayBadDebt_caller_not_cleaner(address) (runs: 256, μ: 34579, ~: 34579)
[PASS] test_reverts_batchRepayBadDebt_exceeded_pull_limit() (gas: 2283956)
[PASS] test_reverts_setAvailableBudget_caller_not_admin(address) (runs: 256, μ: 15391, ~: 15391)
[PASS] test_setAvailableBudget() (gas: 24847)
[PASS] test_userHasSomeCollateral_returns_zero() (gas: 66453)
Suite result: ok. 16 passed; 0 failed; 0 skipped; finished in 378.74ms (217.39ms CPU time)

Ran 2 test suites in 382.58ms (698.53ms CPU time): 44 tests passed, 0 failed, 0 skipped (44 total tests)

@sakulstra
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Forge Gas Snapshots

🔕 Unchanged
Path Value
snapshots/ClinicSteward.json
function batchLiquidate: with 0 users 117,607
function batchLiquidate: with 1 user 569,483
function batchLiquidate: with 2 users 899,138
function batchLiquidate: with 3 users 1,116,891
function batchLiquidate: with 4 users 1,334,656
function batchLiquidate: with 5 users 1,658,296
function batchLiquidate: with 6 users 1,876,061
function batchRepayBadDebt: with 0 users 93,637
function batchRepayBadDebt: with 1 user 253,484
function batchRepayBadDebt: with 2 users 315,955
function batchRepayBadDebt: with 3 users 378,425
function batchRepayBadDebt: with 4 users 440,896
function batchRepayBadDebt: with 5 users 503,367
function batchRepayBadDebt: with 6 users 565,838
function getBadDebtAmount: with 0 users 12,843
function getBadDebtAmount: with 1 user 38,653
function getBadDebtAmount: with 2 users 51,411
function getBadDebtAmount: with 4 users 76,929
function getBadDebtAmount: with 5 users 89,688
function getBadDebtAmount: with 6 users 102,447
function getDebtAmount: with 0 users 12,866
function getDebtAmount: with 1 user 34,336
function getDebtAmount: with 2 users 51,411
function getDebtAmount: with 4 users 59,591
function getDebtAmount: with 5 users 68,009
function getDebtAmount: with 6 users 76,428

Please sign in to comment.