-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Add mGlobal Listing Payload #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
07fda6b
758a11d
f61ee67
2ba4185
d120486
2c421be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol'; | ||
| import {AaveV3Horizon_mGLOBALListing_20260616} from './AaveV3Horizon_mGLOBALListing_20260616.sol'; | ||
|
|
||
| /** | ||
| * @dev Deploy Horizon mGLOBAL Listing payload. | ||
| * deploy-command: make deploy-payload | ||
| */ | ||
| contract DeployEthereum is EthereumScript { | ||
| function run() external broadcast { | ||
| new AaveV3Horizon_mGLOBALListing_20260616(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {AaveV3EthereumHorizonCustom} from 'src/utils/AaveV3EthereumHorizonCustom.sol'; | ||
| import {AaveV3EthereumHorizonAssets} from 'aave-address-book-latest/AaveV3EthereumHorizon.sol'; | ||
| import {AaveV3PayloadHorizonEthereum} from 'src/utils/AaveV3PayloadHorizonEthereum.sol'; | ||
| import {IAaveV3ConfigEngine as IEngine} from 'aave-v3-origin/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol'; | ||
| import {EngineFlags} from 'aave-v3-origin/contracts/extensions/v3-config-engine/EngineFlags.sol'; | ||
|
|
||
| /** | ||
| * @title Horizon Listing — mGLOBAL | ||
| * @author Aave Labs | ||
| * @dev Lists mGLOBAL (Midas Global Diversified Alternative Debt Fund) as an RWA collateral asset | ||
| * on the Horizon pool. The default reserve config uses near-zero LTV/LT so that borrowing | ||
| * against mGLOBAL outside eMode is negligible; an mGLOBAL/stablecoins eMode (USDC, RLUSD) | ||
| * provides the intended collateral parameters. | ||
| */ | ||
| contract AaveV3Horizon_mGLOBALListing_20260616 is AaveV3PayloadHorizonEthereum { | ||
| address public constant MGLOBAL = AaveV3EthereumHorizonCustom.MGLOBAL_UNDERLYING; | ||
| address public constant MGLOBAL_PRICE_FEED = AaveV3EthereumHorizonCustom.MGLOBAL_PRICE_FEED; | ||
| uint8 public constant MGLOBAL_EMODE_CATEGORY = 5; | ||
|
|
||
| function newListingsCustom() | ||
| public | ||
| pure | ||
| override | ||
| returns (IEngine.ListingWithCustomImpl[] memory) | ||
| { | ||
| IEngine.ListingWithCustomImpl[] memory listingsCustom = new IEngine.ListingWithCustomImpl[](1); | ||
|
|
||
| listingsCustom[0] = IEngine.ListingWithCustomImpl( | ||
| IEngine.Listing({ | ||
| asset: MGLOBAL, | ||
| assetSymbol: 'mGLOBAL', | ||
| priceFeed: MGLOBAL_PRICE_FEED, | ||
| rateStrategyParams: AaveV3EthereumHorizonCustom.defaultRwaInterestRateInputData(), | ||
| enabledToBorrow: EngineFlags.DISABLED, | ||
| borrowableInIsolation: EngineFlags.DISABLED, | ||
| withSiloedBorrowing: EngineFlags.DISABLED, | ||
| flashloanable: EngineFlags.DISABLED, | ||
| ltv: 5, | ||
| liqThreshold: 10, | ||
| liqBonus: 6_00, | ||
| reserveFactor: EngineFlags.KEEP_CURRENT, | ||
| supplyCap: 50_000_000, | ||
| borrowCap: 0, | ||
| debtCeiling: 0, | ||
| liqProtocolFee: 0 | ||
|
miguelmtzinf marked this conversation as resolved.
|
||
| }), | ||
| IEngine.TokenImplementations({ | ||
| aToken: AaveV3EthereumHorizonCustom.RWA_A_TOKEN_IMPL, | ||
| vToken: AaveV3EthereumHorizonCustom.DEFAULT_VARIABLE_DEBT_TOKEN_IMPL | ||
| }) | ||
| ); | ||
|
|
||
| return listingsCustom; | ||
| } | ||
|
|
||
| function assetsEModeUpdates() public pure override returns (IEngine.AssetEModeUpdate[] memory) { | ||
| IEngine.AssetEModeUpdate[] memory assetsEMode = new IEngine.AssetEModeUpdate[](3); | ||
|
|
||
| // mGLOBAL as collateral in the stablecoins eMode | ||
| assetsEMode[0] = IEngine.AssetEModeUpdate({ | ||
| asset: MGLOBAL, | ||
| eModeCategory: MGLOBAL_EMODE_CATEGORY, | ||
| collateral: EngineFlags.ENABLED, | ||
| borrowable: EngineFlags.DISABLED | ||
| }); | ||
|
|
||
| // USDC as borrowable in the stablecoins eMode | ||
| assetsEMode[1] = IEngine.AssetEModeUpdate({ | ||
| asset: AaveV3EthereumHorizonAssets.USDC_UNDERLYING, | ||
| eModeCategory: MGLOBAL_EMODE_CATEGORY, | ||
| collateral: EngineFlags.DISABLED, | ||
| borrowable: EngineFlags.ENABLED | ||
| }); | ||
|
|
||
| // RLUSD as borrowable in the stablecoins eMode | ||
| assetsEMode[2] = IEngine.AssetEModeUpdate({ | ||
| asset: AaveV3EthereumHorizonAssets.RLUSD_UNDERLYING, | ||
| eModeCategory: MGLOBAL_EMODE_CATEGORY, | ||
| collateral: EngineFlags.DISABLED, | ||
| borrowable: EngineFlags.ENABLED | ||
| }); | ||
|
|
||
| return assetsEMode; | ||
| } | ||
|
|
||
| function eModeCategoriesUpdates() | ||
| public | ||
| pure | ||
| override | ||
| returns (IEngine.EModeCategoryUpdate[] memory) | ||
| { | ||
| IEngine.EModeCategoryUpdate[] memory eModeCategories = new IEngine.EModeCategoryUpdate[](1); | ||
|
|
||
| // mGLOBAL Stablecoins | ||
| eModeCategories[0] = IEngine.EModeCategoryUpdate({ | ||
| eModeCategory: MGLOBAL_EMODE_CATEGORY, | ||
| ltv: 75_00, | ||
| liqThreshold: 80_00, | ||
| liqBonus: 6_00, | ||
|
miguelmtzinf marked this conversation as resolved.
|
||
| label: 'mGLOBAL Stablecoins' | ||
| }); | ||
|
|
||
| return eModeCategories; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {IPool} from 'aave-v3-origin/contracts/interfaces/IPool.sol'; | ||
| import {IERC20} from 'aave-v3-origin/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; | ||
| import {IDefaultInterestRateStrategyV2} from 'aave-v3-origin/contracts/interfaces/IDefaultInterestRateStrategyV2.sol'; | ||
| import {Errors} from 'src/dependencies/Errors.sol'; | ||
| import {ProtocolV3HorizonTestBase, ReserveConfig} from 'tests/utils/ProtocolV3HorizonTestBase.sol'; | ||
| import {HorizonConfigAssertionHelper} from 'tests/utils/HorizonConfigAssertionHelper.sol'; | ||
| import {AaveV3Horizon_mGLOBALListing_20260616} from 'src/AaveV3Horizon_mGLOBALListing_20260616/AaveV3Horizon_mGLOBALListing_20260616.sol'; | ||
| import {AaveV3EthereumHorizonCustom} from 'src/utils/AaveV3EthereumHorizonCustom.sol'; | ||
| import {AaveV3EthereumHorizonAssets} from 'aave-address-book-latest/AaveV3EthereumHorizon.sol'; | ||
|
|
||
| abstract contract AaveV3Horizon_mGLOBALListing_20260616_TestBase is ProtocolV3HorizonTestBase { | ||
| AaveV3Horizon_mGLOBALListing_20260616 internal proposal; | ||
|
|
||
| ExpectedAssetConfig internal expectedAssetConfig; | ||
| ExpectedEModeConfig internal expectedEModeConfig; | ||
|
|
||
| function setUp() public virtual { | ||
| _setExpectedConfig(); | ||
| } | ||
|
|
||
| function _setExpectedConfig() internal virtual override { | ||
| expectedAssetConfig = ExpectedAssetConfig({ | ||
| underlying: AaveV3EthereumHorizonCustom.MGLOBAL_UNDERLYING, | ||
| isRwa: true, | ||
| oracle: AaveV3EthereumHorizonCustom.MGLOBAL_PRICE_FEED, | ||
| aTokenName: 'Aave Horizon RWA mGLOBAL', | ||
| aTokenSymbol: 'aHorRwamGLOBAL', | ||
| variableDebtTokenName: 'Aave Horizon RWA Variable Debt mGLOBAL', | ||
| variableDebtTokenSymbol: 'variableDebtHorRwamGLOBAL', | ||
| supplyCap: 50_000_000, | ||
| borrowCap: 0, | ||
| reserveFactor: 0, | ||
| borrowingEnabled: false, | ||
| flashloanable: false, | ||
| ltv: 5, | ||
| liquidationThreshold: 10, | ||
| liquidationBonus: 100_00 + 6_00, | ||
| debtCeiling: 0, | ||
| liqProtocolFee: 0, | ||
| rateData: IDefaultInterestRateStrategyV2.InterestRateData({ | ||
| optimalUsageRatio: 99_00, | ||
| baseVariableBorrowRate: 0, | ||
| variableRateSlope1: 0, | ||
| variableRateSlope2: 0 | ||
| }) | ||
| }); | ||
| expectedEModeConfig = ExpectedEModeConfig({ | ||
| eModeCategory: 5, | ||
| ltv: 75_00, | ||
| liquidationThreshold: 80_00, | ||
| liquidationBonus: 100_00 + 6_00, | ||
| label: 'mGLOBAL Stablecoins', | ||
| collateralAssets: _toAddressArray(AaveV3EthereumHorizonCustom.MGLOBAL_UNDERLYING), | ||
| borrowableAssets: _toAddressArray( | ||
| AaveV3EthereumHorizonAssets.USDC_UNDERLYING, | ||
| AaveV3EthereumHorizonAssets.RLUSD_UNDERLYING | ||
| ) | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @dev Test for Horizon mGLOBAL listing (pre-execution). | ||
| * command: FOUNDRY_PROFILE=test forge test --match-contract AaveV3Horizon_mGLOBALListing_20260616_Test -vv | ||
| */ | ||
| contract AaveV3Horizon_mGLOBALListing_20260616_Test is | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the abstract then test contract split needed here ?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bc below we also have a fork test for post tx execution, follows same pattern we used for other payloads here as well
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should switch to permissioned payloads controller model asap, this separate test is v stupid lol and ppc absorbs most footguns w current flow |
||
| AaveV3Horizon_mGLOBALListing_20260616_TestBase | ||
| { | ||
| function setUp() public virtual override { | ||
| super.setUp(); | ||
| vm.createSelectFork(vm.rpcUrl('mainnet'), 25352313); | ||
| proposal = new AaveV3Horizon_mGLOBALListing_20260616(); | ||
| } | ||
|
|
||
| /** | ||
| * @dev executes the generic test suite including e2e and config snapshots | ||
| */ | ||
| function _executeMGLOBALListing() internal { | ||
| _executeHorizonPayload(address(proposal)); | ||
| } | ||
|
|
||
| function test_defaultProposalExecution() public virtual { | ||
| defaultTest_v3_3('AaveV3Horizon_mGLOBALListing_20260616', _pool(), _executeMGLOBALListing); | ||
| } | ||
|
|
||
| /** | ||
| * @dev verifies the exact config values set by the mGLOBAL listing payload | ||
| */ | ||
| function test_mglobalConfig() public virtual { | ||
| IPool pool = _pool(); | ||
| uint8 eModeCategory = proposal.MGLOBAL_EMODE_CATEGORY(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you also test this is the _nextEmodeCategory() bc 5 is hardcoded right now
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would rather fetch it dynamically in the proposal tbh but nbd
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's not I think, 5 is a deprecate emode
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i confirm we must use 5 to replace existing emode that was deprecated in a previous gov action |
||
|
|
||
| // the eMode category must be unused before execution (no collateral/borrowable assets, no label) | ||
| assertEq(pool.getEModeCategoryCollateralBitmap(eModeCategory), 0, 'emode collateral not empty'); | ||
| assertEq(pool.getEModeCategoryBorrowableBitmap(eModeCategory), 0, 'emode borrowable not empty'); | ||
| assertEq(bytes(pool.getEModeCategoryLabel(eModeCategory)).length, 0, 'emode label not empty'); | ||
|
|
||
| // execute payload | ||
| _executeHorizonPayload(address(proposal)); | ||
|
|
||
| // verify mGLOBAL asset config | ||
| _assertAssetConfig(pool, expectedAssetConfig); | ||
|
|
||
| // verify mGLOBAL Stablecoins eMode | ||
| _assertEModeConfig(pool, expectedEModeConfig); | ||
| } | ||
|
|
||
| /** | ||
| * @dev mGLOBAL's near-zero default params must prevent borrowing a meaningful amount of a | ||
| * stablecoin against it outside of the eMode. | ||
| */ | ||
| function test_mglobalDefaultModeBorrowReverts() public { | ||
| IPool pool = _pool(); | ||
| _executeHorizonPayload(address(proposal)); | ||
|
|
||
| _initTestActors(); | ||
| _whitelistRwaUsers(_testActorsArray()); | ||
| _whitelistRwaPool(pool); | ||
|
|
||
| address user = regularCollateralSupplier; | ||
| address mGlobal = AaveV3EthereumHorizonCustom.MGLOBAL_UNDERLYING; | ||
| uint256 supplyAmount = 100_000e18; | ||
|
|
||
| // supply mGLOBAL as collateral in the default eMode (category 0) | ||
| deal(mGlobal, user, supplyAmount); | ||
| vm.startPrank(user); | ||
| IERC20(mGlobal).approve(address(pool), supplyAmount); | ||
| pool.supply(mGlobal, supplyAmount, user, 0); | ||
|
|
||
| // borrowing a stablecoin against it in default mode must revert (near-zero default LTV) | ||
| vm.expectRevert(bytes(Errors.COLLATERAL_CANNOT_COVER_NEW_BORROW)); | ||
| pool.borrow(AaveV3EthereumHorizonAssets.USDC_UNDERLYING, 10_000e6, 2, 0, user); | ||
|
|
||
| vm.expectRevert(bytes(Errors.COLLATERAL_CANNOT_COVER_NEW_BORROW)); | ||
| pool.borrow(AaveV3EthereumHorizonAssets.GHO_UNDERLYING, 10_000e18, 2, 0, user); | ||
| vm.stopPrank(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @dev Post-execution fork test. Run after the payload has been executed on mainnet | ||
| * to validate the live state matches expected config and run full E2E. | ||
| * command: FOUNDRY_PROFILE=test forge test --match-contract AaveV3Horizon_mGLOBALListing_20260616_PostExecution_Test -vv | ||
| */ | ||
| contract AaveV3Horizon_mGLOBALListing_20260616_PostExecution_Test is | ||
| AaveV3Horizon_mGLOBALListing_20260616_TestBase | ||
| { | ||
| function setUp() public virtual override { | ||
| super.setUp(); | ||
| vm.createSelectFork(vm.rpcUrl('mainnet'), 25380179); | ||
| } | ||
|
|
||
| function test_defaultProposalExecution() public { | ||
| defaultTest_v3_3_postExecution(_pool()); | ||
| } | ||
|
|
||
| function test_mglobalConfig() public { | ||
| _assertAssetConfig(_pool(), expectedAssetConfig); | ||
| _assertEModeConfig(_pool(), expectedEModeConfig); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,4 +54,10 @@ library AaveV3EthereumHorizonCustom { | |
| address public constant ACRED_UNDERLYING = 0x17418038ecF73BA4026c4f428547BF099706F27B; | ||
| // https://etherscan.io/address/0x60AEd7d20AC6328f7BA771aD58931c996aff30E8 | ||
| address public constant ACRED_PRICE_FEED = 0x60AEd7d20AC6328f7BA771aD58931c996aff30E8; | ||
|
|
||
| // https://etherscan.io/address/0x7433806912Eae67919e66aea853d46Fa0aef98A8 | ||
| address public constant MGLOBAL_UNDERLYING = 0x7433806912Eae67919e66aea853d46Fa0aef98A8; | ||
| // TODO: replace with the LlamaGuard-bounded NAV feed once deployed; this is the unbounded Chainlink feed | ||
| // https://etherscan.io/address/0xB92A68763b2F83e094595c7B41a7FB9D0f8Da193 | ||
| address public constant MGLOBAL_PRICE_FEED = 0xB92A68763b2F83e094595c7B41a7FB9D0f8Da193; | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ import {AaveV3EthereumHorizonAssets} from 'aave-address-book-latest/AaveV3Ethere | |
| * - Centrifuge (JTRSY, JAAA) — RestrictionManager.endorse | ||
| * - Circle/USYC — RolesAuthority.setUserRole | ||
| * - Securitize (VBILL, ACRED) — RegistryService.addWallet | ||
| * - Midas (mGLOBAL) — MidasAccessControl GREENLISTED_ROLE | ||
| * | ||
| * Override `_whitelistRwaActors` in concrete tests to add whitelisting for | ||
| * newly listed assets. | ||
|
|
@@ -37,6 +38,14 @@ abstract contract HorizonRwaWhitelistHelper is Test { | |
| string internal constant VBILL_SECURITIZE_FUND_ID = 'f27e20ca73314651b387da0aa9116f30'; | ||
| string internal constant ACRED_SECURITIZE_FUND_ID = '69023a78d57776eca9542d33'; | ||
|
|
||
| // ── Midas (mGLOBAL) ────────────────────────────────────────────────── | ||
| // Shared Midas access control (mGLOBAL.accessControl()). Transfers require the | ||
| // sender and recipient to hold mGLOBAL's product-specific greenlist role. | ||
| address internal constant MIDAS_ACCESS_CONTROL = 0x0312A9D1Ff2372DDEdCBB21e4B6389aFc919aC4B; | ||
| bytes32 internal constant MGLOBAL_GREENLISTED_ROLE = keccak256('M_GLOBAL_GREENLISTED_ROLE'); | ||
| // Slot of the OZ `_roles` mapping (role => account => bool) in MidasAccessControl. | ||
| uint256 internal constant MIDAS_ROLES_SLOT = 101; | ||
|
|
||
| // ── Whale addresses for tokens incompatible with foundry `deal` ──── | ||
| // Securitize DS-protocol tokens store balances in an external data store, | ||
| // so cannot use native foundry deal. Transfer from a real holder instead. | ||
|
|
@@ -61,6 +70,8 @@ abstract contract HorizonRwaWhitelistHelper is Test { | |
| _whitelistVbillRwa(actors[i]); | ||
| // Securitize (ACRED) — only if listed (aToken exists) | ||
| _whitelistAcredRwa(actors[i]); | ||
| // Midas (mGLOBAL) | ||
| _whitelistMidasRwa(actors[i]); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -79,6 +90,8 @@ abstract contract HorizonRwaWhitelistHelper is Test { | |
| _whitelistVbillRwa(_requireAToken(pool, AaveV3EthereumHorizonAssets.VBILL_UNDERLYING)); | ||
| // Securitize (ACRED) | ||
| _whitelistAcredRwa(_requireAToken(pool, AaveV3EthereumHorizonCustom.ACRED_UNDERLYING)); | ||
| // Midas (mGLOBAL) | ||
| _whitelistMidasRwa(_requireAToken(pool, AaveV3EthereumHorizonCustom.MGLOBAL_UNDERLYING)); | ||
| } | ||
|
|
||
| function _requireAToken(IPool pool, address underlying) internal view returns (address aToken) { | ||
|
|
@@ -176,6 +189,27 @@ abstract contract HorizonRwaWhitelistHelper is Test { | |
| ); | ||
| } | ||
|
|
||
| /// @dev Midas whitelisting: grants mGLOBAL's greenlist role on the shared MidasAccessControl. | ||
| function _whitelistMidasRwa(address addressToWhitelist) internal { | ||
| bytes32 slot = keccak256( | ||
| abi.encode( | ||
| addressToWhitelist, | ||
| keccak256(abi.encode(MGLOBAL_GREENLISTED_ROLE, MIDAS_ROLES_SLOT)) | ||
| ) | ||
| ); | ||
| vm.store(MIDAS_ACCESS_CONTROL, slot, bytes32(uint256(1))); | ||
|
|
||
| // Verify whitelisted | ||
| (bool success, bytes memory data) = MIDAS_ACCESS_CONTROL.call( | ||
| abi.encodeWithSignature( | ||
| 'hasRole(bytes32,address)', | ||
| MGLOBAL_GREENLISTED_ROLE, | ||
| addressToWhitelist | ||
| ) | ||
| ); | ||
| require(success && abi.decode(data, (bool)), 'Midas: address not greenlisted'); | ||
|
Comment on lines
+203
to
+210
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not do a high level call with a casted interface if we're reverting on failure anyway, no need to do
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is how we do it in this entire file. The reason is becase I'd thought it's cleaner like this than defining specific interfaces for each issuer, wdyt
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hasRole is in IAccessManager from oz, nothing custom needed here in general it's totally fine to add vender specific interfaces, proposals repo (v3) has always done that in src/interfaces
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would prefer to add those interfaces actually, trimmed from source with their natspec over this in general also
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wdyt |
||
| } | ||
|
|
||
| /** | ||
| * @dev Generic Securitize whitelisting via RegistryService.addWallet. | ||
| * Reuse for any Securitize DS-protocol token (VBILL, ACRED, etc.) by supplying | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.