Skip to content

Commit 07fda6b

Browse files
committed
feat: initial mGlobal payload
1 parent f336630 commit 07fda6b

8 files changed

Lines changed: 508 additions & 1 deletion

diffs/AaveV3Horizon_JAAASupplyCap_20260518_before_AaveV3Horizon_JAAASupplyCap_20260518_after.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
},
2424
"raw": {
2525
"0xae05cd22df81871bc7cc2a04becfb516bfe332c8": {
26-
"label": null,
26+
"label": "AaveV3EthereumHorizon.POOL",
2727
"contract": "lib/aave-umbrella/lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy",
2828
"balanceDiff": null,
2929
"nonceDiff": null,

diffs/AaveV3Horizon_mGLOBALListing_20260616_before_AaveV3Horizon_mGLOBALListing_20260616_after.md

Lines changed: 310 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
5+
import {AaveV3Horizon_mGLOBALListing_20260616} from './AaveV3Horizon_mGLOBALListing_20260616.sol';
6+
7+
/**
8+
* @dev Deploy Horizon mGLOBAL Listing payload.
9+
* deploy-command: make deploy-payload
10+
*/
11+
contract DeployEthereum is EthereumScript {
12+
function run() external broadcast {
13+
new AaveV3Horizon_mGLOBALListing_20260616();
14+
}
15+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {AaveV3EthereumHorizonCustom} from 'src/utils/AaveV3EthereumHorizonCustom.sol';
5+
import {AaveV3PayloadHorizonEthereum} from 'src/utils/AaveV3PayloadHorizonEthereum.sol';
6+
import {IAaveV3ConfigEngine as IEngine} from 'aave-v3-origin/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol';
7+
import {EngineFlags} from 'aave-v3-origin/contracts/extensions/v3-config-engine/EngineFlags.sol';
8+
9+
/**
10+
* @title Horizon Listing — mGLOBAL
11+
* @author Aave Labs
12+
* @dev Lists mGLOBAL (Midas Global Diversified Alternative Debt Fund) as an RWA collateral asset
13+
* on the Horizon pool.
14+
*/
15+
contract AaveV3Horizon_mGLOBALListing_20260616 is AaveV3PayloadHorizonEthereum {
16+
address public constant MGLOBAL = AaveV3EthereumHorizonCustom.MGLOBAL_UNDERLYING;
17+
address public constant MGLOBAL_PRICE_FEED = AaveV3EthereumHorizonCustom.MGLOBAL_PRICE_FEED;
18+
19+
function newListingsCustom()
20+
public
21+
pure
22+
override
23+
returns (IEngine.ListingWithCustomImpl[] memory)
24+
{
25+
IEngine.ListingWithCustomImpl[] memory listingsCustom = new IEngine.ListingWithCustomImpl[](1);
26+
27+
listingsCustom[0] = IEngine.ListingWithCustomImpl(
28+
IEngine.Listing({
29+
asset: MGLOBAL,
30+
assetSymbol: 'mGLOBAL',
31+
priceFeed: MGLOBAL_PRICE_FEED,
32+
rateStrategyParams: AaveV3EthereumHorizonCustom.defaultRwaInterestRateInputData(),
33+
enabledToBorrow: EngineFlags.DISABLED,
34+
borrowableInIsolation: EngineFlags.DISABLED,
35+
withSiloedBorrowing: EngineFlags.DISABLED,
36+
flashloanable: EngineFlags.DISABLED,
37+
ltv: 70_00,
38+
liqThreshold: 75_00,
39+
liqBonus: 6_00,
40+
reserveFactor: EngineFlags.KEEP_CURRENT,
41+
supplyCap: 60_000_000,
42+
borrowCap: 0,
43+
debtCeiling: 0,
44+
liqProtocolFee: 0
45+
}),
46+
IEngine.TokenImplementations({
47+
aToken: AaveV3EthereumHorizonCustom.RWA_A_TOKEN_IMPL,
48+
vToken: AaveV3EthereumHorizonCustom.DEFAULT_VARIABLE_DEBT_TOKEN_IMPL
49+
})
50+
);
51+
52+
return listingsCustom;
53+
}
54+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {IPool} from 'aave-v3-origin/contracts/interfaces/IPool.sol';
5+
import {IDefaultInterestRateStrategyV2} from 'aave-v3-origin/contracts/interfaces/IDefaultInterestRateStrategyV2.sol';
6+
import {ProtocolV3HorizonTestBase, ReserveConfig} from 'tests/utils/ProtocolV3HorizonTestBase.sol';
7+
import {HorizonConfigAssertionHelper} from 'tests/utils/HorizonConfigAssertionHelper.sol';
8+
import {AaveV3Horizon_mGLOBALListing_20260616} from 'src/AaveV3Horizon_mGLOBALListing_20260616/AaveV3Horizon_mGLOBALListing_20260616.sol';
9+
import {AaveV3EthereumHorizonCustom} from 'src/utils/AaveV3EthereumHorizonCustom.sol';
10+
11+
abstract contract AaveV3Horizon_mGLOBALListing_20260616_TestBase is ProtocolV3HorizonTestBase {
12+
AaveV3Horizon_mGLOBALListing_20260616 internal proposal;
13+
14+
ExpectedAssetConfig internal expectedAssetConfig;
15+
16+
function setUp() public virtual {
17+
_setExpectedConfig();
18+
}
19+
20+
function _setExpectedConfig() internal virtual override {
21+
expectedAssetConfig = ExpectedAssetConfig({
22+
underlying: AaveV3EthereumHorizonCustom.MGLOBAL_UNDERLYING,
23+
isRwa: true,
24+
oracle: AaveV3EthereumHorizonCustom.MGLOBAL_PRICE_FEED,
25+
aTokenName: 'Aave Horizon RWA mGLOBAL',
26+
aTokenSymbol: 'aHorRwamGLOBAL',
27+
variableDebtTokenName: 'Aave Horizon RWA Variable Debt mGLOBAL',
28+
variableDebtTokenSymbol: 'variableDebtHorRwamGLOBAL',
29+
supplyCap: 60_000_000, // keep in sync with payload supplyCap
30+
borrowCap: 0,
31+
reserveFactor: 0,
32+
borrowingEnabled: false,
33+
flashloanable: false,
34+
ltv: 70_00,
35+
liquidationThreshold: 75_00,
36+
liquidationBonus: 100_00 + 6_00,
37+
debtCeiling: 0,
38+
liqProtocolFee: 0,
39+
rateData: IDefaultInterestRateStrategyV2.InterestRateData({
40+
optimalUsageRatio: 99_00,
41+
baseVariableBorrowRate: 0,
42+
variableRateSlope1: 0,
43+
variableRateSlope2: 0
44+
})
45+
});
46+
}
47+
}
48+
49+
/**
50+
* @dev Test for Horizon mGLOBAL listing (pre-execution).
51+
* command: FOUNDRY_PROFILE=test forge test --match-contract AaveV3Horizon_mGLOBALListing_20260616_Test -vv
52+
*/
53+
contract AaveV3Horizon_mGLOBALListing_20260616_Test is
54+
AaveV3Horizon_mGLOBALListing_20260616_TestBase
55+
{
56+
function setUp() public virtual override {
57+
super.setUp();
58+
vm.createSelectFork(vm.rpcUrl('mainnet')); // TODO: pin to a block once the asset/feed are deployed
59+
proposal = new AaveV3Horizon_mGLOBALListing_20260616();
60+
}
61+
62+
/**
63+
* @dev executes the generic test suite including e2e and config snapshots
64+
*/
65+
function _executeMGLOBALListing() internal {
66+
_executeHorizonPayload(address(proposal));
67+
}
68+
69+
function test_defaultProposalExecution() public virtual {
70+
defaultTest_v3_3('AaveV3Horizon_mGLOBALListing_20260616', _pool(), _executeMGLOBALListing);
71+
}
72+
73+
/**
74+
* @dev verifies the exact config values set by the mGLOBAL listing payload
75+
*/
76+
function test_mglobalConfig() public virtual {
77+
IPool pool = _pool();
78+
79+
// execute payload
80+
_executeHorizonPayload(address(proposal));
81+
82+
// verify mGLOBAL asset config
83+
_assertAssetConfig(pool, expectedAssetConfig);
84+
}
85+
}

src/utils/AaveV3EthereumHorizonCustom.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,10 @@ library AaveV3EthereumHorizonCustom {
5454
address public constant ACRED_UNDERLYING = 0x17418038ecF73BA4026c4f428547BF099706F27B;
5555
// https://etherscan.io/address/0x60AEd7d20AC6328f7BA771aD58931c996aff30E8
5656
address public constant ACRED_PRICE_FEED = 0x60AEd7d20AC6328f7BA771aD58931c996aff30E8;
57+
58+
// https://etherscan.io/address/0x7433806912Eae67919e66aea853d46Fa0aef98A8
59+
address public constant MGLOBAL_UNDERLYING = 0x7433806912Eae67919e66aea853d46Fa0aef98A8;
60+
// TODO: replace with the LlamaGuard-bounded NAV feed once deployed; this is the unbounded Chainlink feed
61+
// https://etherscan.io/address/0xB92A68763b2F83e094595c7B41a7FB9D0f8Da193
62+
address public constant MGLOBAL_PRICE_FEED = 0xB92A68763b2F83e094595c7B41a7FB9D0f8Da193;
5763
}

tests/utils/HorizonConfigAssertionHelper.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,9 @@ abstract contract HorizonConfigAssertionHelper is Test {
406406
if (underlying == AaveV3EthereumHorizonCustom.ACRED_UNDERLYING) {
407407
return AaveV3EthereumHorizonCustom.ACRED_PRICE_FEED;
408408
}
409+
if (underlying == AaveV3EthereumHorizonCustom.MGLOBAL_UNDERLYING) {
410+
return AaveV3EthereumHorizonCustom.MGLOBAL_PRICE_FEED;
411+
}
409412
revert('_expectedPriceFeed: unknown underlying');
410413
}
411414

tests/utils/HorizonRwaWhitelistHelper.sol

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {AaveV3EthereumHorizonAssets} from 'aave-address-book-latest/AaveV3Ethere
1414
* - Centrifuge (JTRSY, JAAA) — RestrictionManager.endorse
1515
* - Circle/USYC — RolesAuthority.setUserRole
1616
* - Securitize (VBILL, ACRED) — RegistryService.addWallet
17+
* - Midas (mGLOBAL) — MidasAccessControl GREENLISTED_ROLE
1718
*
1819
* Override `_whitelistRwaActors` in concrete tests to add whitelisting for
1920
* newly listed assets.
@@ -37,6 +38,14 @@ abstract contract HorizonRwaWhitelistHelper is Test {
3738
string internal constant VBILL_SECURITIZE_FUND_ID = 'f27e20ca73314651b387da0aa9116f30';
3839
string internal constant ACRED_SECURITIZE_FUND_ID = '69023a78d57776eca9542d33';
3940

41+
// ── Midas (mGLOBAL) ──────────────────────────────────────────────────
42+
// Shared Midas access control (mGLOBAL.accessControl()). Transfers require the
43+
// sender and recipient to hold mGLOBAL's product-specific greenlist role.
44+
address internal constant MIDAS_ACCESS_CONTROL = 0x0312A9D1Ff2372DDEdCBB21e4B6389aFc919aC4B;
45+
bytes32 internal constant MGLOBAL_GREENLISTED_ROLE = keccak256('M_GLOBAL_GREENLISTED_ROLE');
46+
// Slot of the OZ `_roles` mapping (role => account => bool) in MidasAccessControl.
47+
uint256 internal constant MIDAS_ROLES_SLOT = 101;
48+
4049
// ── Whale addresses for tokens incompatible with foundry `deal` ────
4150
// Securitize DS-protocol tokens store balances in an external data store,
4251
// so cannot use native foundry deal. Transfer from a real holder instead.
@@ -61,6 +70,8 @@ abstract contract HorizonRwaWhitelistHelper is Test {
6170
_whitelistVbillRwa(actors[i]);
6271
// Securitize (ACRED) — only if listed (aToken exists)
6372
_whitelistAcredRwa(actors[i]);
73+
// Midas (mGLOBAL)
74+
_whitelistMidasRwa(actors[i]);
6475
}
6576
}
6677

@@ -79,6 +90,8 @@ abstract contract HorizonRwaWhitelistHelper is Test {
7990
_whitelistVbillRwa(_requireAToken(pool, AaveV3EthereumHorizonAssets.VBILL_UNDERLYING));
8091
// Securitize (ACRED)
8192
_whitelistAcredRwa(_requireAToken(pool, AaveV3EthereumHorizonCustom.ACRED_UNDERLYING));
93+
// Midas (mGLOBAL)
94+
_whitelistMidasRwa(_requireAToken(pool, AaveV3EthereumHorizonCustom.MGLOBAL_UNDERLYING));
8295
}
8396

8497
function _requireAToken(IPool pool, address underlying) internal view returns (address aToken) {
@@ -176,6 +189,27 @@ abstract contract HorizonRwaWhitelistHelper is Test {
176189
);
177190
}
178191

192+
/// @dev Midas whitelisting: grants mGLOBAL's greenlist role on the shared MidasAccessControl.
193+
function _whitelistMidasRwa(address addressToWhitelist) internal {
194+
bytes32 slot = keccak256(
195+
abi.encode(
196+
addressToWhitelist,
197+
keccak256(abi.encode(MGLOBAL_GREENLISTED_ROLE, MIDAS_ROLES_SLOT))
198+
)
199+
);
200+
vm.store(MIDAS_ACCESS_CONTROL, slot, bytes32(uint256(1)));
201+
202+
// Verify whitelisted
203+
(bool success, bytes memory data) = MIDAS_ACCESS_CONTROL.call(
204+
abi.encodeWithSignature(
205+
'hasRole(bytes32,address)',
206+
MGLOBAL_GREENLISTED_ROLE,
207+
addressToWhitelist
208+
)
209+
);
210+
require(success && abi.decode(data, (bool)), 'Midas: address not greenlisted');
211+
}
212+
179213
/**
180214
* @dev Generic Securitize whitelisting via RegistryService.addWallet.
181215
* Reuse for any Securitize DS-protocol token (VBILL, ACRED, etc.) by supplying

0 commit comments

Comments
 (0)