Skip to content

Commit 76c847b

Browse files
committed
feat: generic payload exec; gho caps adjustment
1 parent 309643e commit 76c847b

5 files changed

Lines changed: 239 additions & 8 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
## Reserve changes
2+
3+
### Reserves altered
4+
5+
#### GHO ([0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f](https://etherscan.io/address/0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f))
6+
7+
| description | value before | value after |
8+
| --- | --- | --- |
9+
| supplyCap | 55,000,000 GHO | 45,000,000 GHO |
10+
| borrowCap | 43,000,000 GHO | 35,000,000 GHO |
11+
12+
13+
## Raw diff
14+
15+
```json
16+
{
17+
"reserves": {
18+
"0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f": {
19+
"borrowCap": {
20+
"from": 43000000,
21+
"to": 35000000
22+
},
23+
"supplyCap": {
24+
"from": 55000000,
25+
"to": 45000000
26+
}
27+
}
28+
},
29+
"raw": {
30+
"0xae05cd22df81871bc7cc2a04becfb516bfe332c8": {
31+
"label": null,
32+
"contract": "lib/aave-umbrella/lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy",
33+
"balanceDiff": null,
34+
"nonceDiff": null,
35+
"stateDiff": {
36+
"0xfd2dab4be6d07bba0109696859cf3ea9f610b92569d2a062e705af4b9c58ff16": {
37+
"previousValue": "0x10000000000000000000000000003473bc00029020c003e80512000000000000",
38+
"newValue": "0x10000000000000000000000000002aea540002160ec003e80512000000000000"
39+
}
40+
}
41+
},
42+
"0xe6ec1f0ae6cd023bd0a9b4d0253bdc755103253c": {
43+
"label": null,
44+
"contract": null,
45+
"balanceDiff": null,
46+
"nonceDiff": null,
47+
"stateDiff": {
48+
"0x0000000000000000000000000000000000000000000000000000000000000005": {
49+
"previousValue": "0x000000000000000000000000000000000000000000000000000000000000002a",
50+
"newValue": "0x000000000000000000000000000000000000000000000000000000000000002b"
51+
}
52+
}
53+
}
54+
}
55+
}
56+
```

src/AaveV3Horizon_ACREDListing_20260217/AaveV3Horizon_ACREDListing_20260217.t.sol

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ abstract contract AaveV3Horizon_ACREDListing_20260217_TestBase is ProtocolV3Hori
1919
_setExpectedConfig();
2020
}
2121

22-
function _pool() internal pure returns (IPool) {
23-
return IPool(AaveV3EthereumHorizonCustom.POOL);
24-
}
25-
2622
function _setExpectedConfig() internal virtual override {
2723
expectedAssetConfig = ExpectedAssetConfig({
2824
underlying: AaveV3EthereumHorizonCustom.ACRED_UNDERLYING,
@@ -75,8 +71,12 @@ contract AaveV3Horizon_ACREDListing_20260217_Test is AaveV3Horizon_ACREDListing_
7571
/**
7672
* @dev executes the generic test suite including e2e and config snapshots
7773
*/
74+
function _executeACREDListing() internal {
75+
_executeHorizonPayload(address(proposal));
76+
}
77+
7878
function test_defaultProposalExecution() public virtual {
79-
defaultTest_v3_3('AaveV3Horizon_ACREDListing_20260217', _pool(), address(proposal));
79+
defaultTest_v3_3('AaveV3Horizon_ACREDListing_20260217', _pool(), _executeACREDListing);
8080
}
8181

8282
/**
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {IPoolDataProvider} from 'aave-v3-origin/contracts/interfaces/IPoolDataProvider.sol';
5+
import {ProtocolV3HorizonTestBase, ReserveConfig} from 'tests/utils/ProtocolV3HorizonTestBase.sol';
6+
import {AaveV3EthereumHorizon, AaveV3EthereumHorizonAssets} from 'aave-address-book-latest/AaveV3EthereumHorizon.sol';
7+
8+
/**
9+
* @dev Test for GHO caps update via multisig transaction.
10+
* command: FOUNDRY_PROFILE=test forge test --match-contract AaveV3Horizon_GHOcaps_20260312_Test -vv
11+
*/
12+
contract AaveV3Horizon_GHOcaps_20260312_Test is ProtocolV3HorizonTestBase {
13+
address internal constant OPS_TARGET = 0x9641d764fc13c8B624c04430C7356C1C7C8102e2;
14+
bytes internal constant OPS_DATA =
15+
hex'8d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001320083cb1b4af26eef6463ac20afbac9c0e2e017202f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044571f03e500000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000000000000002aea5400083cb1b4af26eef6463ac20afbac9c0e2e017202f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044d14a098300000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000000000000002160ec00000000000000000000000000000';
16+
uint256 internal constant OPS_NONCE = 42;
17+
18+
function setUp() public {
19+
vm.createSelectFork(vm.rpcUrl('mainnet'), 24644395);
20+
}
21+
22+
function _executeGHOcapsUpdate() internal {
23+
_executeOpsMultisigTx({to: OPS_TARGET, data: OPS_DATA, operation: 1, nonce: OPS_NONCE});
24+
}
25+
26+
/**
27+
* @dev Full test suite: snapshots, state diff, validations, e2e.
28+
*/
29+
function test_defaultProposalExecution() public {
30+
defaultTest_v3_3('AaveV3Horizon_GHOcaps_20260312', _pool(), _executeGHOcapsUpdate);
31+
}
32+
33+
/**
34+
* @dev Custom before/after assertions for the GHO caps change.
35+
*/
36+
function test_ghoCapsChange() public {
37+
(uint256 borrowCapBefore, uint256 supplyCapBefore) = (
38+
IPoolDataProvider(AaveV3EthereumHorizon.AAVE_PROTOCOL_DATA_PROVIDER).getReserveCaps(
39+
AaveV3EthereumHorizonAssets.GHO_UNDERLYING
40+
)
41+
);
42+
43+
assertEq(borrowCapBefore, 43_000_000, 'Borrow cap before');
44+
assertEq(supplyCapBefore, 55_000_000, 'Supply cap before');
45+
46+
_executeGHOcapsUpdate();
47+
48+
(uint256 borrowCapAfter, uint256 supplyCapAfter) = (
49+
IPoolDataProvider(AaveV3EthereumHorizon.AAVE_PROTOCOL_DATA_PROVIDER).getReserveCaps(
50+
AaveV3EthereumHorizonAssets.GHO_UNDERLYING
51+
)
52+
);
53+
54+
assertEq(borrowCapAfter, 35_000_000, 'Borrow cap after');
55+
assertEq(supplyCapAfter, 45_000_000, 'Supply cap after');
56+
}
57+
}

src/dependencies/ISafeAccount.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
interface ISafeAccount {
5+
function execTransaction(
6+
address to,
7+
uint256 value,
8+
bytes calldata data,
9+
uint8 operation,
10+
uint256 safeTxGas,
11+
uint256 baseGas,
12+
uint256 gasPrice,
13+
address gasToken,
14+
address payable refundReceiver,
15+
bytes memory signatures
16+
) external payable returns (bool success);
17+
function getOwners() external view returns (address[] memory);
18+
}

tests/utils/ProtocolV3HorizonTestBase.sol

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {AaveV3EthereumHorizonCustom} from 'src/utils/AaveV3EthereumHorizonCustom
1212
import {HorizonRwaWhitelistHelper} from 'tests/utils/HorizonRwaWhitelistHelper.sol';
1313
import {HorizonConfigAssertionHelper} from 'tests/utils/HorizonConfigAssertionHelper.sol';
1414
import {Errors} from 'src/dependencies/Errors.sol';
15+
import {ISafeAccount} from 'src/dependencies/ISafeAccount.sol';
1516

1617
/**
1718
* @dev Adapted from ProtocolV3TestBase for the Horizon market (currently at Aave v3.3).
@@ -45,19 +46,22 @@ abstract contract ProtocolV3HorizonTestBase is
4546
* - diffing the config
4647
* - checking if the changes are plausible (no conflicting config changes etc)
4748
* - running an e2e testsuite over all assets
49+
* @param reportName the name of the report
50+
* @param pool the pool that should be tested
51+
* @param executeTx the function that will execute the transaction that is being tested
4852
*/
4953
function defaultTest_v3_3(
5054
string memory reportName,
5155
IPool pool,
52-
address payload
53-
) public returns (ReserveConfig[] memory, ReserveConfig[] memory) {
56+
function() internal executeTx
57+
) internal returns (ReserveConfig[] memory, ReserveConfig[] memory) {
5458
string memory beforeString = string(abi.encodePacked(reportName, '_before'));
5559
ReserveConfig[] memory configBefore = createConfigurationSnapshot(beforeString, pool);
5660

5761
uint256 startGas = gasleft();
5862

5963
vm.startStateDiffRecording();
60-
_executeHorizonPayload(payload);
64+
executeTx(); // execute the transaction that is being tested
6165
string memory rawDiff = vm.getStateDiffJson();
6266

6367
uint256 gasUsed = startGas - gasleft();
@@ -278,6 +282,10 @@ abstract contract ProtocolV3HorizonTestBase is
278282
}
279283
}
280284

285+
function _pool() internal pure returns (IPool) {
286+
return IPool(AaveV3EthereumHorizonCustom.POOL);
287+
}
288+
281289
/**
282290
* @dev Execute a Horizon payload through the real executor path.
283291
* HORIZON_EMERGENCY calls HORIZON_EXECUTOR.executeTransaction() which delegatecalls
@@ -490,4 +498,96 @@ abstract contract ProtocolV3HorizonTestBase is
490498
}
491499

492500
function _setExpectedConfig() internal virtual {}
501+
502+
// ---------------------------------------------------------------------------
503+
// Gnosis Safe multisig transaction helpers
504+
// ---------------------------------------------------------------------------
505+
506+
/**
507+
* @dev Execute a transaction as the HORIZON_EMERGENCY multisig.
508+
* @param to The `to` address of the Safe transaction.
509+
* @param data The calldata of the Safe transaction.
510+
* @param operation 0 = call, 1 = delegatecall.
511+
*/
512+
function _executeEmergencyMultisigTx(
513+
address to,
514+
bytes memory data,
515+
uint8 operation,
516+
uint256 nonce
517+
) internal {
518+
_executeSafeTx({
519+
safe: AaveV3EthereumHorizonCustom.HORIZON_EMERGENCY,
520+
to: to,
521+
data: data,
522+
operation: operation,
523+
nonce: nonce
524+
});
525+
}
526+
527+
/**
528+
* @dev Execute a transaction as the HORIZON_OPS multisig.
529+
* @param to The `to` address of the Safe transaction.
530+
* @param data The calldata of the Safe transaction.
531+
* @param operation 0 = call, 1 = delegatecall.
532+
* @param nonce The Safe nonce to set before execution.
533+
*/
534+
function _executeOpsMultisigTx(
535+
address to,
536+
bytes memory data,
537+
uint8 operation,
538+
uint256 nonce
539+
) internal {
540+
_executeSafeTx({
541+
safe: AaveV3EthereumHorizonCustom.HORIZON_OPS,
542+
to: to,
543+
data: data,
544+
operation: operation,
545+
nonce: nonce
546+
});
547+
}
548+
549+
/**
550+
* @dev Core Safe execution: sets threshold to 1 and nonce, picks the first owner,
551+
* and calls execTransaction with a minimal pre-approved signature.
552+
*/
553+
function _executeSafeTx(
554+
address safe,
555+
address to,
556+
bytes memory data,
557+
uint8 operation,
558+
uint256 nonce
559+
) internal {
560+
// reset signer threshold to 1 (slot 4) and set nonce (slot 5)
561+
vm.store(safe, bytes32(uint256(4)), bytes32(uint256(1)));
562+
vm.store(safe, bytes32(uint256(5)), bytes32(nonce));
563+
address signer = ISafeAccount(safe).getOwners()[0];
564+
565+
vm.prank(signer);
566+
(bool ok, bytes memory ret) = safe.call(
567+
abi.encodeCall(
568+
ISafeAccount.execTransaction,
569+
(
570+
to, // to
571+
0, // value
572+
data,
573+
operation,
574+
0, // safeTxGas
575+
0, // baseGas
576+
0, // gasPrice
577+
address(0), // gasToken
578+
payable(address(0)), // refundReceiver
579+
abi.encodePacked(bytes32(uint256(uint160(signer))), bytes32(0), uint8(1)) // r, s, v
580+
)
581+
)
582+
);
583+
584+
if (!ok) {
585+
if (ret.length > 0) {
586+
assembly {
587+
revert(add(ret, 32), mload(ret))
588+
}
589+
}
590+
revert('_executeSafeTx: unknown error');
591+
}
592+
}
493593
}

0 commit comments

Comments
 (0)