diff --git a/.openzeppelin/mainnet.json b/.openzeppelin/mainnet.json index 3f1e6fa6..72ceff1d 100644 --- a/.openzeppelin/mainnet.json +++ b/.openzeppelin/mainnet.json @@ -1649,6 +1649,61 @@ "address": "0x7F72AA0339b4944E1A77df847168AE1936c1EBBB", "txHash": "0x874c5ae49ebe8d2db1d9ba4adec60cafcdc283e2058eaf595ca295e61a8991d7", "kind": "transparent" + }, + { + "address": "0x2A2895f343CA5a5E3d81A53e4C959367EB339f11", + "txHash": "0x00b0da8f99b0815fc549893a2fdf7f7c4b0bf2782c4d3810fea84fff40882a82", + "kind": "transparent" + }, + { + "address": "0xFBD70c323238B5EDA94b6DEfFc5dB8c70F1f0e6D", + "txHash": "0x61ec0bda12baee45c917b3ebddb60391d496562f358bbd51562560fb5cf1b935", + "kind": "transparent" + }, + { + "address": "0x04DbaB674457A696cd4884c2E7c8cc6cA3118513", + "txHash": "0xb3784ec11392433943aedbf44a707e572e3b5845957b9dc729ad4e389ac2892f", + "kind": "transparent" + }, + { + "address": "0xD9795cDFb09f1c599e16f6b150f642eC529b7aBB", + "txHash": "0x19f809150ba382e7ca8c418385e6cfd05da55132c170329e7c4cd944b936ca45", + "kind": "transparent" + }, + { + "address": "0x4D88E4a32cf289ECfA6C9303B57aF0b6c132a733", + "txHash": "0xe7dc2d51d77e253662c8345cafa890abd8aad036a75f328dde70b5fc641cd2ae", + "kind": "transparent" + }, + { + "address": "0x2F8bae126b8d416C009b82Ccd60e4f9cAbbd7702", + "txHash": "0xe6c6392811a15dd038b39feeae52e6d548542ba2d1762ba97fb13a6e99a2643d", + "kind": "transparent" + }, + { + "address": "0x8B7B1689396ef4468df1c0faB181D29B47089609", + "txHash": "0x15f56462c56c6978ad1a3b3482f70c13917a28d73bb5a886becc1cee5915344c", + "kind": "transparent" + }, + { + "address": "0xc27Cd7c0203B73933dcbcF7bdB4cC5D7bDA80A64", + "txHash": "0x820baeed6ed592f6ae360378f56bd454a9c1f0092e046f0b9998ae8b70301b3d", + "kind": "transparent" + }, + { + "address": "0xF76653eBc3e47B80e89F0e5b9CF6A5CAaB848946", + "txHash": "0x3dc0765875214cceeda83a56448dbebbd4358479c3ddea0b2099b980612e1452", + "kind": "transparent" + }, + { + "address": "0xE0Dcf20b0460e1f9222528F3997F9D71Ad6375C5", + "txHash": "0x087a6f7a192d1d451567185cfa42605abcfd01c65c214467f4a4e70a2dafc169", + "kind": "transparent" + }, + { + "address": "0x1E9f04408FF38d1FB70CE939fC87D26D61F6F758", + "txHash": "0x122ef1f77608537623efd69a17a490c7dd21691e4887179a2f0111607b2cf9a2", + "kind": "transparent" } ], "impls": { @@ -115871,7 +115926,7 @@ "label": "accessControl", "offset": 2, "slot": "0", - "type": "t_contract(MidasAccessControl)8606", + "type": "t_contract(MidasAccessControl)14032", "contract": "WithMidasAccessControl", "src": "contracts/access/WithMidasAccessControl.sol:24" }, @@ -115967,7 +116022,7 @@ "label": "currentRequestId", "offset": 0, "slot": "354", - "type": "t_struct(Counter)4184_storage", + "type": "t_struct(Counter)5102_storage", "contract": "ManageableVault", "src": "contracts/abstract/ManageableVault.sol:53" }, @@ -115975,7 +116030,7 @@ "label": "mToken", "offset": 0, "slot": "355", - "type": "t_contract(IMToken)8967", + "type": "t_contract(IMToken)14647", "contract": "ManageableVault", "src": "contracts/abstract/ManageableVault.sol:66" }, @@ -115983,7 +116038,7 @@ "label": "mTokenDataFeed", "offset": 0, "slot": "356", - "type": "t_contract(IDataFeed)8928", + "type": "t_contract(IDataFeed)14354", "contract": "ManageableVault", "src": "contracts/abstract/ManageableVault.sol:71" }, @@ -116055,7 +116110,7 @@ "label": "tokensConfig", "offset": 0, "slot": "366", - "type": "t_mapping(t_address,t_struct(TokenConfig)8980_storage)", + "type": "t_mapping(t_address,t_struct(TokenConfig)14660_storage)", "contract": "ManageableVault", "src": "contracts/abstract/ManageableVault.sol:118" }, @@ -116111,7 +116166,7 @@ "label": "redeemRequests", "offset": 0, "slot": "422", - "type": "t_mapping(t_uint256,t_struct(Request)9248_storage)", + "type": "t_mapping(t_uint256,t_struct(Request)14928_storage)", "contract": "RedemptionVault", "src": "contracts/RedemptionVault.sol:84" }, @@ -116143,7 +116198,7 @@ "label": "redemptionVault", "offset": 0, "slot": "524", - "type": "t_contract(IRedemptionVault)9485", + "type": "t_contract(IRedemptionVault)15165", "contract": "RedemptionVaultWithMToken", "src": "contracts/RedemptionVaultWithMToken.sol:32", "renamedFrom": "mTbillRedemptionVault" @@ -116203,23 +116258,23 @@ "label": "bytes4", "numberOfBytes": "4" }, - "t_contract(IDataFeed)8928": { + "t_contract(IDataFeed)14354": { "label": "contract IDataFeed", "numberOfBytes": "20" }, - "t_contract(IMToken)8967": { + "t_contract(IMToken)14647": { "label": "contract IMToken", "numberOfBytes": "20" }, - "t_contract(IRedemptionVault)9485": { + "t_contract(IRedemptionVault)15165": { "label": "contract IRedemptionVault", "numberOfBytes": "20" }, - "t_contract(MidasAccessControl)8606": { + "t_contract(MidasAccessControl)14032": { "label": "contract MidasAccessControl", "numberOfBytes": "20" }, - "t_enum(RequestStatus)8984": { + "t_enum(RequestStatus)14664": { "label": "enum RequestStatus", "members": ["Pending", "Processed", "Canceled"], "numberOfBytes": "1" @@ -116228,7 +116283,7 @@ "label": "mapping(address => bool)", "numberOfBytes": "32" }, - "t_mapping(t_address,t_struct(TokenConfig)8980_storage)": { + "t_mapping(t_address,t_struct(TokenConfig)14660_storage)": { "label": "mapping(address => struct TokenConfig)", "numberOfBytes": "32" }, @@ -116240,7 +116295,7 @@ "label": "mapping(bytes4 => bool)", "numberOfBytes": "32" }, - "t_mapping(t_uint256,t_struct(Request)9248_storage)": { + "t_mapping(t_uint256,t_struct(Request)14928_storage)": { "label": "mapping(uint256 => struct Request)", "numberOfBytes": "32" }, @@ -116260,7 +116315,7 @@ ], "numberOfBytes": "64" }, - "t_struct(Counter)4184_storage": { + "t_struct(Counter)5102_storage": { "label": "struct Counters.Counter", "members": [ { @@ -116272,7 +116327,7 @@ ], "numberOfBytes": "32" }, - "t_struct(Request)9248_storage": { + "t_struct(Request)14928_storage": { "label": "struct Request", "members": [ { @@ -116289,7 +116344,7 @@ }, { "label": "status", - "type": "t_enum(RequestStatus)8984", + "type": "t_enum(RequestStatus)14664", "offset": 20, "slot": "1" }, @@ -116332,7 +116387,7 @@ ], "numberOfBytes": "64" }, - "t_struct(TokenConfig)8980_storage": { + "t_struct(TokenConfig)14660_storage": { "label": "struct TokenConfig", "members": [ { @@ -117895,9 +117950,2732 @@ } } }, - "4d7d7b7f14efd50fe78672aaf513abaec2615bf3002532b6bfe02eb5aee2194e": { - "address": "0xAa56d3b188Dc10374F20fCe4f71750646D9992Ee", - "txHash": "0x3c51d8fe83dd69ad7aa6f126a172000ec819800f8142648b2576e9bfa8df8621", + "4d7d7b7f14efd50fe78672aaf513abaec2615bf3002532b6bfe02eb5aee2194e": { + "address": "0xAa56d3b188Dc10374F20fCe4f71750646D9992Ee", + "txHash": "0x3c51d8fe83dd69ad7aa6f126a172000ec819800f8142648b2576e9bfa8df8621", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_balances", + "offset": 0, + "slot": "51", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:40" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "52", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:42" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "53", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44" + }, + { + "label": "_name", + "offset": 0, + "slot": "54", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:46" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "55", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:47" + }, + { + "label": "__gap", + "offset": 0, + "slot": "56", + "type": "t_array(t_uint256)45_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:376" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC20PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol:48" + }, + { + "label": "accessControl", + "offset": 0, + "slot": "201", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "252", + "type": "t_array(t_uint256)50_storage", + "contract": "Blacklistable", + "src": "contracts/access/Blacklistable.sol:16" + }, + { + "label": "metadata", + "offset": 0, + "slot": "302", + "type": "t_mapping(t_bytes32,t_bytes_storage)", + "contract": "mToken", + "src": "contracts/mToken.sol:18" + }, + { + "label": "__gap", + "offset": 0, + "slot": "303", + "type": "t_array(t_uint256)50_storage", + "contract": "mToken", + "src": "contracts/mToken.sol:23" + }, + { + "label": "__gap", + "offset": 0, + "slot": "353", + "type": "t_array(t_uint256)50_storage", + "contract": "mM1BTC", + "src": "contracts/products/mM1BTC/mM1BTC.sol:33" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes_storage": { + "label": "bytes", + "numberOfBytes": "32" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bytes_storage)": { + "label": "mapping(bytes32 => bytes)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "5c19834f970db4e65d9b7ea6a8192729f4037ccd51fba301039122fc26f1e529": { + "address": "0xf459780a3c622Ea71c411873AE1a4773B4baA325", + "txHash": "0xce6b827406601b9d8ef47c35d2e6476aad84452310ca1f795d5ba8cf41f72279", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "description", + "offset": 0, + "slot": "51", + "type": "t_string_storage", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:31" + }, + { + "label": "latestRound", + "offset": 0, + "slot": "52", + "type": "t_uint80", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:36" + }, + { + "label": "maxAnswerDeviation", + "offset": 0, + "slot": "53", + "type": "t_uint256", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:42" + }, + { + "label": "minAnswer", + "offset": 0, + "slot": "54", + "type": "t_int192", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:47" + }, + { + "label": "maxAnswer", + "offset": 0, + "slot": "55", + "type": "t_int192", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:52" + }, + { + "label": "_roundData", + "offset": 0, + "slot": "56", + "type": "t_mapping(t_uint80,t_struct(RoundData)17975_storage)", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:57" + }, + { + "label": "__gap", + "offset": 0, + "slot": "57", + "type": "t_array(t_uint256)50_storage", + "contract": "MM1BtcCustomAggregatorFeed", + "src": "contracts/products/mM1BTC/MM1BtcCustomAggregatorFeed.sol:20" + } + ], + "types": { + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_int192": { + "label": "int192", + "numberOfBytes": "24" + }, + "t_int256": { + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_uint80,t_struct(RoundData)17975_storage)": { + "label": "mapping(uint80 => struct CustomAggregatorV3CompatibleFeed.RoundData)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(RoundData)17975_storage": { + "label": "struct CustomAggregatorV3CompatibleFeed.RoundData", + "members": [ + { + "label": "roundId", + "type": "t_uint80", + "offset": 0, + "slot": "0" + }, + { + "label": "answer", + "type": "t_int256", + "offset": 0, + "slot": "1" + }, + { + "label": "startedAt", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "updatedAt", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "answeredInRound", + "type": "t_uint80", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + }, + "t_uint80": { + "label": "uint80", + "numberOfBytes": "10" + } + } + } + }, + "b2dba8978eb820a808b2360efe147af7e578a35a55c5f74fed22ba7a43b867fb": { + "address": "0xe102d0402c13f5652DF4424736C9f64e79df4a46", + "txHash": "0x83fbb8ab7b8ba38589d5e63f62cd9239945540eb3dd981e1c522a86bbe9b6d57", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "aggregator", + "offset": 0, + "slot": "51", + "type": "t_contract(AggregatorV3Interface)45", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:22" + }, + { + "label": "healthyDiff", + "offset": 0, + "slot": "52", + "type": "t_uint256", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:27" + }, + { + "label": "minExpectedAnswer", + "offset": 0, + "slot": "53", + "type": "t_int256", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:32" + }, + { + "label": "maxExpectedAnswer", + "offset": 0, + "slot": "54", + "type": "t_int256", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:37" + }, + { + "label": "__gap", + "offset": 0, + "slot": "55", + "type": "t_array(t_uint256)50_storage", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:42" + }, + { + "label": "__gap", + "offset": 0, + "slot": "105", + "type": "t_array(t_uint256)50_storage", + "contract": "MM1BtcDataFeed", + "src": "contracts/products/mM1BTC/MM1BtcDataFeed.sol:16" + } + ], + "types": { + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(AggregatorV3Interface)45": { + "label": "contract AggregatorV3Interface", + "numberOfBytes": "20" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_int256": { + "label": "int256", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "9046b1f23408a1fdce4c19d7a5fc5ad74cad620fc80390feff1f9fd472769466": { + "address": "0x762ef2593e07De2ED5574539a12d3f203f1c297A", + "txHash": "0x31a142b61bb974e99f9d937580b4df73b1e9c9c69a66cfd22c586af062d745b1", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "fnPaused", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_bytes4,t_bool)", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:14" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)50_storage", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)50_storage", + "contract": "Blacklistable", + "src": "contracts/access/Blacklistable.sol:16" + }, + { + "label": "greenlistEnabled", + "offset": 0, + "slot": "252", + "type": "t_bool", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:16" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:21" + }, + { + "label": "sanctionsList", + "offset": 0, + "slot": "303", + "type": "t_address", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:18" + }, + { + "label": "__gap", + "offset": 0, + "slot": "304", + "type": "t_array(t_uint256)50_storage", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:23" + }, + { + "label": "currentRequestId", + "offset": 0, + "slot": "354", + "type": "t_struct(Counter)8079_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:53" + }, + { + "label": "mToken", + "offset": 0, + "slot": "355", + "type": "t_contract(IMToken)20085", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:66" + }, + { + "label": "mTokenDataFeed", + "offset": 0, + "slot": "356", + "type": "t_contract(IDataFeed)19792", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:71" + }, + { + "label": "tokensReceiver", + "offset": 0, + "slot": "357", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:76" + }, + { + "label": "instantFee", + "offset": 0, + "slot": "358", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:81" + }, + { + "label": "instantDailyLimit", + "offset": 0, + "slot": "359", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:88" + }, + { + "label": "dailyLimits", + "offset": 0, + "slot": "360", + "type": "t_mapping(t_uint256,t_uint256)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:93" + }, + { + "label": "feeReceiver", + "offset": 0, + "slot": "361", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:98" + }, + { + "label": "variationTolerance", + "offset": 0, + "slot": "362", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:103" + }, + { + "label": "waivedFeeRestriction", + "offset": 0, + "slot": "363", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:108" + }, + { + "label": "_paymentTokens", + "offset": 0, + "slot": "364", + "type": "t_struct(AddressSet)3891_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:113" + }, + { + "label": "tokensConfig", + "offset": 0, + "slot": "366", + "type": "t_mapping(t_address,t_struct(TokenConfig)20098_storage)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:118" + }, + { + "label": "minAmount", + "offset": 0, + "slot": "367", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:123" + }, + { + "label": "isFreeFromMinAmount", + "offset": 0, + "slot": "368", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:128" + }, + { + "label": "__gap", + "offset": 0, + "slot": "369", + "type": "t_array(t_uint256)50_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:133" + }, + { + "label": "minMTokenAmountForFirstDeposit", + "offset": 0, + "slot": "419", + "type": "t_uint256", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:78" + }, + { + "label": "mintRequests", + "offset": 0, + "slot": "420", + "type": "t_mapping(t_uint256,t_struct(Request)19809_storage)", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:83" + }, + { + "label": "totalMinted", + "offset": 0, + "slot": "421", + "type": "t_mapping(t_address,t_uint256)", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:88" + }, + { + "label": "maxSupplyCap", + "offset": 0, + "slot": "422", + "type": "t_uint256", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:95" + }, + { + "label": "__gap", + "offset": 0, + "slot": "423", + "type": "t_array(t_uint256)49_storage", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:103" + }, + { + "label": "__gap", + "offset": 0, + "slot": "472", + "type": "t_array(t_uint256)50_storage", + "contract": "MM1BtcDepositVault", + "src": "contracts/products/mM1BTC/MM1BtcDepositVault.sol:16" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes4": { + "label": "bytes4", + "numberOfBytes": "4" + }, + "t_contract(IDataFeed)19792": { + "label": "contract IDataFeed", + "numberOfBytes": "20" + }, + "t_contract(IMToken)20085": { + "label": "contract IMToken", + "numberOfBytes": "20" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_enum(RequestStatus)20102": { + "label": "enum RequestStatus", + "members": ["Pending", "Processed", "Canceled"], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(TokenConfig)20098_storage)": { + "label": "mapping(address => struct TokenConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes4,t_bool)": { + "label": "mapping(bytes4 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(Request)19809_storage)": { + "label": "mapping(uint256 => struct Request)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)3891_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)3576_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)8079_storage": { + "label": "struct Counters.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Request)19809_storage": { + "label": "struct Request", + "members": [ + { + "label": "sender", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "tokenIn", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "status", + "type": "t_enum(RequestStatus)20102", + "offset": 20, + "slot": "1" + }, + { + "label": "depositedUsdAmount", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "usdAmountWithoutFees", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "tokenOutRate", + "type": "t_uint256", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_struct(Set)3576_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(TokenConfig)20098_storage": { + "label": "struct TokenConfig", + "members": [ + { + "label": "dataFeed", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "fee", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "allowance", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "stable", + "type": "t_bool", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "cdb546c1b7dd874f3a00df32e366c4ce35a55ba5127f5bbc16614f4b244caeac": { + "address": "0x95a4265c9f118b39EAf9d470d0AC69D0F9266292", + "txHash": "0x24c2bb5485177e5f54fc1d1fa680de6dbdbe785cf1850d6b57346be479912091", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "fnPaused", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_bytes4,t_bool)", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:14" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)50_storage", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)50_storage", + "contract": "Blacklistable", + "src": "contracts/access/Blacklistable.sol:16" + }, + { + "label": "greenlistEnabled", + "offset": 0, + "slot": "252", + "type": "t_bool", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:16" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:21" + }, + { + "label": "sanctionsList", + "offset": 0, + "slot": "303", + "type": "t_address", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:18" + }, + { + "label": "__gap", + "offset": 0, + "slot": "304", + "type": "t_array(t_uint256)50_storage", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:23" + }, + { + "label": "currentRequestId", + "offset": 0, + "slot": "354", + "type": "t_struct(Counter)8079_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:53" + }, + { + "label": "mToken", + "offset": 0, + "slot": "355", + "type": "t_contract(IMToken)20085", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:66" + }, + { + "label": "mTokenDataFeed", + "offset": 0, + "slot": "356", + "type": "t_contract(IDataFeed)19792", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:71" + }, + { + "label": "tokensReceiver", + "offset": 0, + "slot": "357", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:76" + }, + { + "label": "instantFee", + "offset": 0, + "slot": "358", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:81" + }, + { + "label": "instantDailyLimit", + "offset": 0, + "slot": "359", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:88" + }, + { + "label": "dailyLimits", + "offset": 0, + "slot": "360", + "type": "t_mapping(t_uint256,t_uint256)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:93" + }, + { + "label": "feeReceiver", + "offset": 0, + "slot": "361", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:98" + }, + { + "label": "variationTolerance", + "offset": 0, + "slot": "362", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:103" + }, + { + "label": "waivedFeeRestriction", + "offset": 0, + "slot": "363", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:108" + }, + { + "label": "_paymentTokens", + "offset": 0, + "slot": "364", + "type": "t_struct(AddressSet)3891_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:113" + }, + { + "label": "tokensConfig", + "offset": 0, + "slot": "366", + "type": "t_mapping(t_address,t_struct(TokenConfig)20098_storage)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:118" + }, + { + "label": "minAmount", + "offset": 0, + "slot": "367", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:123" + }, + { + "label": "isFreeFromMinAmount", + "offset": 0, + "slot": "368", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:128" + }, + { + "label": "__gap", + "offset": 0, + "slot": "369", + "type": "t_array(t_uint256)50_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:133" + }, + { + "label": "minFiatRedeemAmount", + "offset": 0, + "slot": "419", + "type": "t_uint256", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:69" + }, + { + "label": "fiatAdditionalFee", + "offset": 0, + "slot": "420", + "type": "t_uint256", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:74" + }, + { + "label": "fiatFlatFee", + "offset": 0, + "slot": "421", + "type": "t_uint256", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:79" + }, + { + "label": "redeemRequests", + "offset": 0, + "slot": "422", + "type": "t_mapping(t_uint256,t_struct(Request)20366_storage)", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:84" + }, + { + "label": "requestRedeemer", + "offset": 0, + "slot": "423", + "type": "t_address", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:89" + }, + { + "label": "__gap", + "offset": 0, + "slot": "424", + "type": "t_array(t_uint256)50_storage", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:94" + }, + { + "label": "___gap", + "offset": 0, + "slot": "474", + "type": "t_array(t_uint256)50_storage", + "contract": "RedemptionVaultWithSwapper", + "src": "contracts/RedemptionVaultWithSwapper.sol:34" + }, + { + "label": "mTbillRedemptionVault", + "offset": 0, + "slot": "524", + "type": "t_contract(IRedemptionVault)20603", + "contract": "RedemptionVaultWithSwapper", + "src": "contracts/RedemptionVaultWithSwapper.sol:41" + }, + { + "label": "liquidityProvider", + "offset": 0, + "slot": "525", + "type": "t_address", + "contract": "RedemptionVaultWithSwapper", + "src": "contracts/RedemptionVaultWithSwapper.sol:43" + }, + { + "label": "__gap", + "offset": 0, + "slot": "526", + "type": "t_array(t_uint256)50_storage", + "contract": "RedemptionVaultWithSwapper", + "src": "contracts/RedemptionVaultWithSwapper.sol:48" + }, + { + "label": "__gap", + "offset": 0, + "slot": "576", + "type": "t_array(t_uint256)50_storage", + "contract": "MM1BtcRedemptionVaultWithSwapper", + "src": "contracts/products/mM1BTC/MM1BtcRedemptionVaultWithSwapper.sol:19" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes4": { + "label": "bytes4", + "numberOfBytes": "4" + }, + "t_contract(IDataFeed)19792": { + "label": "contract IDataFeed", + "numberOfBytes": "20" + }, + "t_contract(IMToken)20085": { + "label": "contract IMToken", + "numberOfBytes": "20" + }, + "t_contract(IRedemptionVault)20603": { + "label": "contract IRedemptionVault", + "numberOfBytes": "20" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_enum(RequestStatus)20102": { + "label": "enum RequestStatus", + "members": ["Pending", "Processed", "Canceled"], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(TokenConfig)20098_storage)": { + "label": "mapping(address => struct TokenConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes4,t_bool)": { + "label": "mapping(bytes4 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(Request)20366_storage)": { + "label": "mapping(uint256 => struct Request)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)3891_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)3576_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)8079_storage": { + "label": "struct Counters.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Request)20366_storage": { + "label": "struct Request", + "members": [ + { + "label": "sender", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "tokenOut", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "status", + "type": "t_enum(RequestStatus)20102", + "offset": 20, + "slot": "1" + }, + { + "label": "amountMToken", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "mTokenRate", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "tokenOutRate", + "type": "t_uint256", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_struct(Set)3576_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(TokenConfig)20098_storage": { + "label": "struct TokenConfig", + "members": [ + { + "label": "dataFeed", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "fee", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "allowance", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "stable", + "type": "t_bool", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "4ab677bc2848d9a588023e79f9676786276377df378e7c55c4f424e0c6cea7b3": { + "address": "0x3421478bDE3Ce905d85fE24682fa8CafdfF3E44a", + "txHash": "0xa64bb971fe47aaa6e0f7aa7b9e2a6d80b1215e085f8992e25e2f857ff5620904", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_balances", + "offset": 0, + "slot": "51", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:40" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "52", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:42" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "53", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44" + }, + { + "label": "_name", + "offset": 0, + "slot": "54", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:46" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "55", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:47" + }, + { + "label": "__gap", + "offset": 0, + "slot": "56", + "type": "t_array(t_uint256)45_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:376" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC20PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol:48" + }, + { + "label": "accessControl", + "offset": 0, + "slot": "201", + "type": "t_contract(MidasAccessControl)3346", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "252", + "type": "t_array(t_uint256)50_storage", + "contract": "Blacklistable", + "src": "contracts/access/Blacklistable.sol:16" + }, + { + "label": "metadata", + "offset": 0, + "slot": "302", + "type": "t_mapping(t_bytes32,t_bytes_storage)", + "contract": "mToken", + "src": "contracts/mToken.sol:18" + }, + { + "label": "__gap", + "offset": 0, + "slot": "303", + "type": "t_array(t_uint256)50_storage", + "contract": "mToken", + "src": "contracts/mToken.sol:23" + }, + { + "label": "__gap", + "offset": 0, + "slot": "353", + "type": "t_array(t_uint256)50_storage", + "contract": "mTokenMinBalance", + "src": "contracts/mTokenMinBalance.sol:16" + }, + { + "label": "__gap", + "offset": 0, + "slot": "403", + "type": "t_array(t_uint256)50_storage", + "contract": "mTokenPermissioned", + "src": "contracts/mTokenPermissioned.sol:16" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes_storage": { + "label": "bytes", + "numberOfBytes": "32" + }, + "t_contract(MidasAccessControl)3346": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bytes_storage)": { + "label": "mapping(bytes32 => bytes)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "9dfbaa430e00eac2e2aa870498562f8dcdcc409143cf4cb1973b67d7ecc112d9": { + "address": "0x7156b140AaD6999F48767Db6687F46e8Df331C4C", + "txHash": "0xcabb320c7d7fd542ccd3bec4deb3d82a6c85ccfef0323a8d9b2d9bdd6df4b776", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)3335", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "description", + "offset": 0, + "slot": "51", + "type": "t_string_storage", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:31" + }, + { + "label": "latestRound", + "offset": 0, + "slot": "52", + "type": "t_uint80", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:36" + }, + { + "label": "maxAnswerDeviation", + "offset": 0, + "slot": "53", + "type": "t_uint256", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:42" + }, + { + "label": "minAnswer", + "offset": 0, + "slot": "54", + "type": "t_int192", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:47" + }, + { + "label": "maxAnswer", + "offset": 0, + "slot": "55", + "type": "t_int192", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:52" + }, + { + "label": "_roundData", + "offset": 0, + "slot": "56", + "type": "t_mapping(t_uint80,t_struct(RoundData)3499_storage)", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:57" + }, + { + "label": "__gap", + "offset": 0, + "slot": "57", + "type": "t_array(t_uint256)50_storage", + "contract": "MWinCustomAggregatorFeed", + "src": "contracts/products/mWIN/MWinCustomAggregatorFeed.sol:20" + } + ], + "types": { + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(MidasAccessControl)3335": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_int192": { + "label": "int192", + "numberOfBytes": "24" + }, + "t_int256": { + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_uint80,t_struct(RoundData)3499_storage)": { + "label": "mapping(uint80 => struct CustomAggregatorV3CompatibleFeed.RoundData)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(RoundData)3499_storage": { + "label": "struct CustomAggregatorV3CompatibleFeed.RoundData", + "members": [ + { + "label": "roundId", + "type": "t_uint80", + "offset": 0, + "slot": "0" + }, + { + "label": "answer", + "type": "t_int256", + "offset": 0, + "slot": "1" + }, + { + "label": "startedAt", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "updatedAt", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "answeredInRound", + "type": "t_uint80", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + }, + "t_uint80": { + "label": "uint80", + "numberOfBytes": "10" + } + } + } + }, + "cb519bdb0660028d1ba032c5d608c91f58897dac1f0f41c5201a8d5e2b0be96c": { + "address": "0x4F2B9102B436fC37e4667f8d93304F8f0a5Bc5bb", + "txHash": "0x923f6ff786648201c7519593c2f64ebfdf13a96324b599d317ce563bca322deb", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)16693", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "fnPaused", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_bytes4,t_bool)", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:14" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)50_storage", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)50_storage", + "contract": "Blacklistable", + "src": "contracts/access/Blacklistable.sol:16" + }, + { + "label": "greenlistEnabled", + "offset": 0, + "slot": "252", + "type": "t_bool", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:16" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:21" + }, + { + "label": "sanctionsList", + "offset": 0, + "slot": "303", + "type": "t_address", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:18" + }, + { + "label": "__gap", + "offset": 0, + "slot": "304", + "type": "t_array(t_uint256)50_storage", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:23" + }, + { + "label": "currentRequestId", + "offset": 0, + "slot": "354", + "type": "t_struct(Counter)7492_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:53" + }, + { + "label": "mToken", + "offset": 0, + "slot": "355", + "type": "t_contract(IMToken)19433", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:66" + }, + { + "label": "mTokenDataFeed", + "offset": 0, + "slot": "356", + "type": "t_contract(IDataFeed)19140", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:71" + }, + { + "label": "tokensReceiver", + "offset": 0, + "slot": "357", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:76" + }, + { + "label": "instantFee", + "offset": 0, + "slot": "358", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:81" + }, + { + "label": "instantDailyLimit", + "offset": 0, + "slot": "359", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:88" + }, + { + "label": "dailyLimits", + "offset": 0, + "slot": "360", + "type": "t_mapping(t_uint256,t_uint256)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:93" + }, + { + "label": "feeReceiver", + "offset": 0, + "slot": "361", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:98" + }, + { + "label": "variationTolerance", + "offset": 0, + "slot": "362", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:103" + }, + { + "label": "waivedFeeRestriction", + "offset": 0, + "slot": "363", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:108" + }, + { + "label": "_paymentTokens", + "offset": 0, + "slot": "364", + "type": "t_struct(AddressSet)3891_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:113" + }, + { + "label": "tokensConfig", + "offset": 0, + "slot": "366", + "type": "t_mapping(t_address,t_struct(TokenConfig)19446_storage)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:118" + }, + { + "label": "minAmount", + "offset": 0, + "slot": "367", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:123" + }, + { + "label": "isFreeFromMinAmount", + "offset": 0, + "slot": "368", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:128" + }, + { + "label": "__gap", + "offset": 0, + "slot": "369", + "type": "t_array(t_uint256)50_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:133" + }, + { + "label": "minFiatRedeemAmount", + "offset": 0, + "slot": "419", + "type": "t_uint256", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:69" + }, + { + "label": "fiatAdditionalFee", + "offset": 0, + "slot": "420", + "type": "t_uint256", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:74" + }, + { + "label": "fiatFlatFee", + "offset": 0, + "slot": "421", + "type": "t_uint256", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:79" + }, + { + "label": "redeemRequests", + "offset": 0, + "slot": "422", + "type": "t_mapping(t_uint256,t_struct(Request)19714_storage)", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:84" + }, + { + "label": "requestRedeemer", + "offset": 0, + "slot": "423", + "type": "t_address", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:89" + }, + { + "label": "__gap", + "offset": 0, + "slot": "424", + "type": "t_array(t_uint256)50_storage", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:94" + }, + { + "label": "___gap", + "offset": 0, + "slot": "474", + "type": "t_array(t_uint256)50_storage", + "contract": "RedemptionVaultWithMToken", + "src": "contracts/RedemptionVaultWithMToken.sol:26" + }, + { + "label": "redemptionVault", + "offset": 0, + "slot": "524", + "type": "t_contract(IRedemptionVault)19951", + "contract": "RedemptionVaultWithMToken", + "src": "contracts/RedemptionVaultWithMToken.sol:32", + "renamedFrom": "mTbillRedemptionVault" + }, + { + "label": "liquidityProvider_deprecated", + "offset": 0, + "slot": "525", + "type": "t_address", + "contract": "RedemptionVaultWithMToken", + "src": "contracts/RedemptionVaultWithMToken.sol:39", + "renamedFrom": "liquidityProvider" + }, + { + "label": "__gap", + "offset": 0, + "slot": "526", + "type": "t_array(t_uint256)50_storage", + "contract": "RedemptionVaultWithMToken", + "src": "contracts/RedemptionVaultWithMToken.sol:44" + }, + { + "label": "__gap", + "offset": 0, + "slot": "576", + "type": "t_array(t_uint256)50_storage", + "contract": "MWinRedemptionVaultWithMToken", + "src": "contracts/products/mWIN/MWinRedemptionVaultWithMToken.sol:21" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes4": { + "label": "bytes4", + "numberOfBytes": "4" + }, + "t_contract(IDataFeed)19140": { + "label": "contract IDataFeed", + "numberOfBytes": "20" + }, + "t_contract(IMToken)19433": { + "label": "contract IMToken", + "numberOfBytes": "20" + }, + "t_contract(IRedemptionVault)19951": { + "label": "contract IRedemptionVault", + "numberOfBytes": "20" + }, + "t_contract(MidasAccessControl)16693": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_enum(RequestStatus)19450": { + "label": "enum RequestStatus", + "members": ["Pending", "Processed", "Canceled"], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(TokenConfig)19446_storage)": { + "label": "mapping(address => struct TokenConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes4,t_bool)": { + "label": "mapping(bytes4 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(Request)19714_storage)": { + "label": "mapping(uint256 => struct Request)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)3891_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)3576_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)7492_storage": { + "label": "struct Counters.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Request)19714_storage": { + "label": "struct Request", + "members": [ + { + "label": "sender", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "tokenOut", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "status", + "type": "t_enum(RequestStatus)19450", + "offset": 20, + "slot": "1" + }, + { + "label": "amountMToken", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "mTokenRate", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "tokenOutRate", + "type": "t_uint256", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_struct(Set)3576_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(TokenConfig)19446_storage": { + "label": "struct TokenConfig", + "members": [ + { + "label": "dataFeed", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "fee", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "allowance", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "stable", + "type": "t_bool", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "20ef74ef348cd04515894e02664350c2512b5a2a546ab7ba2520753130b859b0": { + "address": "0x02D7ec4A7F42bbb1e3879599a5c23c70c35748cf", + "txHash": "0xb03bc433fcbf555affb26155e25b0df28ff2a76caf447cf99d4cf59292cdcb6a", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "numeratorFeed", + "offset": 0, + "slot": "51", + "type": "t_contract(IDataFeed)19792", + "contract": "CompositeDataFeed", + "src": "contracts/feeds/CompositeDataFeed.sol:20" + }, + { + "label": "denominatorFeed", + "offset": 0, + "slot": "52", + "type": "t_contract(IDataFeed)19792", + "contract": "CompositeDataFeed", + "src": "contracts/feeds/CompositeDataFeed.sol:26" + }, + { + "label": "minExpectedAnswer", + "offset": 0, + "slot": "53", + "type": "t_uint256", + "contract": "CompositeDataFeed", + "src": "contracts/feeds/CompositeDataFeed.sol:31" + }, + { + "label": "maxExpectedAnswer", + "offset": 0, + "slot": "54", + "type": "t_uint256", + "contract": "CompositeDataFeed", + "src": "contracts/feeds/CompositeDataFeed.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "55", + "type": "t_array(t_uint256)50_storage", + "contract": "CompositeDataFeed", + "src": "contracts/feeds/CompositeDataFeed.sol:41" + } + ], + "types": { + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IDataFeed)19792": { + "label": "contract IDataFeed", + "numberOfBytes": "20" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "645dd6bb23526df5ae142cf011cf472c3c084791cd6c6ce321e3a04ffbff6593": { + "address": "0xCA18bBD53554b2461d0739eaF25e21c872a20E59", + "txHash": "0x9e1ad1439eef4138d39ac49a1d81d5f42ab1f99bb524c7473dd2ab772407b400", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "description", + "offset": 0, + "slot": "51", + "type": "t_string_storage", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:31" + }, + { + "label": "latestRound", + "offset": 0, + "slot": "52", + "type": "t_uint80", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:36" + }, + { + "label": "maxAnswerDeviation", + "offset": 0, + "slot": "53", + "type": "t_uint256", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:42" + }, + { + "label": "minAnswer", + "offset": 0, + "slot": "54", + "type": "t_int192", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:47" + }, + { + "label": "maxAnswer", + "offset": 0, + "slot": "55", + "type": "t_int192", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:52" + }, + { + "label": "_roundData", + "offset": 0, + "slot": "56", + "type": "t_mapping(t_uint80,t_struct(RoundData)17975_storage)", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:57" + } + ], + "types": { + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_int192": { + "label": "int192", + "numberOfBytes": "24" + }, + "t_int256": { + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_uint80,t_struct(RoundData)17975_storage)": { + "label": "mapping(uint80 => struct CustomAggregatorV3CompatibleFeed.RoundData)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(RoundData)17975_storage": { + "label": "struct CustomAggregatorV3CompatibleFeed.RoundData", + "members": [ + { + "label": "roundId", + "type": "t_uint80", + "offset": 0, + "slot": "0" + }, + { + "label": "answer", + "type": "t_int256", + "offset": 0, + "slot": "1" + }, + { + "label": "startedAt", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "updatedAt", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "answeredInRound", + "type": "t_uint80", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + }, + "t_uint80": { + "label": "uint80", + "numberOfBytes": "10" + } + } + } + }, + "ac38a1923bfd1a4465db3352916ecea0a4ae507dac0883d1a3903512f5b2ba34": { + "address": "0x578ba8bf689d167Ac30DFe2a8285A3A16519B18d", + "txHash": "0x762b73b5e701d7a5d2d5a3262a9f8dd378b6c6b5b4b90bd391af3682dacfe85b", "layout": { "solcVersion": "0.8.9", "storage": [ @@ -118043,8 +120821,16 @@ "offset": 0, "slot": "353", "type": "t_array(t_uint256)50_storage", - "contract": "mM1BTC", - "src": "contracts/products/mM1BTC/mM1BTC.sol:33" + "contract": "mTokenMinBalance", + "src": "contracts/mTokenMinBalance.sol:16" + }, + { + "label": "__gap", + "offset": 0, + "slot": "403", + "type": "t_array(t_uint256)50_storage", + "contract": "mGLOeuro", + "src": "contracts/products/mGLOeuro/mGLOeuro.sol:39" } ], "types": { @@ -118107,9 +120893,9 @@ } } }, - "5c19834f970db4e65d9b7ea6a8192729f4037ccd51fba301039122fc26f1e529": { - "address": "0xf459780a3c622Ea71c411873AE1a4773B4baA325", - "txHash": "0xce6b827406601b9d8ef47c35d2e6476aad84452310ca1f795d5ba8cf41f72279", + "a95446ac61efa7df55ae397582ff4792bafae41969ba247aca00166681446056": { + "address": "0xbFd25A5d18B2E49eb4d486A99d9373440909795a", + "txHash": "0xb0dbfd155d7958e5a9111234ebea41aae14a2d69c8e99f098e6de961c3258b42", "layout": { "solcVersion": "0.8.9", "storage": [ @@ -118199,8 +120985,8 @@ "offset": 0, "slot": "57", "type": "t_array(t_uint256)50_storage", - "contract": "MM1BtcCustomAggregatorFeed", - "src": "contracts/products/mM1BTC/MM1BtcCustomAggregatorFeed.sol:20" + "contract": "MGloEuroCustomAggregatorFeed", + "src": "contracts/products/mGLOeuro/MGloEuroCustomAggregatorFeed.sol:20" } ], "types": { @@ -118283,9 +121069,9 @@ } } }, - "b2dba8978eb820a808b2360efe147af7e578a35a55c5f74fed22ba7a43b867fb": { - "address": "0xe102d0402c13f5652DF4424736C9f64e79df4a46", - "txHash": "0x83fbb8ab7b8ba38589d5e63f62cd9239945540eb3dd981e1c522a86bbe9b6d57", + "3fbddeea5f4ee5684e4ba90a02d73e84eecbed8cd72b3cec1eb41028f325474f": { + "address": "0x69ca8c969884A755dDA6B8DC2F91A10e63148675", + "txHash": "0x1d19c14329994681654de49c1065455296214e6254f96d7bdb8d19f7d5478fee", "layout": { "solcVersion": "0.8.9", "storage": [ @@ -118367,8 +121153,8 @@ "offset": 0, "slot": "105", "type": "t_array(t_uint256)50_storage", - "contract": "MM1BtcDataFeed", - "src": "contracts/products/mM1BTC/MM1BtcDataFeed.sol:16" + "contract": "MGloEuroDataFeed", + "src": "contracts/products/mGLOeuro/MGloEuroDataFeed.sol:16" } ], "types": { @@ -118403,9 +121189,9 @@ } } }, - "9046b1f23408a1fdce4c19d7a5fc5ad74cad620fc80390feff1f9fd472769466": { - "address": "0x762ef2593e07De2ED5574539a12d3f203f1c297A", - "txHash": "0x31a142b61bb974e99f9d937580b4df73b1e9c9c69a66cfd22c586af062d745b1", + "a77e77c5b9296e786ff1e44e54b0e72f58d640c67d409fc104690c9fdeab9e6f": { + "address": "0x370e3933Ce6a8C710455AFFBA984c1104e42Fb59", + "txHash": "0x20344318993757b11ad88337767c1345d864a4fca1a6a7d6cc7d68a55cb36024", "layout": { "solcVersion": "0.8.9", "storage": [ @@ -118683,12 +121469,44 @@ "src": "contracts/DepositVault.sol:103" }, { - "label": "__gap", + "label": "morphoVaults", "offset": 0, "slot": "472", + "type": "t_mapping(t_address,t_contract(IMorphoVault)20791)", + "contract": "DepositVaultWithMorpho", + "src": "contracts/DepositVaultWithMorpho.sol:24" + }, + { + "label": "morphoDepositsEnabled", + "offset": 0, + "slot": "473", + "type": "t_bool", + "contract": "DepositVaultWithMorpho", + "src": "contracts/DepositVaultWithMorpho.sol:30" + }, + { + "label": "autoInvestFallbackEnabled", + "offset": 1, + "slot": "473", + "type": "t_bool", + "contract": "DepositVaultWithMorpho", + "src": "contracts/DepositVaultWithMorpho.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "474", "type": "t_array(t_uint256)50_storage", - "contract": "MM1BtcDepositVault", - "src": "contracts/products/mM1BTC/MM1BtcDepositVault.sol:16" + "contract": "DepositVaultWithMorpho", + "src": "contracts/DepositVaultWithMorpho.sol:41" + }, + { + "label": "__gap", + "offset": 0, + "slot": "524", + "type": "t_array(t_uint256)50_storage", + "contract": "MGloEuroDepositVaultWithMorpho", + "src": "contracts/products/mGLOeuro/MGloEuroDepositVaultWithMorpho.sol:19" } ], "types": { @@ -118728,6 +121546,10 @@ "label": "contract IMToken", "numberOfBytes": "20" }, + "t_contract(IMorphoVault)20791": { + "label": "contract IMorphoVault", + "numberOfBytes": "20" + }, "t_contract(MidasAccessControl)17280": { "label": "contract MidasAccessControl", "numberOfBytes": "20" @@ -118741,6 +121563,10 @@ "label": "mapping(address => bool)", "numberOfBytes": "32" }, + "t_mapping(t_address,t_contract(IMorphoVault)20791)": { + "label": "mapping(address => contract IMorphoVault)", + "numberOfBytes": "32" + }, "t_mapping(t_address,t_struct(TokenConfig)20098_storage)": { "label": "mapping(address => struct TokenConfig)", "numberOfBytes": "32" @@ -118890,9 +121716,9 @@ } } }, - "cdb546c1b7dd874f3a00df32e366c4ce35a55ba5127f5bbc16614f4b244caeac": { - "address": "0x95a4265c9f118b39EAf9d470d0AC69D0F9266292", - "txHash": "0x24c2bb5485177e5f54fc1d1fa680de6dbdbe785cf1850d6b57346be479912091", + "c48e51c6e9141222340dd06506758d1bac849758940302d57a6a93d125c1c317": { + "address": "0x71f0b214d6F552795737d37CBCCb148C2dE0bCeF", + "txHash": "0x93513d7142203e02d4ed69134cefcde02190320eab3200e2731ecc5a87faffba", "layout": { "solcVersion": "0.8.9", "storage": [ @@ -119178,44 +122004,28 @@ "src": "contracts/RedemptionVault.sol:94" }, { - "label": "___gap", + "label": "morphoVaults", "offset": 0, "slot": "474", - "type": "t_array(t_uint256)50_storage", - "contract": "RedemptionVaultWithSwapper", - "src": "contracts/RedemptionVaultWithSwapper.sol:34" - }, - { - "label": "mTbillRedemptionVault", - "offset": 0, - "slot": "524", - "type": "t_contract(IRedemptionVault)20603", - "contract": "RedemptionVaultWithSwapper", - "src": "contracts/RedemptionVaultWithSwapper.sol:41" - }, - { - "label": "liquidityProvider", - "offset": 0, - "slot": "525", - "type": "t_address", - "contract": "RedemptionVaultWithSwapper", - "src": "contracts/RedemptionVaultWithSwapper.sol:43" + "type": "t_mapping(t_address,t_contract(IMorphoVault)20791)", + "contract": "RedemptionVaultWithMorpho", + "src": "contracts/RedemptionVaultWithMorpho.sol:25" }, { "label": "__gap", "offset": 0, - "slot": "526", + "slot": "475", "type": "t_array(t_uint256)50_storage", - "contract": "RedemptionVaultWithSwapper", - "src": "contracts/RedemptionVaultWithSwapper.sol:48" + "contract": "RedemptionVaultWithMorpho", + "src": "contracts/RedemptionVaultWithMorpho.sol:30" }, { "label": "__gap", "offset": 0, - "slot": "576", + "slot": "525", "type": "t_array(t_uint256)50_storage", - "contract": "MM1BtcRedemptionVaultWithSwapper", - "src": "contracts/products/mM1BTC/MM1BtcRedemptionVaultWithSwapper.sol:19" + "contract": "MGloEuroRedemptionVaultWithMorpho", + "src": "contracts/products/mGLOeuro/MGloEuroRedemptionVaultWithMorpho.sol:19" } ], "types": { @@ -119255,8 +122065,8 @@ "label": "contract IMToken", "numberOfBytes": "20" }, - "t_contract(IRedemptionVault)20603": { - "label": "contract IRedemptionVault", + "t_contract(IMorphoVault)20791": { + "label": "contract IMorphoVault", "numberOfBytes": "20" }, "t_contract(MidasAccessControl)17280": { @@ -119272,6 +122082,10 @@ "label": "mapping(address => bool)", "numberOfBytes": "32" }, + "t_mapping(t_address,t_contract(IMorphoVault)20791)": { + "label": "mapping(address => contract IMorphoVault)", + "numberOfBytes": "32" + }, "t_mapping(t_address,t_struct(TokenConfig)20098_storage)": { "label": "mapping(address => struct TokenConfig)", "numberOfBytes": "32" diff --git a/.openzeppelin/optimism.json b/.openzeppelin/optimism.json index ded6c36b..bc934382 100644 --- a/.openzeppelin/optimism.json +++ b/.openzeppelin/optimism.json @@ -149,6 +149,36 @@ "address": "0x12Ae90dCe5C2a4Ee5141FBfc408ff1022D051F42", "txHash": "0xeb76c96323a1e0daabfb430315f5d5f65fe7173509df08f86a1dda380f4cce11", "kind": "transparent" + }, + { + "address": "0x1Eaf7cceAA5DC6d605760718722fbcC58579b3Ff", + "txHash": "0xf6452cae920dceb114e18256ecbf87492d64f583f5f29d818c91b902c6ee7318", + "kind": "transparent" + }, + { + "address": "0x4c8f5CEd7F3211ad1D70B6F33e585309Fb1AcA75", + "txHash": "0x5c87a8a048b835f26fecf6e97541dc59ed8d3ffb3714f1fbf5985037265a95c3", + "kind": "transparent" + }, + { + "address": "0xAf91e7754E12c90F3F07a1719E39a65Fc58204ff", + "txHash": "0x12128927cec5f04ffe5f96f6cd817f8a2e145d4e983a48dbd59dfa20bc41eeb8", + "kind": "transparent" + }, + { + "address": "0xB391486b0d058176A845aCcF2Cb160A2be9CfCb8", + "txHash": "0x38513b651d0cc93471575a3bc5f88aa8a7439671256b3c90475bf68dae7191ce", + "kind": "transparent" + }, + { + "address": "0xa5d0E53059b648B7615B67602De4489079EAe2B4", + "txHash": "0xbc48f2a325486db704f878ff854d75bc035a91768dbbfe29d14ff02fbe14b34b", + "kind": "transparent" + }, + { + "address": "0x99656E9753047F2769D10fa65B9D8ebfE44B35ab", + "txHash": "0x6848e1b7fd38c3037445257391b42f81b3aac1f6f005a5037647f5a2b016f871", + "kind": "transparent" } ], "impls": { @@ -7161,6 +7191,1528 @@ } } } + }, + "bd43bec9d71009d2d5057a25b7b2ff2271c3c1265815d8930c185e8c634ef4cd": { + "address": "0x6a2A00B07FEF2beeCa67c454b093963789D9d194", + "txHash": "0xa7465366951c068f4d2096d91562ed3bf3b38430249358008eb23191761215e0", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_balances", + "offset": 0, + "slot": "51", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:40" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "52", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:42" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "53", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44" + }, + { + "label": "_name", + "offset": 0, + "slot": "54", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:46" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "55", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:47" + }, + { + "label": "__gap", + "offset": 0, + "slot": "56", + "type": "t_array(t_uint256)45_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:376" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC20PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol:48" + }, + { + "label": "accessControl", + "offset": 0, + "slot": "201", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "252", + "type": "t_array(t_uint256)50_storage", + "contract": "Blacklistable", + "src": "contracts/access/Blacklistable.sol:16" + }, + { + "label": "metadata", + "offset": 0, + "slot": "302", + "type": "t_mapping(t_bytes32,t_bytes_storage)", + "contract": "mToken", + "src": "contracts/mToken.sol:18" + }, + { + "label": "__gap", + "offset": 0, + "slot": "303", + "type": "t_array(t_uint256)50_storage", + "contract": "mToken", + "src": "contracts/mToken.sol:23" + }, + { + "label": "__gap", + "offset": 0, + "slot": "353", + "type": "t_array(t_uint256)50_storage", + "contract": "mGLO", + "src": "contracts/products/mGLO/mGLO.sol:33" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes_storage": { + "label": "bytes", + "numberOfBytes": "32" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bytes_storage)": { + "label": "mapping(bytes32 => bytes)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "8fa3463fdea2a95bc34777b60b21a82202e4b168b2fc9422f0e6dc55ca35556a": { + "address": "0x4640878904492027faB0edd8d5193E054aB37F62", + "txHash": "0x0ef551572603b3dec0ea8f04d7b0778ea2d56d89fc3299fb2d13e411ba4d5bac", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "description", + "offset": 0, + "slot": "51", + "type": "t_string_storage", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:31" + }, + { + "label": "latestRound", + "offset": 0, + "slot": "52", + "type": "t_uint80", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:36" + }, + { + "label": "maxAnswerDeviation", + "offset": 0, + "slot": "53", + "type": "t_uint256", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:42" + }, + { + "label": "minAnswer", + "offset": 0, + "slot": "54", + "type": "t_int192", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:47" + }, + { + "label": "maxAnswer", + "offset": 0, + "slot": "55", + "type": "t_int192", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:52" + }, + { + "label": "_roundData", + "offset": 0, + "slot": "56", + "type": "t_mapping(t_uint80,t_struct(RoundData)17975_storage)", + "contract": "CustomAggregatorV3CompatibleFeed", + "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:57" + }, + { + "label": "__gap", + "offset": 0, + "slot": "57", + "type": "t_array(t_uint256)50_storage", + "contract": "MGloCustomAggregatorFeed", + "src": "contracts/products/mGLO/MGloCustomAggregatorFeed.sol:20" + } + ], + "types": { + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_int192": { + "label": "int192", + "numberOfBytes": "24" + }, + "t_int256": { + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_uint80,t_struct(RoundData)17975_storage)": { + "label": "mapping(uint80 => struct CustomAggregatorV3CompatibleFeed.RoundData)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(RoundData)17975_storage": { + "label": "struct CustomAggregatorV3CompatibleFeed.RoundData", + "members": [ + { + "label": "roundId", + "type": "t_uint80", + "offset": 0, + "slot": "0" + }, + { + "label": "answer", + "type": "t_int256", + "offset": 0, + "slot": "1" + }, + { + "label": "startedAt", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "updatedAt", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "answeredInRound", + "type": "t_uint80", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + }, + "t_uint80": { + "label": "uint80", + "numberOfBytes": "10" + } + } + } + }, + "1c466f9d3d59dc637bb3ab9803405d25102d82828a68bfb578c4915a4586949b": { + "address": "0x760B413c8dF6f2C5fdCa80aB58b43e7389C41A83", + "txHash": "0x50ee552138998a4a7cae7177f1f7a4db46a12791bf599fdc40b2f79210332194", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "aggregator", + "offset": 0, + "slot": "51", + "type": "t_contract(AggregatorV3Interface)45", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:22" + }, + { + "label": "healthyDiff", + "offset": 0, + "slot": "52", + "type": "t_uint256", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:27" + }, + { + "label": "minExpectedAnswer", + "offset": 0, + "slot": "53", + "type": "t_int256", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:32" + }, + { + "label": "maxExpectedAnswer", + "offset": 0, + "slot": "54", + "type": "t_int256", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:37" + }, + { + "label": "__gap", + "offset": 0, + "slot": "55", + "type": "t_array(t_uint256)50_storage", + "contract": "DataFeed", + "src": "contracts/feeds/DataFeed.sol:42" + }, + { + "label": "__gap", + "offset": 0, + "slot": "105", + "type": "t_array(t_uint256)50_storage", + "contract": "MGloDataFeed", + "src": "contracts/products/mGLO/MGloDataFeed.sol:16" + } + ], + "types": { + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(AggregatorV3Interface)45": { + "label": "contract AggregatorV3Interface", + "numberOfBytes": "20" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_int256": { + "label": "int256", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "99af436b93e7b8e3b548df81a443da1feae190805ed7f81051bad1f11e0610a3": { + "address": "0xC8259d5F756c8440B8762C6da3Bb1Fd018d23430", + "txHash": "0x858ca35450fcf46e5c820ffbcb2621792a7f92f5c26de961dba063ec6eaf8bc4", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "fnPaused", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_bytes4,t_bool)", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:14" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)50_storage", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)50_storage", + "contract": "Blacklistable", + "src": "contracts/access/Blacklistable.sol:16" + }, + { + "label": "greenlistEnabled", + "offset": 0, + "slot": "252", + "type": "t_bool", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:16" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:21" + }, + { + "label": "sanctionsList", + "offset": 0, + "slot": "303", + "type": "t_address", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:18" + }, + { + "label": "__gap", + "offset": 0, + "slot": "304", + "type": "t_array(t_uint256)50_storage", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:23" + }, + { + "label": "currentRequestId", + "offset": 0, + "slot": "354", + "type": "t_struct(Counter)8079_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:53" + }, + { + "label": "mToken", + "offset": 0, + "slot": "355", + "type": "t_contract(IMToken)20085", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:66" + }, + { + "label": "mTokenDataFeed", + "offset": 0, + "slot": "356", + "type": "t_contract(IDataFeed)19792", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:71" + }, + { + "label": "tokensReceiver", + "offset": 0, + "slot": "357", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:76" + }, + { + "label": "instantFee", + "offset": 0, + "slot": "358", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:81" + }, + { + "label": "instantDailyLimit", + "offset": 0, + "slot": "359", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:88" + }, + { + "label": "dailyLimits", + "offset": 0, + "slot": "360", + "type": "t_mapping(t_uint256,t_uint256)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:93" + }, + { + "label": "feeReceiver", + "offset": 0, + "slot": "361", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:98" + }, + { + "label": "variationTolerance", + "offset": 0, + "slot": "362", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:103" + }, + { + "label": "waivedFeeRestriction", + "offset": 0, + "slot": "363", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:108" + }, + { + "label": "_paymentTokens", + "offset": 0, + "slot": "364", + "type": "t_struct(AddressSet)3891_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:113" + }, + { + "label": "tokensConfig", + "offset": 0, + "slot": "366", + "type": "t_mapping(t_address,t_struct(TokenConfig)20098_storage)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:118" + }, + { + "label": "minAmount", + "offset": 0, + "slot": "367", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:123" + }, + { + "label": "isFreeFromMinAmount", + "offset": 0, + "slot": "368", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:128" + }, + { + "label": "__gap", + "offset": 0, + "slot": "369", + "type": "t_array(t_uint256)50_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:133" + }, + { + "label": "minMTokenAmountForFirstDeposit", + "offset": 0, + "slot": "419", + "type": "t_uint256", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:78" + }, + { + "label": "mintRequests", + "offset": 0, + "slot": "420", + "type": "t_mapping(t_uint256,t_struct(Request)19809_storage)", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:83" + }, + { + "label": "totalMinted", + "offset": 0, + "slot": "421", + "type": "t_mapping(t_address,t_uint256)", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:88" + }, + { + "label": "maxSupplyCap", + "offset": 0, + "slot": "422", + "type": "t_uint256", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:95" + }, + { + "label": "__gap", + "offset": 0, + "slot": "423", + "type": "t_array(t_uint256)49_storage", + "contract": "DepositVault", + "src": "contracts/DepositVault.sol:103" + }, + { + "label": "__gap", + "offset": 0, + "slot": "472", + "type": "t_array(t_uint256)50_storage", + "contract": "MGloDepositVault", + "src": "contracts/products/mGLO/MGloDepositVault.sol:16" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes4": { + "label": "bytes4", + "numberOfBytes": "4" + }, + "t_contract(IDataFeed)19792": { + "label": "contract IDataFeed", + "numberOfBytes": "20" + }, + "t_contract(IMToken)20085": { + "label": "contract IMToken", + "numberOfBytes": "20" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_enum(RequestStatus)20102": { + "label": "enum RequestStatus", + "members": ["Pending", "Processed", "Canceled"], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(TokenConfig)20098_storage)": { + "label": "mapping(address => struct TokenConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes4,t_bool)": { + "label": "mapping(bytes4 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(Request)19809_storage)": { + "label": "mapping(uint256 => struct Request)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)3891_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)3576_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)8079_storage": { + "label": "struct Counters.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Request)19809_storage": { + "label": "struct Request", + "members": [ + { + "label": "sender", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "tokenIn", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "status", + "type": "t_enum(RequestStatus)20102", + "offset": 20, + "slot": "1" + }, + { + "label": "depositedUsdAmount", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "usdAmountWithoutFees", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "tokenOutRate", + "type": "t_uint256", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_struct(Set)3576_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(TokenConfig)20098_storage": { + "label": "struct TokenConfig", + "members": [ + { + "label": "dataFeed", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "fee", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "allowance", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "stable", + "type": "t_bool", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "6d87829d923eaee078c11820df3f6ef282b4fa138c0179e1010fce7f88e60671": { + "address": "0xE6DcC8b0EA00b202224B2fe519E6677BEA3aC381", + "txHash": "0xd1b5d0670daafc5ac610f984d1ca03630277d495a08eae0d15ff5e504895b0e8", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "accessControl", + "offset": 2, + "slot": "0", + "type": "t_contract(MidasAccessControl)17280", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "WithMidasAccessControl", + "src": "contracts/access/WithMidasAccessControl.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "fnPaused", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_bytes4,t_bool)", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:14" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)50_storage", + "contract": "Pausable", + "src": "contracts/access/Pausable.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)50_storage", + "contract": "Blacklistable", + "src": "contracts/access/Blacklistable.sol:16" + }, + { + "label": "greenlistEnabled", + "offset": 0, + "slot": "252", + "type": "t_bool", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:16" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "Greenlistable", + "src": "contracts/access/Greenlistable.sol:21" + }, + { + "label": "sanctionsList", + "offset": 0, + "slot": "303", + "type": "t_address", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:18" + }, + { + "label": "__gap", + "offset": 0, + "slot": "304", + "type": "t_array(t_uint256)50_storage", + "contract": "WithSanctionsList", + "src": "contracts/abstract/WithSanctionsList.sol:23" + }, + { + "label": "currentRequestId", + "offset": 0, + "slot": "354", + "type": "t_struct(Counter)8079_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:53" + }, + { + "label": "mToken", + "offset": 0, + "slot": "355", + "type": "t_contract(IMToken)20085", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:66" + }, + { + "label": "mTokenDataFeed", + "offset": 0, + "slot": "356", + "type": "t_contract(IDataFeed)19792", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:71" + }, + { + "label": "tokensReceiver", + "offset": 0, + "slot": "357", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:76" + }, + { + "label": "instantFee", + "offset": 0, + "slot": "358", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:81" + }, + { + "label": "instantDailyLimit", + "offset": 0, + "slot": "359", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:88" + }, + { + "label": "dailyLimits", + "offset": 0, + "slot": "360", + "type": "t_mapping(t_uint256,t_uint256)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:93" + }, + { + "label": "feeReceiver", + "offset": 0, + "slot": "361", + "type": "t_address", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:98" + }, + { + "label": "variationTolerance", + "offset": 0, + "slot": "362", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:103" + }, + { + "label": "waivedFeeRestriction", + "offset": 0, + "slot": "363", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:108" + }, + { + "label": "_paymentTokens", + "offset": 0, + "slot": "364", + "type": "t_struct(AddressSet)3891_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:113" + }, + { + "label": "tokensConfig", + "offset": 0, + "slot": "366", + "type": "t_mapping(t_address,t_struct(TokenConfig)20098_storage)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:118" + }, + { + "label": "minAmount", + "offset": 0, + "slot": "367", + "type": "t_uint256", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:123" + }, + { + "label": "isFreeFromMinAmount", + "offset": 0, + "slot": "368", + "type": "t_mapping(t_address,t_bool)", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:128" + }, + { + "label": "__gap", + "offset": 0, + "slot": "369", + "type": "t_array(t_uint256)50_storage", + "contract": "ManageableVault", + "src": "contracts/abstract/ManageableVault.sol:133" + }, + { + "label": "minFiatRedeemAmount", + "offset": 0, + "slot": "419", + "type": "t_uint256", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:69" + }, + { + "label": "fiatAdditionalFee", + "offset": 0, + "slot": "420", + "type": "t_uint256", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:74" + }, + { + "label": "fiatFlatFee", + "offset": 0, + "slot": "421", + "type": "t_uint256", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:79" + }, + { + "label": "redeemRequests", + "offset": 0, + "slot": "422", + "type": "t_mapping(t_uint256,t_struct(Request)20366_storage)", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:84" + }, + { + "label": "requestRedeemer", + "offset": 0, + "slot": "423", + "type": "t_address", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:89" + }, + { + "label": "__gap", + "offset": 0, + "slot": "424", + "type": "t_array(t_uint256)50_storage", + "contract": "RedemptionVault", + "src": "contracts/RedemptionVault.sol:94" + }, + { + "label": "___gap", + "offset": 0, + "slot": "474", + "type": "t_array(t_uint256)50_storage", + "contract": "RedemptionVaultWithSwapper", + "src": "contracts/RedemptionVaultWithSwapper.sol:34" + }, + { + "label": "mTbillRedemptionVault", + "offset": 0, + "slot": "524", + "type": "t_contract(IRedemptionVault)20603", + "contract": "RedemptionVaultWithSwapper", + "src": "contracts/RedemptionVaultWithSwapper.sol:41" + }, + { + "label": "liquidityProvider", + "offset": 0, + "slot": "525", + "type": "t_address", + "contract": "RedemptionVaultWithSwapper", + "src": "contracts/RedemptionVaultWithSwapper.sol:43" + }, + { + "label": "__gap", + "offset": 0, + "slot": "526", + "type": "t_array(t_uint256)50_storage", + "contract": "RedemptionVaultWithSwapper", + "src": "contracts/RedemptionVaultWithSwapper.sol:48" + }, + { + "label": "__gap", + "offset": 0, + "slot": "576", + "type": "t_array(t_uint256)50_storage", + "contract": "MGloRedemptionVaultWithSwapper", + "src": "contracts/products/mGLO/MGloRedemptionVaultWithSwapper.sol:19" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes4": { + "label": "bytes4", + "numberOfBytes": "4" + }, + "t_contract(IDataFeed)19792": { + "label": "contract IDataFeed", + "numberOfBytes": "20" + }, + "t_contract(IMToken)20085": { + "label": "contract IMToken", + "numberOfBytes": "20" + }, + "t_contract(IRedemptionVault)20603": { + "label": "contract IRedemptionVault", + "numberOfBytes": "20" + }, + "t_contract(MidasAccessControl)17280": { + "label": "contract MidasAccessControl", + "numberOfBytes": "20" + }, + "t_enum(RequestStatus)20102": { + "label": "enum RequestStatus", + "members": ["Pending", "Processed", "Canceled"], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(TokenConfig)20098_storage)": { + "label": "mapping(address => struct TokenConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes4,t_bool)": { + "label": "mapping(bytes4 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(Request)20366_storage)": { + "label": "mapping(uint256 => struct Request)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)3891_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)3576_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)8079_storage": { + "label": "struct Counters.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Request)20366_storage": { + "label": "struct Request", + "members": [ + { + "label": "sender", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "tokenOut", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "status", + "type": "t_enum(RequestStatus)20102", + "offset": 20, + "slot": "1" + }, + { + "label": "amountMToken", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "mTokenRate", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "tokenOutRate", + "type": "t_uint256", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_struct(Set)3576_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(TokenConfig)20098_storage": { + "label": "struct TokenConfig", + "members": [ + { + "label": "dataFeed", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "fee", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "allowance", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "stable", + "type": "t_bool", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } } } } diff --git a/ROLES.md b/ROLES.md index 6ab88016..6b037ed7 100644 --- a/ROLES.md +++ b/ROLES.md @@ -933,6 +933,7 @@ All the roles for the Midas protocol smart contracts are listed below. | **_depositVaultAdmin_** | `0x5e436b58093eec5c9bb0bf88a6e66765dc4e9b5fd9716a7da608c87d5f7b02bf` | | **_redemptionVaultAdmin_** | `0xf1f308589835b1481efea09554e2f72656725481c02aa4aaadab93e7045e5a53` | | **_greenlisted_** | `0xc7021654dd5cc2dc098b5f088ed0c6a046d28f5eaf2f8d0008903e2b8520acb2` | +| **_minBalanceExempt_** | `0x23e8bb4597bf064a5742e658e9f935a80bebc3543fc490fd89c0e3a5dcb527ab` | ### qHVNUSD Roles @@ -981,3 +982,16 @@ All the roles for the Midas protocol smart contracts are listed below. | **_depositVaultAdmin_** | `0x0f6a147de8cdfffe171daade1586996aefc68576e55cdec9b77b1e419cac1ce1` | | **_redemptionVaultAdmin_** | `0x5f1f545d6af70010d52bd3c02c8e59828c88b7384e5388ce3b2902ad63da8a43` | | **_greenlisted_** | `0xd4cee0f1caf45844ac124a181a38faea72039907def2059414beb261dc86eb67` | + +### mGLOeuro Roles + +| Role Name | Role | +| -------------------------- | -------------------------------------------------------------------- | +| **_minter_** | `0x29b52ad8b5aa9311f7fde983a723c52e504b21cb5c9bbd739a967c9e0cd715ee` | +| **_burner_** | `0x070ba01379eed310ddfca9075a00e040cc41c90d55c2a52e697ce7473e0b499f` | +| **_pauser_** | `0x96e4a6769d73f487be8632f4c7283e8a7bd60f67e46f1c472960bfe52eb1d698` | +| **_customFeedAdmin_** | `0x8cfa174b39696c5fd7407caa7ed3dd721ef06ed4f345ca9e3827d221f9bbf4da` | +| **_depositVaultAdmin_** | `0xbea31e5142f943ef913e5422e0e9a586e9d0303ccd63e0eead6b4189eb11cb27` | +| **_redemptionVaultAdmin_** | `0x0fada8a7f8ae9df6a95f6bc75af039633867da152deda5fb8ade34998ef95ae8` | +| **_greenlisted_** | `0x49a103d47daa98d445728ba0f2e848dccbbd73dea56729c961450eeb09890acc` | +| **_minBalanceExempt_** | `0x4c160e777147efaadeec64143e540a0cb03c77322c06777f48cab5184c7a2a03` | diff --git a/config/constants/addresses.ts b/config/constants/addresses.ts index 9a3beb49..5e75eac5 100644 --- a/config/constants/addresses.ts +++ b/config/constants/addresses.ts @@ -105,6 +105,25 @@ export const midasAddressesPerNetwork: ConfigPerNetwork< token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', dataFeed: '0x3aAc6fd73fA4e16Ec683BD4aaF5Ec89bb2C0EdC2', }, + eurc: { + token: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c', + numerator: { + // Chainlink EURC/USD: USD per EURC, 8 decimals. + aggregator: '0x04F84020Fdf10d9ee64D1dcC2986EDF2F556DA11', + dataFeed: '0x2A2895f343CA5a5E3d81A53e4C959367EB339f11', + }, + denominator: { + // Chainlink EUR/USD: USD per EUR, 8 decimals. + aggregator: '0xb49f677943BC038e9857d61E7d053CaA2C1734C1', + dataFeed: '0xFBD70c323238B5EDA94b6DEfFc5dB8c70F1f0e6D', + }, + dataFeed: '0x04DbaB674457A696cd4884c2E7c8cc6cA3118513', + }, + eurcv: { + token: '0x5F7827FDeb7c20b443265Fc2F40845B715385Ff2', + aggregator: '0xD9795cDFb09f1c599e16f6b150f642eC529b7aBB', + dataFeed: '0x4D88E4a32cf289ECfA6C9303B57aF0b6c132a733', + }, rlusd: { aggregator: '0x26C46B7aD0012cA71F2298ada567dC9Af14E7f2A', token: '0x8292Bb45bf1Ee4d140127049757C2E0fF06317eD', @@ -646,6 +665,16 @@ export const midasAddressesPerNetwork: ConfigPerNetwork< depositVault: '0xcEDCa505839c01Cc4FbE65496AA3Bb03B8ff98Ed', redemptionVaultSwapper: '0x7F72AA0339b4944E1A77df847168AE1936c1EBBB', }, + mGLOeuro: { + token: '0x2F8bae126b8d416C009b82Ccd60e4f9cAbbd7702', + customFeed: '0x8B7B1689396ef4468df1c0faB181D29B47089609', + customFeedDv: '0x165a52261202c0e32F70dE8eD715A1e3cF9a228c', + customFeedRv: '0x0f46c1E4F51B92c2966BEA4bd5A52E71E423D6e0', + dataFeedDv: '0xc27Cd7c0203B73933dcbcF7bdB4cC5D7bDA80A64', + dataFeedRv: '0xF76653eBc3e47B80e89F0e5b9CF6A5CAaB848946', + depositVaultMorpho: '0xE0Dcf20b0460e1f9222528F3997F9D71Ad6375C5', + redemptionVaultMorpho: '0x1E9f04408FF38d1FB70CE939fC87D26D61F6F758', + }, }, arbitrum: { accessControl: '0x0312A9D1Ff2372DDEdCBB21e4B6389aFc919aC4B', @@ -1598,6 +1627,16 @@ export const midasAddressesPerNetwork: ConfigPerNetwork< depositVault: '0x97b30c9D53A010009136b830f8A12f8d5624Bc43', redemptionVaultSwapper: '0x12Ae90dCe5C2a4Ee5141FBfc408ff1022D051F42', }, + mGLO: { + token: '0x1Eaf7cceAA5DC6d605760718722fbcC58579b3Ff', + customFeed: '0x4c8f5CEd7F3211ad1D70B6F33e585309Fb1AcA75', + customFeedDv: '0x116fc90020456f33C801850e89a05a7edCd04673', + customFeedRv: '0x02E16Cc29B99A27725E77976400dDf204c295705', + dataFeedDv: '0xAf91e7754E12c90F3F07a1719E39a65Fc58204ff', + dataFeedRv: '0xB391486b0d058176A845aCcF2Cb160A2be9CfCb8', + depositVault: '0xa5d0E53059b648B7615B67602De4489079EAe2B4', + redemptionVaultSwapper: '0x99656E9753047F2769D10fa65B9D8ebfE44B35ab', + }, }, robinhood: { accessControl: '0xe5F087203F9e7A6104c821ec25b1F0a4505D3cb5', diff --git a/config/types/tokens.ts b/config/types/tokens.ts index bbe16ecb..5260dee7 100644 --- a/config/types/tokens.ts +++ b/config/types/tokens.ts @@ -80,6 +80,7 @@ export enum MTokenNameEnum { sGold = 'sGold', turtlePST = 'turtlePST', mM1BTC = 'mM1BTC', + mGLOeuro = 'mGLOeuro', } export type MTokenName = keyof typeof MTokenNameEnum; @@ -136,6 +137,7 @@ export enum PaymentTokenNameEnum { winj = 'winj', yinj = 'yinj', eurc = 'eurc', + eurcv = 'eurcv', usdg = 'usdg', pyusd = 'pyusd', ausd = 'ausd', diff --git a/contracts/RedemptionVault.sol b/contracts/RedemptionVault.sol index 8f5762a2..fb031262 100644 --- a/contracts/RedemptionVault.sol +++ b/contracts/RedemptionVault.sol @@ -615,7 +615,6 @@ contract RedemptionVault is ManageableVault, IRedemptionVault { _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut); - mToken.burn(user, calcResult.amountMTokenWithoutFee); if (calcResult.feeAmount > 0) _tokenTransferFromUser( address(mToken), @@ -623,6 +622,7 @@ contract RedemptionVault is ManageableVault, IRedemptionVault { calcResult.feeAmount, 18 ); + mToken.burn(user, calcResult.amountMTokenWithoutFee); _tokenTransferToUser( tokenOutCopy, @@ -682,12 +682,6 @@ contract RedemptionVault is ManageableVault, IRedemptionVault { uint256 mTokenRate = mTokenDataFeed.getDataInBase18(); - _tokenTransferFromUser( - address(mToken), - address(this), - calcResult.amountMTokenWithoutFee, - 18 // mToken always have 18 decimals - ); if (calcResult.feeAmount > 0) _tokenTransferFromUser( address(mToken), @@ -695,6 +689,12 @@ contract RedemptionVault is ManageableVault, IRedemptionVault { calcResult.feeAmount, 18 ); + _tokenTransferFromUser( + address(mToken), + address(this), + calcResult.amountMTokenWithoutFee, + 18 // mToken always have 18 decimals + ); requestId = currentRequestId.current(); currentRequestId.increment(); diff --git a/contracts/RedemptionVaultWithAave.sol b/contracts/RedemptionVaultWithAave.sol index 2ac74b85..bd81544e 100644 --- a/contracts/RedemptionVaultWithAave.sol +++ b/contracts/RedemptionVaultWithAave.sol @@ -129,7 +129,6 @@ contract RedemptionVaultWithAave is RedemptionVault { _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut); - mToken.burn(user, calcResult.amountMTokenWithoutFee); if (calcResult.feeAmount > 0) _tokenTransferFromUser( address(mToken), @@ -137,6 +136,7 @@ contract RedemptionVaultWithAave is RedemptionVault { calcResult.feeAmount, 18 ); + mToken.burn(user, calcResult.amountMTokenWithoutFee); uint256 amountTokenOutWithoutFeeFrom18 = ((calcResult .amountMTokenWithoutFee * mTokenRate) / tokenOutRate) diff --git a/contracts/RedemptionVaultWithBUIDL.sol b/contracts/RedemptionVaultWithBUIDL.sol index 488ce356..d9202b18 100644 --- a/contracts/RedemptionVaultWithBUIDL.sol +++ b/contracts/RedemptionVaultWithBUIDL.sol @@ -164,7 +164,6 @@ contract RedemptionVaultWIthBUIDL is RedemptionVault { _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut); - mToken.burn(user, calcResult.amountMTokenWithoutFee); if (calcResult.feeAmount > 0) _tokenTransferFromUser( address(mToken), @@ -172,6 +171,7 @@ contract RedemptionVaultWIthBUIDL is RedemptionVault { calcResult.feeAmount, 18 ); + mToken.burn(user, calcResult.amountMTokenWithoutFee); amountTokenOutWithoutFee = (calcResult.amountMTokenWithoutFee * mTokenRate) / diff --git a/contracts/RedemptionVaultWithMToken.sol b/contracts/RedemptionVaultWithMToken.sol index 7fa9024d..ad78e706 100644 --- a/contracts/RedemptionVaultWithMToken.sol +++ b/contracts/RedemptionVaultWithMToken.sol @@ -162,7 +162,6 @@ contract RedemptionVaultWithMToken is RedemptionVault { _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut); - mToken.burn(user, calcResult.amountMTokenWithoutFee); if (calcResult.feeAmount > 0) _tokenTransferFromUser( address(mToken), @@ -170,6 +169,7 @@ contract RedemptionVaultWithMToken is RedemptionVault { calcResult.feeAmount, 18 ); + mToken.burn(user, calcResult.amountMTokenWithoutFee); uint256 amountTokenOutWithoutFeeFrom18 = ((calcResult .amountMTokenWithoutFee * mTokenRate) / tokenOutRate) diff --git a/contracts/RedemptionVaultWithMorpho.sol b/contracts/RedemptionVaultWithMorpho.sol index bb4db3d8..55a714d6 100644 --- a/contracts/RedemptionVaultWithMorpho.sol +++ b/contracts/RedemptionVaultWithMorpho.sol @@ -133,7 +133,6 @@ contract RedemptionVaultWithMorpho is RedemptionVault { _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut); - mToken.burn(user, calcResult.amountMTokenWithoutFee); if (calcResult.feeAmount > 0) _tokenTransferFromUser( address(mToken), @@ -141,6 +140,7 @@ contract RedemptionVaultWithMorpho is RedemptionVault { calcResult.feeAmount, 18 ); + mToken.burn(user, calcResult.amountMTokenWithoutFee); uint256 amountTokenOutWithoutFeeFrom18 = ((calcResult .amountMTokenWithoutFee * mTokenRate) / tokenOutRate) diff --git a/contracts/RedemptionVaultWithUSTB.sol b/contracts/RedemptionVaultWithUSTB.sol index 16905126..60a31675 100644 --- a/contracts/RedemptionVaultWithUSTB.sol +++ b/contracts/RedemptionVaultWithUSTB.sol @@ -130,7 +130,6 @@ contract RedemptionVaultWithUSTB is RedemptionVault { _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut); - mToken.burn(user, calcResult.amountMTokenWithoutFee); if (calcResult.feeAmount > 0) _tokenTransferFromUser( address(mToken), @@ -138,6 +137,7 @@ contract RedemptionVaultWithUSTB is RedemptionVault { calcResult.feeAmount, 18 ); + mToken.burn(user, calcResult.amountMTokenWithoutFee); uint256 amountTokenOutWithoutFeeFrom18 = ((calcResult .amountMTokenWithoutFee * mTokenRate) / tokenOutRate) diff --git a/contracts/mTokenMinBalance.sol b/contracts/mTokenMinBalance.sol new file mode 100644 index 00000000..f2755b61 --- /dev/null +++ b/contracts/mTokenMinBalance.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "./mToken.sol"; + +/** + * @title mTokenMinBalance + * @notice mToken with minimum balance checks + * @author RedDuck Software + */ +//solhint-disable contract-name-camelcase +abstract contract mTokenMinBalance is mToken { + /** + * @dev leaving a storage gap for futures updates + */ + uint256[50] private __gap; + + /** + * @dev overrides _afterTokenTransfer function to check if the recipient has a minimum balance + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override { + if (from != address(0) && !_isMinBalanceExempt(from)) { + _validateMinBalance(from); + } + + if (to != address(0) && !_isMinBalanceExempt(to)) { + _validateMinBalance(to); + } + + super._afterTokenTransfer(from, to, amount); + } + + /** + * @dev returns the role holder of which is exempt from min balance checks + */ + function _minBalanceExemptRole() internal view virtual returns (bytes32); + + /** + * @dev checks if a user is exempt from min balance checks + * @param user address of the user + * @return bool true if the user is exempt from min balance checks + */ + function _isMinBalanceExempt(address user) private view returns (bool) { + return accessControl.hasRole(_minBalanceExemptRole(), user); + } + + /** + * @dev validates the minimum balance of a user + * @param user address of the user + */ + function _validateMinBalance(address user) private view { + uint256 balance = balanceOf(user); + require( + balance == 0 || balance >= 1 ether, + "MTMB: min balance not met" + ); + } +} diff --git a/contracts/mTokenPermissionedMinBalance.sol b/contracts/mTokenPermissionedMinBalance.sol new file mode 100644 index 00000000..9806a432 --- /dev/null +++ b/contracts/mTokenPermissionedMinBalance.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "./mTokenPermissioned.sol"; +import "./mTokenMinBalance.sol"; + +/** + * @title mTokenPermissionedMinBalance + * @notice mToken with permissioned transfers and minimum balance checks + * @author RedDuck Software + */ +//solhint-disable contract-name-camelcase +abstract contract mTokenPermissionedMinBalance is + mTokenMinBalance, + mTokenPermissioned +{ + // no gap as we would upgrade some of the deployments + // to mTokenMinBalance + + /** + * @dev overrides _beforeTokenTransfer function to call the parent hooks + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override(mTokenPermissioned, mToken) { + mTokenPermissioned._beforeTokenTransfer(from, to, amount); + } + + /** + * @dev overrides _afterTokenTransfer function to call the parent hooks + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override(mTokenMinBalance, ERC20Upgradeable) { + mTokenMinBalance._afterTokenTransfer(from, to, amount); + } +} diff --git a/contracts/products/mGLOeuro/MGloEuroCustomAggregatorFeed.sol b/contracts/products/mGLOeuro/MGloEuroCustomAggregatorFeed.sol new file mode 100644 index 00000000..7f0b106b --- /dev/null +++ b/contracts/products/mGLOeuro/MGloEuroCustomAggregatorFeed.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "../../feeds/CustomAggregatorV3CompatibleFeed.sol"; +import "./MGloEuroMidasAccessControlRoles.sol"; + +/** + * @title MGloEuroCustomAggregatorFeed + * @notice AggregatorV3 compatible feed for mGLOeuro, + * where price is submitted manually by feed admins + * @author RedDuck Software + */ +contract MGloEuroCustomAggregatorFeed is + CustomAggregatorV3CompatibleFeed, + MGloEuroMidasAccessControlRoles +{ + /** + * @dev leaving a storage gap for futures updates + */ + uint256[50] private __gap; + + /** + * @inheritdoc CustomAggregatorV3CompatibleFeed + */ + function feedAdminRole() public pure override returns (bytes32) { + return M_GLO_EURO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE; + } +} diff --git a/contracts/products/mGLOeuro/MGloEuroDataFeed.sol b/contracts/products/mGLOeuro/MGloEuroDataFeed.sol new file mode 100644 index 00000000..7c8f2857 --- /dev/null +++ b/contracts/products/mGLOeuro/MGloEuroDataFeed.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "../../feeds/DataFeed.sol"; +import "./MGloEuroMidasAccessControlRoles.sol"; + +/** + * @title MGloEuroDataFeed + * @notice DataFeed for mGLOeuro product + * @author RedDuck Software + */ +contract MGloEuroDataFeed is DataFeed, MGloEuroMidasAccessControlRoles { + /** + * @dev leaving a storage gap for futures updates + */ + uint256[50] private __gap; + + /** + * @inheritdoc DataFeed + */ + function feedAdminRole() public pure override returns (bytes32) { + return M_GLO_EURO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE; + } +} diff --git a/contracts/products/mGLOeuro/MGloEuroDepositVaultWithMorpho.sol b/contracts/products/mGLOeuro/MGloEuroDepositVaultWithMorpho.sol new file mode 100644 index 00000000..4a3349a1 --- /dev/null +++ b/contracts/products/mGLOeuro/MGloEuroDepositVaultWithMorpho.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "../../DepositVaultWithMorpho.sol"; +import "./MGloEuroMidasAccessControlRoles.sol"; + +/** + * @title MGloEuroDepositVaultWithMorpho + * @notice Smart contract that handles mGLOeuro minting with Morpho auto-invest + * @author RedDuck Software + */ +contract MGloEuroDepositVaultWithMorpho is + DepositVaultWithMorpho, + MGloEuroMidasAccessControlRoles +{ + /** + * @dev leaving a storage gap for futures updates + */ + uint256[50] private __gap; + + /** + * @inheritdoc ManageableVault + */ + function vaultRole() public pure override returns (bytes32) { + return M_GLO_EURO_DEPOSIT_VAULT_ADMIN_ROLE; + } + + /** + * @inheritdoc Greenlistable + */ + function greenlistedRole() public pure override returns (bytes32) { + return M_GLOBAL_GREENLISTED_ROLE; + } +} diff --git a/contracts/products/mGLOeuro/MGloEuroMidasAccessControlRoles.sol b/contracts/products/mGLOeuro/MGloEuroMidasAccessControlRoles.sol new file mode 100644 index 00000000..43b8f2ed --- /dev/null +++ b/contracts/products/mGLOeuro/MGloEuroMidasAccessControlRoles.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +/** + * @title MGloEuroMidasAccessControlRoles + * @notice Base contract that stores all roles descriptors for mGLOeuro contracts + * @author RedDuck Software + */ +abstract contract MGloEuroMidasAccessControlRoles { + /** + * @notice actor that can manage MGloEuroDepositVault + */ + bytes32 public constant M_GLO_EURO_DEPOSIT_VAULT_ADMIN_ROLE = + keccak256("M_GLO_EURO_DEPOSIT_VAULT_ADMIN_ROLE"); + + /** + * @notice actor that can manage MGloEuroRedemptionVault + */ + bytes32 public constant M_GLO_EURO_REDEMPTION_VAULT_ADMIN_ROLE = + keccak256("M_GLO_EURO_REDEMPTION_VAULT_ADMIN_ROLE"); + + /** + * @notice actor that can manage MGloEuroCustomAggregatorFeed and MGloEuroDataFeed + */ + bytes32 public constant M_GLO_EURO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE = + keccak256("M_GLO_EURO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE"); + + /** + * @notice greenlist role for mGLOeuro + */ + bytes32 public constant M_GLOBAL_GREENLISTED_ROLE = + keccak256("M_GLOBAL_GREENLISTED_ROLE"); +} diff --git a/contracts/products/mGLOeuro/MGloEuroRedemptionVaultWithMorpho.sol b/contracts/products/mGLOeuro/MGloEuroRedemptionVaultWithMorpho.sol new file mode 100644 index 00000000..867a369e --- /dev/null +++ b/contracts/products/mGLOeuro/MGloEuroRedemptionVaultWithMorpho.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "../../RedemptionVaultWithMorpho.sol"; +import "./MGloEuroMidasAccessControlRoles.sol"; + +/** + * @title MGloEuroRedemptionVaultWithMorpho + * @notice Smart contract that handles mGLOeuro redemptions via Morpho Vault + * @author RedDuck Software + */ +contract MGloEuroRedemptionVaultWithMorpho is + RedemptionVaultWithMorpho, + MGloEuroMidasAccessControlRoles +{ + /** + * @dev leaving a storage gap for futures updates + */ + uint256[50] private __gap; + + /** + * @inheritdoc ManageableVault + */ + function vaultRole() public pure override returns (bytes32) { + return M_GLO_EURO_REDEMPTION_VAULT_ADMIN_ROLE; + } + + /** + * @inheritdoc Greenlistable + */ + function greenlistedRole() public pure override returns (bytes32) { + return M_GLOBAL_GREENLISTED_ROLE; + } +} diff --git a/contracts/products/mGLOeuro/mGLOeuro.sol b/contracts/products/mGLOeuro/mGLOeuro.sol new file mode 100644 index 00000000..00455b31 --- /dev/null +++ b/contracts/products/mGLOeuro/mGLOeuro.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "../../mTokenMinBalance.sol"; + +/** + * @title mGLOeuro + * @author RedDuck Software + */ +//solhint-disable contract-name-camelcase +contract mGLOeuro is mTokenMinBalance { + /** + * @notice actor that can mint mGLOeuro + */ + bytes32 public constant M_GLO_EURO_MINT_OPERATOR_ROLE = + keccak256("M_GLO_EURO_MINT_OPERATOR_ROLE"); + + /** + * @notice actor that can burn mGLOeuro + */ + bytes32 public constant M_GLO_EURO_BURN_OPERATOR_ROLE = + keccak256("M_GLO_EURO_BURN_OPERATOR_ROLE"); + + /** + * @notice actor that can pause mGLOeuro + */ + bytes32 public constant M_GLO_EURO_PAUSE_OPERATOR_ROLE = + keccak256("M_GLO_EURO_PAUSE_OPERATOR_ROLE"); + + /** + * @notice actor that is exempt from mGLOeuro min balance checks + */ + bytes32 public constant M_GLO_EURO_MIN_BALANCE_EXEMPT_ROLE = + keccak256("M_GLO_EURO_MIN_BALANCE_EXEMPT_ROLE"); + + /** + * @dev leaving a storage gap for futures updates + */ + uint256[50] private __gap; + + /** + * @inheritdoc mToken + */ + function _getNameSymbol() + internal + pure + override + returns (string memory, string memory) + { + return ("Midas Fasanara Global Euro", "mGLOeuro"); + } + + /** + * @dev AC role, owner of which can mint mGLOeuro token + */ + function _minterRole() internal pure override returns (bytes32) { + return M_GLO_EURO_MINT_OPERATOR_ROLE; + } + + /** + * @dev AC role, owner of which can burn mGLOeuro token + */ + function _burnerRole() internal pure override returns (bytes32) { + return M_GLO_EURO_BURN_OPERATOR_ROLE; + } + + /** + * @dev AC role, owner of which can pause mGLOeuro token + */ + function _pauserRole() internal pure override returns (bytes32) { + return M_GLO_EURO_PAUSE_OPERATOR_ROLE; + } + + /** + * @inheritdoc mTokenMinBalance + */ + function _minBalanceExemptRole() internal pure override returns (bytes32) { + return M_GLO_EURO_MIN_BALANCE_EXEMPT_ROLE; + } +} diff --git a/contracts/products/mWIN/MWinCustomAggregatorFeed.sol b/contracts/products/mWIN/MWinCustomAggregatorFeed.sol index 5744ec2b..f1199c01 100644 --- a/contracts/products/mWIN/MWinCustomAggregatorFeed.sol +++ b/contracts/products/mWIN/MWinCustomAggregatorFeed.sol @@ -19,6 +19,21 @@ contract MWinCustomAggregatorFeed is */ uint256[50] private __gap; + /** + * @notice reinitialize the feed with new min and max answer + * @param _newMinAnswer The new min answer + * @param _newMaxAnswer The new max answer + */ + function initializeV2(int192 _newMinAnswer, int192 _newMaxAnswer) + external + reinitializer(2) + { + require(_newMinAnswer < _newMaxAnswer, "CA: !min/max"); + + minAnswer = _newMinAnswer; + maxAnswer = _newMaxAnswer; + } + /** * @inheritdoc CustomAggregatorV3CompatibleFeed */ diff --git a/contracts/products/mWIN/mWIN.sol b/contracts/products/mWIN/mWIN.sol index c31efe28..d00bd219 100644 --- a/contracts/products/mWIN/mWIN.sol +++ b/contracts/products/mWIN/mWIN.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.9; -import "../../mTokenPermissioned.sol"; +import "../../mTokenPermissionedMinBalance.sol"; import "./MWinMidasAccessControlRoles.sol"; /** @@ -9,7 +9,7 @@ import "./MWinMidasAccessControlRoles.sol"; * @author RedDuck Software */ //solhint-disable contract-name-camelcase -contract mWIN is mTokenPermissioned, MWinMidasAccessControlRoles { +contract mWIN is mTokenPermissionedMinBalance, MWinMidasAccessControlRoles { /** * @notice actor that can mint mWIN */ @@ -29,9 +29,13 @@ contract mWIN is mTokenPermissioned, MWinMidasAccessControlRoles { keccak256("M_WIN_PAUSE_OPERATOR_ROLE"); /** - * @dev leaving a storage gap for futures updates + * @notice actor that is exempt from mWIN min balance checks */ - uint256[50] private __gap; + bytes32 public constant M_WIN_MIN_BALANCE_EXEMPT_ROLE = + keccak256("M_WIN_MIN_BALANCE_EXEMPT_ROLE"); + + // no gap as we would upgrade some of the deployments + // to mTokenMinBalance /** * @inheritdoc mToken @@ -72,4 +76,11 @@ contract mWIN is mTokenPermissioned, MWinMidasAccessControlRoles { function _greenlistedRole() internal pure override returns (bytes32) { return M_WIN_GREENLISTED_ROLE; } + + /** + * @inheritdoc mTokenMinBalance + */ + function _minBalanceExemptRole() internal pure override returns (bytes32) { + return M_WIN_MIN_BALANCE_EXEMPT_ROLE; + } } diff --git a/contracts/testers/mTokenMinBalanceTest.sol b/contracts/testers/mTokenMinBalanceTest.sol new file mode 100644 index 00000000..bece14c1 --- /dev/null +++ b/contracts/testers/mTokenMinBalanceTest.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "../mTokenMinBalance.sol"; + +//solhint-disable contract-name-camelcase +contract mTokenMinBalanceTest is mTokenMinBalance { + bytes32 public constant M_TOKEN_TEST_MINT_OPERATOR_ROLE = + keccak256("M_TOKEN_TEST_MINT_OPERATOR_ROLE"); + + bytes32 public constant M_TOKEN_TEST_BURN_OPERATOR_ROLE = + keccak256("M_TOKEN_TEST_BURN_OPERATOR_ROLE"); + + bytes32 public constant M_TOKEN_TEST_PAUSE_OPERATOR_ROLE = + keccak256("M_TOKEN_TEST_PAUSE_OPERATOR_ROLE"); + + bytes32 public constant M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE = + keccak256("M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE"); + + function _disableInitializers() internal override {} + + function _getNameSymbol() + internal + pure + override + returns (string memory, string memory) + { + return ("mTokenMinBalanceTest", "mTokenMinBalanceTest"); + } + + function _minterRole() internal pure override returns (bytes32) { + return M_TOKEN_TEST_MINT_OPERATOR_ROLE; + } + + function _burnerRole() internal pure override returns (bytes32) { + return M_TOKEN_TEST_BURN_OPERATOR_ROLE; + } + + function _pauserRole() internal pure override returns (bytes32) { + return M_TOKEN_TEST_PAUSE_OPERATOR_ROLE; + } + + function _minBalanceExemptRole() internal pure override returns (bytes32) { + return M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE; + } +} diff --git a/contracts/testers/mTokenPermissionedMinBalanceTest.sol b/contracts/testers/mTokenPermissionedMinBalanceTest.sol new file mode 100644 index 00000000..0a5b9244 --- /dev/null +++ b/contracts/testers/mTokenPermissionedMinBalanceTest.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "../mTokenPermissionedMinBalance.sol"; + +//solhint-disable contract-name-camelcase +contract mTokenPermissionedMinBalanceTest is mTokenPermissionedMinBalance { + bytes32 public constant M_TOKEN_TEST_MINT_OPERATOR_ROLE = + keccak256("M_TOKEN_TEST_MINT_OPERATOR_ROLE"); + + bytes32 public constant M_TOKEN_TEST_BURN_OPERATOR_ROLE = + keccak256("M_TOKEN_TEST_BURN_OPERATOR_ROLE"); + + bytes32 public constant M_TOKEN_TEST_PAUSE_OPERATOR_ROLE = + keccak256("M_TOKEN_TEST_PAUSE_OPERATOR_ROLE"); + + bytes32 public constant M_TOKEN_TEST_GREENLISTED_ROLE = + keccak256("M_TOKEN_TEST_GREENLISTED_ROLE"); + + bytes32 public constant M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE = + keccak256("M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE"); + + function _disableInitializers() internal override {} + + function _getNameSymbol() + internal + pure + override + returns (string memory, string memory) + { + return ( + "mTokenPermissionedMinBalanceTest", + "mTokenPermissionedMinBalanceTest" + ); + } + + function _minterRole() internal pure override returns (bytes32) { + return M_TOKEN_TEST_MINT_OPERATOR_ROLE; + } + + function _burnerRole() internal pure override returns (bytes32) { + return M_TOKEN_TEST_BURN_OPERATOR_ROLE; + } + + function _pauserRole() internal pure override returns (bytes32) { + return M_TOKEN_TEST_PAUSE_OPERATOR_ROLE; + } + + function _greenlistedRole() internal pure override returns (bytes32) { + return M_TOKEN_TEST_GREENLISTED_ROLE; + } + + function _minBalanceExemptRole() internal pure override returns (bytes32) { + return M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE; + } +} diff --git a/helpers/contracts.ts b/helpers/contracts.ts index c0bbfac0..51f4a742 100644 --- a/helpers/contracts.ts +++ b/helpers/contracts.ts @@ -144,6 +144,7 @@ export const contractNamesPrefixes: Record = { sGold: 'SGold', turtlePST: 'TurtlePst', mM1BTC: 'MM1Btc', + mGLOeuro: 'MGloEuro', }; export const getCommonContractNames = (): CommonContractNames => { diff --git a/helpers/mtokens-metadata.ts b/helpers/mtokens-metadata.ts index 149644dc..bee621b2 100644 --- a/helpers/mtokens-metadata.ts +++ b/helpers/mtokens-metadata.ts @@ -2,7 +2,12 @@ import { MTokenName } from '../config'; export const mTokensMetadata: Record< MTokenName, - { name: string; symbol: string; isPermissioned?: boolean } + { + name: string; + symbol: string; + isPermissioned?: boolean; + isMinBalance?: boolean; + } > = { mTBILL: { name: 'Midas US Treasury Bill Token', @@ -310,6 +315,7 @@ export const mTokensMetadata: Record< name: 'Midas Wellington Income Opportunities', symbol: 'mWIN', isPermissioned: true, + isMinBalance: true, }, qHVNUSD: { name: 'Qapture Safe Haven', @@ -332,4 +338,9 @@ export const mTokensMetadata: Record< name: 'Midas M1 BTC Market Neutral', symbol: 'mM1-BTC', }, + mGLOeuro: { + name: 'Midas Fasanara Global Euro', + symbol: 'mGLOeuro', + isMinBalance: true, + }, }; diff --git a/helpers/roles.ts b/helpers/roles.ts index 2767f339..29d323ca 100644 --- a/helpers/roles.ts +++ b/helpers/roles.ts @@ -86,6 +86,7 @@ export const prefixes: Record = { sGold: 'S_GOLD', turtlePST: 'TURTLE_PST', mM1BTC: 'M_M1_BTC', + mGLOeuro: 'M_GLO_EURO', }; const mappedTokenNames: Partial> = { @@ -105,6 +106,7 @@ export const tokenLevelGreenlistTokens: MTokenName[] = [ 'mWIN', 'qHVNUSD', 'mGLO', + 'mGLOeuro', ]; /** @@ -118,6 +120,7 @@ export const sharedGreenlistRoleSource: Partial< Record > = { mGLO: 'mGLOBAL', + mGLOeuro: 'mGLOBAL', }; const getGreenlistRoleName = (token: MTokenName): string => { @@ -135,6 +138,7 @@ type TokenRoles = { redemptionVaultAdmin: string; customFeedAdmin: string | null; greenlisted: string; + minBalanceExempt: string; }; type CommonRoles = { @@ -176,6 +180,7 @@ export const getRolesNamesForToken = (token: MTokenName): TokenRoles => { depositVaultAdmin: `${restPrefix}DEPOSIT_VAULT_ADMIN_ROLE`, redemptionVaultAdmin: `${restPrefix}REDEMPTION_VAULT_ADMIN_ROLE`, greenlisted: getGreenlistRoleName(token), + minBalanceExempt: `${tokenPrefix}_MIN_BALANCE_EXEMPT_ROLE`, }; }; export const getRolesNamesCommon = (): CommonRoles => { diff --git a/scripts/deploy/codegen/common/index.ts b/scripts/deploy/codegen/common/index.ts index 2160829f..0e5ba7a5 100644 --- a/scripts/deploy/codegen/common/index.ts +++ b/scripts/deploy/codegen/common/index.ts @@ -46,6 +46,7 @@ import { getGenerationModeFromUser, getGreenlistRoleSourceFromUser, getShouldUseTokenLevelGreenListFromUser, + getShouldUseTokenMinBalanceFromUser, getShouldUseTokenPermissionedFromUser, getTokenContractNameFromUser, } from './ui/deployment-contracts'; @@ -110,6 +111,7 @@ export const updateConfigFiles = ( symbol, mToken, isPermissioned, + isMinBalance, useTokenLevelGreenList, greenlistRoleSource, }: { @@ -119,6 +121,7 @@ export const updateConfigFiles = ( symbol: string; mToken: string; isPermissioned?: true; + isMinBalance?: true; useTokenLevelGreenList?: boolean; greenlistRoleSource?: string; }, @@ -233,17 +236,20 @@ export const updateConfigFiles = ( ); if (!objLiteral.getProperty(mToken)) { + const flags = [ + isPermissioned ? 'isPermissioned: true' : undefined, + isMinBalance ? 'isMinBalance: true' : undefined, + ] + .filter(Boolean) + .map((flag) => `,\n ${flag}`) + .join(''); + objLiteral.addPropertyAssignment({ name: mToken, initializer: (writer) => writer.write(`{ name: '${name}', - symbol: '${symbol}'${ - isPermissioned - ? `, - isPermissioned: true` - : '' - } + symbol: '${symbol}'${flags} }`), }); } @@ -775,6 +781,7 @@ export const generateContracts = async (hre: HardhatRuntimeEnvironment) => { rolesPrefix: string; }; let isPermissionedFromMetadata = false; + let isMinBalanceFromMetadata = false; if (mode === 'create') { config = await getConfigFromUser(mToken); @@ -800,12 +807,14 @@ export const generateContracts = async (hre: HardhatRuntimeEnvironment) => { rolesPrefix, }; isPermissionedFromMetadata = !!metadata.isPermissioned; + isMinBalanceFromMetadata = !!metadata.isMinBalance; } const contractsToGenerate = await getContractsToGenerateFromUser(); let shouldUseTokenLevelGreenList = false; let shouldUseTokenPermissioned = isPermissionedFromMetadata; + let shouldUseTokenMinBalance = isMinBalanceFromMetadata; let greenlistRoleSource: string | undefined; if ( @@ -835,6 +844,7 @@ export const generateContracts = async (hre: HardhatRuntimeEnvironment) => { if (contractsToGenerate.includes('token') && mode === 'create') { shouldUseTokenPermissioned = await getShouldUseTokenPermissionedFromUser(); + shouldUseTokenMinBalance = await getShouldUseTokenMinBalanceFromUser(); } await tasks([ @@ -848,6 +858,7 @@ export const generateContracts = async (hre: HardhatRuntimeEnvironment) => { symbol: config.tokenSymbol, mToken: config.tokenContractName, isPermissioned: shouldUseTokenPermissioned ? true : undefined, + isMinBalance: shouldUseTokenMinBalance ? true : undefined, useTokenLevelGreenList: shouldUseTokenLevelGreenList, greenlistRoleSource, }); @@ -883,6 +894,7 @@ export const generateContracts = async (hre: HardhatRuntimeEnvironment) => { generator(mToken, { vaultUseTokenLevelGreenList: shouldUseTokenLevelGreenList, isPermissionedMToken: shouldUseTokenPermissioned, + isMinBalanceMToken: shouldUseTokenMinBalance, }), ), ); diff --git a/scripts/deploy/codegen/common/templates/mtoken.template.ts b/scripts/deploy/codegen/common/templates/mtoken.template.ts index c27ce66b..7a9b2c56 100644 --- a/scripts/deploy/codegen/common/templates/mtoken.template.ts +++ b/scripts/deploy/codegen/common/templates/mtoken.template.ts @@ -5,7 +5,8 @@ export const getTokenContractFromTemplate = async ( mToken: MTokenName, optionalParams?: Record, ) => { - const { isPermissionedMToken = false } = optionalParams || {}; + const { isPermissionedMToken = false, isMinBalanceMToken = false } = + optionalParams || {}; const { getTokenContractNames } = await importWithoutCache( require.resolve('../../../../../helpers/contracts'), ); @@ -23,13 +24,17 @@ export const getTokenContractFromTemplate = async ( const contractNames = getTokenContractNames(mToken); const roles = getRolesNamesForToken(mToken); + const mTokenBase = `mToken${isPermissionedMToken ? 'Permissioned' : ''}${ + isMinBalanceMToken ? 'MinBalance' : '' + }`; + return { name: contractNames.token, content: ` // SPDX-License-Identifier: MIT pragma solidity 0.8.9; - import "../../mToken${isPermissionedMToken ? 'Permissioned' : ''}.sol"; + import "../../${mTokenBase}.sol"; ${isPermissionedMToken ? `import "./${contractNames.roles}.sol";` : ''} /** @@ -37,8 +42,8 @@ export const getTokenContractFromTemplate = async ( * @author RedDuck Software */ //solhint-disable contract-name-camelcase - contract ${contractNames.token} is mToken${ - isPermissionedMToken ? `Permissioned, ${contractNames.roles}` : '' + contract ${contractNames.token} is ${mTokenBase}${ + isPermissionedMToken ? `, ${contractNames.roles}` : '' } { /** * @notice actor that can mint ${contractNames.token} @@ -58,10 +63,31 @@ export const getTokenContractFromTemplate = async ( bytes32 public constant ${roles.pauser} = keccak256("${roles.pauser}"); + ${ + isMinBalanceMToken + ? ` + /** + * @notice actor that is exempt from ${contractNames.token} min balance checks + */ + bytes32 public constant ${roles.minBalanceExempt} = + keccak256("${roles.minBalanceExempt}"); + ` + : '' + } + + ${ + isPermissionedMToken && isMinBalanceMToken + ? ` + // no gap as we would upgrade some of the deployments + // to mTokenMinBalance + ` + : ` /** * @dev leaving a storage gap for futures updates */ uint256[50] private __gap; + ` + } /** * @inheritdoc mToken @@ -107,6 +133,18 @@ export const getTokenContractFromTemplate = async ( }` : '' } + + ${ + isMinBalanceMToken + ? ` + /** + * @inheritdoc mTokenMinBalance + */ + function _minBalanceExemptRole() internal pure override returns (bytes32) { + return ${roles.minBalanceExempt}; + }` + : '' + } }`, }; }; diff --git a/scripts/deploy/codegen/common/templates/token-roles.template.ts b/scripts/deploy/codegen/common/templates/token-roles.template.ts index 1120bb96..63f47b82 100644 --- a/scripts/deploy/codegen/common/templates/token-roles.template.ts +++ b/scripts/deploy/codegen/common/templates/token-roles.template.ts @@ -5,7 +5,8 @@ export const getTokenRolesContractFromTemplate = async ( mToken: MTokenName, optionalParams?: Record, ) => { - const { vaultUseTokenLevelGreenList = false } = optionalParams || {}; + const { vaultUseTokenLevelGreenList = false, isPermissionedMToken = false } = + optionalParams || {}; const { getTokenContractNames } = await importWithoutCache( require.resolve('../../../../../helpers/contracts'), @@ -18,6 +19,9 @@ export const getTokenRolesContractFromTemplate = async ( const contractNames = getTokenContractNames(mToken); const roles = getRolesNamesForToken(mToken); + const includeGreenlistedRole = + vaultUseTokenLevelGreenList || isPermissionedMToken; + return { name: contractNames.roles, content: ` @@ -53,7 +57,7 @@ export const getTokenRolesContractFromTemplate = async ( keccak256("${roles.customFeedAdmin}"); ${ - vaultUseTokenLevelGreenList + includeGreenlistedRole ? ` /** * @notice greenlist role for ${contractNames.token} diff --git a/scripts/deploy/codegen/common/ui/deployment-contracts.ts b/scripts/deploy/codegen/common/ui/deployment-contracts.ts index d8b97ea5..4d378d14 100644 --- a/scripts/deploy/codegen/common/ui/deployment-contracts.ts +++ b/scripts/deploy/codegen/common/ui/deployment-contracts.ts @@ -174,6 +174,13 @@ export const getShouldUseTokenPermissionedFromUser = async () => { }).then(requireNotCancelled); }; +export const getShouldUseTokenMinBalanceFromUser = async () => { + return confirm({ + message: 'Should use min-balance mToken variant?', + initialValue: false, + }).then(requireNotCancelled); +}; + /** * Optionally reuse an existing product's greenlist role instead of minting a * token-specific one (e.g. mGLO reuses mGLOBAL's M_GLOBAL_GREENLISTED_ROLE). diff --git a/scripts/deploy/common/roles.ts b/scripts/deploy/common/roles.ts index 388c88d6..cc4dc410 100644 --- a/scripts/deploy/common/roles.ts +++ b/scripts/deploy/common/roles.ts @@ -26,6 +26,7 @@ export type GrantAllTokenRolesConfig = { tokenManagerAddress: Address; vaultsManagerAddress?: Address; oracleManagerAddress: Address; + minBalanceExemptAddresses?: Address[]; }; const acAdminAddress = '0xd4195CF4df289a4748C1A7B6dDBE770e27bA1227'; @@ -72,6 +73,11 @@ export const grantAllProductRoles = async ( ]; const oracleManagerRoles = [tokenRoles.customFeedAdmin!]; + const minBalanceExemptAddresses = + networkConfig.minBalanceExemptAddresses ?? []; + const minBalanceExemptRoles = minBalanceExemptAddresses.map( + () => tokenRoles.minBalanceExempt, + ); const defaultManager = provider.address; @@ -100,6 +106,7 @@ export const grantAllProductRoles = async ( ...tokenManagerRoles, ...vaultManagerRoles, ...oracleManagerRoles, + ...minBalanceExemptRoles, ...contractsRoles, ]; const grantAddresses = [ @@ -108,6 +115,7 @@ export const grantAllProductRoles = async ( () => networkConfig.vaultsManagerAddress ?? defaultManager, ), ...oracleManagerRoles.map(() => networkConfig.oracleManagerAddress), + ...minBalanceExemptAddresses, ...contractsAddresses, ]; diff --git a/scripts/deploy/common/timelock.ts b/scripts/deploy/common/timelock.ts index 9267616c..d76e5990 100644 --- a/scripts/deploy/common/timelock.ts +++ b/scripts/deploy/common/timelock.ts @@ -212,7 +212,7 @@ const validateSimulateContractUpgrade = async ( await increase(days(3)); await mine(); - const proxyAdmin = (await hre.upgrades.admin.getInstance()) as ProxyAdmin; + const proxyAdmin = await getProxyAdmin(hre); const proxyAdminOwner = await proxyAdmin.owner(); const timelock = await getTimelockContract(hre); @@ -446,7 +446,7 @@ const getUpgradeTx = async ( hre: HardhatRuntimeEnvironment, { newImplementation, proxyAddress, initializerCalldata }: GetUpgradeTxParams, ) => { - const admin = await hre.upgrades.admin.getInstance(); + const admin = await getProxyAdmin(hre); const upgradeCallData = initializerCalldata ? admin.interface.encodeFunctionData('upgradeAndCall', [ @@ -466,7 +466,7 @@ const getTransferOwnershipTx = async ( hre: HardhatRuntimeEnvironment, { newOwner }: { newOwner: string }, ) => { - const admin = await hre.upgrades.admin.getInstance(); + const admin = await getProxyAdmin(hre); const upgradeCallData = admin.interface.encodeFunctionData( 'transferOwnership', @@ -529,7 +529,6 @@ const proposeTimelockTx: PopulateTxFn = async ( if (isOperationExists) { throw new Error('Operation is already exists'); } - const tx = await timelockContract.populateTransaction.schedule( ...params, await timelockContract.getMinDelay(), @@ -558,7 +557,7 @@ const createUpgradeTimelockTx = async ( hre, salt, async (hre) => { - const admin = (await hre.upgrades.admin.getInstance()) as ProxyAdmin; + const admin = await getProxyAdmin(hre); const currentImpl = await admin.getProxyImplementation( params.proxyAddress, @@ -580,6 +579,7 @@ const createUpgradeTimelockTx = async ( // Runs even under skipValidation — this is the incident-preventing check. const { abi } = await hre.artifacts.readArtifact(params.contractName); + await assertReinitializerCovered({ provider: hre.ethers.provider, proxyAddress: params.proxyAddress, @@ -623,7 +623,7 @@ const createTransferOwnershipTimelockTx = async ( async (hre) => { const timelockContract = await getTimelockContract(hre); - const admin = (await hre.upgrades.admin.getInstance()) as ProxyAdmin; + const admin = await getProxyAdmin(hre); const currentOwner = await admin.owner(); @@ -668,7 +668,7 @@ const createTimeLockTx = async ( return false; } - const admin = (await hre.upgrades.admin.getInstance()) as ProxyAdmin; + const admin = await getProxyAdmin(hre); const networkAddresses = getCurrentAddresses(hre); @@ -747,6 +747,12 @@ const createTimeLockTx = async ( return true; }; +const getProxyAdmin = async (hre: HardhatRuntimeEnvironment) => { + return (await hre.upgrades.admin.getInstance()).connect( + hre.ethers.provider, + ) as ProxyAdmin; +}; + function parseRevertReason(data: string) { try { // 0x08c379a0 = Error(string) diff --git a/scripts/deploy/configs/index.ts b/scripts/deploy/configs/index.ts index 4f5aa1f5..2b9991bd 100644 --- a/scripts/deploy/configs/index.ts +++ b/scripts/deploy/configs/index.ts @@ -35,6 +35,7 @@ import { mFARMDeploymentConfig } from './mFARM'; import { mFONEDeploymentConfig } from './mFONE'; import { mGLODeploymentConfig } from './mGLO'; import { mGLOBALDeploymentConfig } from './mGLOBAL'; +import { mGLOeuroDeploymentConfig } from './mGLOeuro'; import { mHYPERDeploymentConfig } from './mHYPER'; import { mHyperBTCDeploymentConfig } from './mHyperBTC'; import { mHyperETHDeploymentConfig } from './mHyperETH'; @@ -165,4 +166,5 @@ export const configsPerToken: Record = { sGold: sGoldDeploymentConfig, turtlePST: turtlePSTDeploymentConfig, mM1BTC: mM1BTCDeploymentConfig, + mGLOeuro: mGLOeuroDeploymentConfig, }; diff --git a/scripts/deploy/configs/mGLO.ts b/scripts/deploy/configs/mGLO.ts index 23848512..ecf61a85 100644 --- a/scripts/deploy/configs/mGLO.ts +++ b/scripts/deploy/configs/mGLO.ts @@ -265,5 +265,77 @@ export const mGLODeploymentConfig: DeploymentConfig = { }, }, }, + [chainIds.optimism]: { + dv: { + type: 'REGULAR', + enableSanctionsList: true, + feeReceiver: '0x6b5067C1D71e1Ad7e5Fbe85A8af04868B2e70a1B', + tokensReceiver: '0x83BfD9233DC281E7BA1311B1245cb2f891a94E56', + instantDailyLimit: parseUnits('30000000', 18), + instantFee: parseUnits('0', 2), + variationTolerance: parseUnits('2', 2), + minAmount: parseUnits('0', 18), + minMTokenAmountForFirstDeposit: parseUnits('0', 18), + maxSupplyCap: constants.MaxUint256, + }, + rvSwapper: { + type: 'SWAPPER', + feeReceiver: '0x83BfD9233DC281E7BA1311B1245cb2f891a94E56', + tokensReceiver: '0x83BfD9233DC281E7BA1311B1245cb2f891a94E56', + requestRedeemer: '0x27c41C320066e92688799b3cd0014992Da7f2f0C', + instantDailyLimit: parseUnits('200000', 18), + instantFee: parseUnits('0.5', 2), + variationTolerance: parseUnits('2', 2), + minAmount: parseUnits('1', 18), + fiatFlatFee: parseUnits('30', 18), + fiatAdditionalFee: parseUnits('0.1', 2), + minFiatRedeemAmount: parseUnits('1000', 18), + enableSanctionsList: true, + liquidityProvider: 'dummy', + swapperVault: 'dummy', + }, + postDeploy: { + addPaymentTokens: { + vaults: [ + { + paymentTokens: [ + { + token: 'usdc', + allowance: parseUnits('1000000000', 18), + isStable: true, + }, + ], + type: 'depositVault', + }, + { + paymentTokens: [ + { + token: 'usdc', + allowance: parseUnits('1000000000', 18), + isStable: true, + }, + ], + type: 'redemptionVaultSwapper', + }, + ], + }, + grantRoles: { + tokenManagerAddress: '0xA13f82F679E24ad08E014F8af6EcE32023b14F07', + vaultsManagerAddress: '0x2ACB4BdCbEf02f81BF713b696Ac26390d7f79A12', + oracleManagerAddress: '0x83b573AA8C4b567c0466c9d5e32D6513676d795b', + }, + greenlist: { + depositVault: true, + redemptionVaultSwapper: true, + }, + pauseFunctions: { + depositVault: ['depositRequest', 'depositRequestWithCustomRecipient'], + redemptionVaultSwapper: ['redeemFiatRequest'], + }, + setRoundData: { + data: parseUnits('1', 8), + }, + }, + }, }, }; diff --git a/scripts/deploy/configs/mGLOeuro.ts b/scripts/deploy/configs/mGLOeuro.ts new file mode 100644 index 00000000..1e8e322b --- /dev/null +++ b/scripts/deploy/configs/mGLOeuro.ts @@ -0,0 +1,132 @@ +import { constants } from 'ethers'; +import { parseUnits } from 'ethers/lib/utils'; + +import { chainIds } from '../../../config'; +import { DeploymentConfig } from '../common/types'; + +export const mGLOeuroDeploymentConfig: DeploymentConfig = { + genericConfigs: { + customAggregator: { + maxAnswerDeviation: parseUnits('1', 8), + description: 'mGLOeuro/EUR', + minAnswer: parseUnits('90000', 8), + maxAnswer: parseUnits('110000', 8), + }, + dataFeed: { + minAnswer: parseUnits('90000', 8), + maxAnswer: parseUnits('110000', 8), + }, + customAggregatorAdjustedDv: { + adjustmentPercentage: parseUnits('7', 8), + underlyingFeed: 'customFeed', + }, + customAggregatorAdjustedRv: { + adjustmentPercentage: parseUnits('-7', 8), + underlyingFeed: 'customFeed', + }, + }, + networkConfigs: { + [chainIds.main]: { + dvMorpho: { + type: 'MORPHO', + enableSanctionsList: true, + feeReceiver: '0xC903840d5E314caA407C6Bc5792746b5282BBFa7', + tokensReceiver: '0x67887dd84E4778d8372BCD296E00995c59C00e52', + instantDailyLimit: parseUnits('10000000', 18), + instantFee: parseUnits('0', 2), + variationTolerance: parseUnits('2', 2), + minAmount: parseUnits('1.1', 18), + minMTokenAmountForFirstDeposit: parseUnits('0', 18), + maxSupplyCap: constants.MaxUint256, + }, + rvMorpho: { + type: 'MORPHO', + feeReceiver: '0x67887dd84E4778d8372BCD296E00995c59C00e52', + tokensReceiver: '0x67887dd84E4778d8372BCD296E00995c59C00e52', + requestRedeemer: '0x7Ec5C012d2f140BE7f55c43777B399442ec8AF51', + instantDailyLimit: parseUnits('500000', 18), + instantFee: parseUnits('0.5', 2), + variationTolerance: parseUnits('2', 2), + minAmount: parseUnits('1.1', 18), + enableSanctionsList: true, + fiatAdditionalFee: parseUnits('0.1', 2), + fiatFlatFee: parseUnits('30', 18), + minFiatRedeemAmount: parseUnits('1000', 18), + }, + postDeploy: { + addPaymentTokens: { + vaults: [ + { + paymentTokens: [ + { + token: 'eurc', + allowance: parseUnits('1000000000', 18), + isStable: true, + fee: parseUnits('0', 2), + }, + { + token: 'eurcv', + allowance: parseUnits('500000000', 18), + isStable: true, + fee: parseUnits('0', 2), + }, + ], + type: 'depositVaultMorpho', + }, + { + paymentTokens: [ + { + token: 'eurc', + allowance: parseUnits('1000000000', 18), + isStable: true, + fee: parseUnits('0', 2), + }, + { + token: 'eurcv', + allowance: parseUnits('500000000', 18), + isStable: true, + fee: parseUnits('0', 2), + }, + ], + type: 'redemptionVaultMorpho', + }, + ], + }, + grantRoles: { + tokenManagerAddress: '0x462C735196AF2277deE9f15Ac60B45E7Fe8415AA', + vaultsManagerAddress: '0x2ACB4BdCbEf02f81BF713b696Ac26390d7f79A12', + oracleManagerAddress: '0xa301F0eD658f72e0520fc47b42888bc985eF600c', + minBalanceExemptAddresses: [ + '0x67887dd84E4778d8372BCD296E00995c59C00e52', + ], + }, + greenlist: { + depositVaultMorpho: true, + redemptionVaultMorpho: true, + }, + pauseFunctions: { + depositVaultMorpho: [ + 'depositRequest', + 'depositRequestWithCustomRecipient', + ], + redemptionVaultMorpho: ['redeemFiatRequest'], + }, + setRoundData: { + data: parseUnits('100000', 8), + }, + setMorphoConfig: [ + { + type: 'depositVaultMorpho', + vaults: [], + depositsEnabled: false, + autoInvestFallbackEnabled: true, + }, + { + type: 'redemptionVaultMorpho', + vaults: [], + }, + ], + }, + }, + }, +}; diff --git a/scripts/deploy/configs/payment-tokens.ts b/scripts/deploy/configs/payment-tokens.ts index 4de42d13..54e52e05 100644 --- a/scripts/deploy/configs/payment-tokens.ts +++ b/scripts/deploy/configs/payment-tokens.ts @@ -382,6 +382,41 @@ export const paymentTokenDeploymentConfigs: PaymentTokenDeploymentConfig = { maxAnswer: parseUnits('1.250000', 6), }, }, + eurc: { + dataFeed: { + // EURC/EUR = (USD per EURC) / (USD per EUR). + // Each wrapper converts its native 8-decimal Chainlink answer to 18 decimals. + numerator: { + healthyDiff: 24 * 60 * 60, + }, + denominator: { + healthyDiff: 24 * 60 * 60, + }, + feedType: 'composite', + // Composite output and bounds are EUR per EURC in 18 decimals (1e18 = 1 EUR per EURC). + // The standard stable-payment-token bounds are shared by the DV and RV. + minAnswer: parseUnits('0.997'), + maxAnswer: parseUnits('1.003'), + }, + }, + eurcv: { + customAggregator: { + description: 'EURCV/EUR', + minAnswer: parseUnits('0.9999', 8), + maxAnswer: parseUnits('1', 8), + maxAnswerDeviation: parseUnits('0', 8), + }, + dataFeed: { + healthyDiff: constants.MaxUint256, + minAnswer: parseUnits('0.9999', 8), + maxAnswer: parseUnits('1', 8), + }, + postDeploy: { + setRoundData: { + data: parseUnits('1', 8), + }, + }, + }, }, [chainIds.base]: { usdc: { diff --git a/scripts/dump_roles.ts b/scripts/dump_roles.ts index ae3c5668..0cf6aef4 100644 --- a/scripts/dump_roles.ts +++ b/scripts/dump_roles.ts @@ -1,5 +1,6 @@ import { writeFile } from 'fs/promises'; +import { mTokensMetadata } from '../helpers/mtokens-metadata'; import { getAllRoles } from '../helpers/roles'; const formatKey = (k: string) => { @@ -17,10 +18,17 @@ const func = async () => { const tokensTables = Object.entries(tokenRoles).reduce( (prev, [mToken, roles]) => { - const mdRows = Object.entries(roles).map(([role, value]) => [ - formatKey(role), - formatValue(value as string), - ]); + const mdRows = Object.entries(roles) + .filter( + ([role]) => + role !== 'minBalanceExempt' || + mTokensMetadata[mToken as keyof typeof mTokensMetadata] + ?.isMinBalance, + ) + .map(([role, value]) => [ + formatKey(role), + formatValue(value as string), + ]); prev += `### ${mToken} Roles\n\n`; diff --git a/scripts/upgrades/configs/upgrade-configs.ts b/scripts/upgrades/configs/upgrade-configs.ts index 1840fbfe..8682390c 100644 --- a/scripts/upgrades/configs/upgrade-configs.ts +++ b/scripts/upgrades/configs/upgrade-configs.ts @@ -45,6 +45,19 @@ export const upgradeConfigs: UpgradeConfig = { }, }, }, + 'mwin-rv-fee-transfer-order-fix': { + vaults: { + [chainIds.main]: { + overrides: { + mWIN: { + overrides: { + redemptionVaultMToken: true, + }, + }, + }, + }, + }, + }, 'batch-upgrade-scope-w-supply-cap': { initializers: { depositVault: { diff --git a/scripts/upgrades/executeUpgrade_Aggregators.ts b/scripts/upgrades/executeUpgrade_Aggregators.ts index c687eb42..45cfec1b 100644 --- a/scripts/upgrades/executeUpgrade_Aggregators.ts +++ b/scripts/upgrades/executeUpgrade_Aggregators.ts @@ -8,7 +8,8 @@ import { getMTokenOrThrow } from '../../helpers/utils'; import { DeployFunction } from '../deploy/common/types'; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const upgradeId = 'mkralpha-custom-aggregator-upgrade-v2'; + const upgradeId = 'mwin-custom-aggregator-upgrade-v2'; + const networkAddresses = getCurrentAddresses(hre); const mToken = getMTokenOrThrow(hre); const tokenAddresses = networkAddresses?.[mToken]; @@ -25,7 +26,7 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { { contractType: 'customAggregator', initializer: 'initializeV2', - initializerArgs: [parseUnits('0.66', 8)], + initializerArgs: [parseUnits('90000', 8), parseUnits('140000', 8)], }, ], }, diff --git a/scripts/upgrades/executeUpgrade_Token.ts b/scripts/upgrades/executeUpgrade_Token.ts index d0cbaeba..dd3c3273 100644 --- a/scripts/upgrades/executeUpgrade_Token.ts +++ b/scripts/upgrades/executeUpgrade_Token.ts @@ -7,7 +7,7 @@ import { getMTokenOrThrow } from '../../helpers/utils'; import { DeployFunction } from '../deploy/common/types'; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const upgradeId = 'mwin-upgrade-permissioned'; + const upgradeId = 'mwin-upgrade-permissioned-min-balance'; const networkAddresses = getCurrentAddresses(hre); const mToken = getMTokenOrThrow(hre); const tokenAddresses = networkAddresses?.[mToken]; diff --git a/scripts/upgrades/proposeUpgrade_Aggregators.ts b/scripts/upgrades/proposeUpgrade_Aggregators.ts index ef2707d9..942c6eae 100644 --- a/scripts/upgrades/proposeUpgrade_Aggregators.ts +++ b/scripts/upgrades/proposeUpgrade_Aggregators.ts @@ -8,7 +8,7 @@ import { getMTokenOrThrow } from '../../helpers/utils'; import { DeployFunction } from '../deploy/common/types'; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const upgradeId = 'mre7-custom-aggregator-upgrade-v3'; + const upgradeId = 'mwin-custom-aggregator-upgrade-v2'; const networkAddresses = getCurrentAddresses(hre); const mToken = getMTokenOrThrow(hre); @@ -26,7 +26,7 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { { contractType: 'customAggregator', initializer: 'initializeV2', - initializerArgs: [parseUnits('0.66', 8)], + initializerArgs: [parseUnits('90000', 8), parseUnits('140000', 8)], }, ], }, diff --git a/scripts/upgrades/proposeUpgrade_Token.ts b/scripts/upgrades/proposeUpgrade_Token.ts index 525e25c0..52ab7066 100644 --- a/scripts/upgrades/proposeUpgrade_Token.ts +++ b/scripts/upgrades/proposeUpgrade_Token.ts @@ -7,7 +7,7 @@ import { getMTokenOrThrow } from '../../helpers/utils'; import { DeployFunction } from '../deploy/common/types'; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const upgradeId = 'mwin-upgrade-permissioned'; + const upgradeId = 'mwin-upgrade-permissioned-min-balance'; const networkAddresses = getCurrentAddresses(hre); const mToken = getMTokenOrThrow(hre); const tokenAddresses = networkAddresses?.[mToken]; diff --git a/test/common/fixtures.ts b/test/common/fixtures.ts index 70e0ab6e..094d6717 100644 --- a/test/common/fixtures.ts +++ b/test/common/fixtures.ts @@ -57,6 +57,8 @@ import { MidasLzOFTAdapter__factory, MidasLzVaultComposerSyncTester, MTokenPermissionedTest__factory, + MTokenMinBalanceTest__factory, + MTokenPermissionedMinBalanceTest__factory, AxelarInterchainTokenServiceMock__factory, MidasAxelarVaultExecutableTester, LzEndpointV2Mock__factory, @@ -985,6 +987,399 @@ export const mTokenPermissionedFixture = async ( }; }; +/** + * mTokenMinBalanceTest fixture for min-balance unit tests. + */ +export const mTokenMinBalanceFixture = async ( + baseFixture?: Awaited>, +) => { + const fx = baseFixture ?? (await defaultDeploy()); + const { owner, accessControl } = fx; + + const mTokenMinBalance = await new MTokenMinBalanceTest__factory( + owner, + ).deploy(); + await mTokenMinBalance.initialize(accessControl.address); + + const mintRole = await mTokenMinBalance.M_TOKEN_TEST_MINT_OPERATOR_ROLE(); + const burnRole = await mTokenMinBalance.M_TOKEN_TEST_BURN_OPERATOR_ROLE(); + const pauseRole = await mTokenMinBalance.M_TOKEN_TEST_PAUSE_OPERATOR_ROLE(); + const minBalanceExemptRole = + await mTokenMinBalance.M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE(); + + await accessControl.grantRole(mintRole, owner.address); + await accessControl.grantRole(burnRole, owner.address); + await accessControl.grantRole(pauseRole, owner.address); + + return { + ...fx, + mTokenMinBalance, + mTokenMinBalanceRoles: { + mint: mintRole, + burn: burnRole, + pause: pauseRole, + minBalanceExempt: minBalanceExemptRole, + }, + }; +}; + +export const mTokenMinBalanceRedemptionFixture = async () => { + const fx = await mTokenMinBalanceFixture(); + const { + accessControl, + aavePoolMock, + buidlRedemption, + dataFeed, + feeReceiver, + liquidityProvider, + mockedSanctionsList, + morphoVaultMock, + mTokenMinBalance, + mTokenMinBalanceRoles, + mTokenToUsdDataFeed, + owner, + requestRedeemer, + stableCoins, + tokensReceiver, + ustbRedemption, + } = fx; + + const redemptionVault = await new RedemptionVaultTest__factory( + owner, + ).deploy(); + await redemptionVault.initialize( + accessControl.address, + { + mToken: mTokenMinBalance.address, + mTokenDataFeed: mTokenToUsdDataFeed.address, + }, + { + feeReceiver: feeReceiver.address, + tokensReceiver: tokensReceiver.address, + }, + { + instantFee: parseUnits('0.5', 2), + instantDailyLimit: parseUnits('500000'), + }, + mockedSanctionsList.address, + parseUnits('2', 2), + parseUnits('1.1'), + { + fiatAdditionalFee: parseUnits('0.1', 2), + fiatFlatFee: parseUnits('30'), + minFiatRedeemAmount: parseUnits('1000'), + }, + requestRedeemer.address, + ); + + const redemptionVaultWithAave = + await new RedemptionVaultWithAaveTest__factory(owner).deploy(); + await redemptionVaultWithAave.initialize( + accessControl.address, + { + mToken: mTokenMinBalance.address, + mTokenDataFeed: mTokenToUsdDataFeed.address, + }, + { + feeReceiver: feeReceiver.address, + tokensReceiver: tokensReceiver.address, + }, + { + instantFee: parseUnits('0.5', 2), + instantDailyLimit: parseUnits('500000'), + }, + mockedSanctionsList.address, + parseUnits('2', 2), + parseUnits('1.1'), + { + fiatAdditionalFee: parseUnits('0.1', 2), + fiatFlatFee: parseUnits('30'), + minFiatRedeemAmount: parseUnits('1000'), + }, + requestRedeemer.address, + ); + await redemptionVaultWithAave.setAavePool( + stableCoins.usdc.address, + aavePoolMock.address, + ); + + const redemptionVaultWithBUIDL = + await new RedemptionVaultWithBUIDLTest__factory(owner).deploy(); + await redemptionVaultWithBUIDL[ + 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,uint256,uint256)' + ]( + accessControl.address, + { + mToken: mTokenMinBalance.address, + mTokenDataFeed: mTokenToUsdDataFeed.address, + }, + { + feeReceiver: feeReceiver.address, + tokensReceiver: tokensReceiver.address, + }, + { + instantFee: parseUnits('0.5', 2), + instantDailyLimit: parseUnits('500000'), + }, + mockedSanctionsList.address, + parseUnits('2', 2), + parseUnits('1.1'), + { + fiatAdditionalFee: parseUnits('0.1', 2), + fiatFlatFee: parseUnits('30'), + minFiatRedeemAmount: parseUnits('1000'), + }, + requestRedeemer.address, + buidlRedemption.address, + parseUnits('250000', 6), + parseUnits('250000', 6), + ); + + const redemptionVaultWithMorpho = + await new RedemptionVaultWithMorphoTest__factory(owner).deploy(); + await redemptionVaultWithMorpho.initialize( + accessControl.address, + { + mToken: mTokenMinBalance.address, + mTokenDataFeed: mTokenToUsdDataFeed.address, + }, + { + feeReceiver: feeReceiver.address, + tokensReceiver: tokensReceiver.address, + }, + { + instantFee: parseUnits('0.5', 2), + instantDailyLimit: parseUnits('500000'), + }, + mockedSanctionsList.address, + parseUnits('2', 2), + parseUnits('1.1'), + { + fiatAdditionalFee: parseUnits('0.1', 2), + fiatFlatFee: parseUnits('30'), + minFiatRedeemAmount: parseUnits('1000'), + }, + requestRedeemer.address, + ); + + const redemptionVaultWithMToken = + await new RedemptionVaultWithMTokenTest__factory(owner).deploy(); + await redemptionVaultWithMToken[ + 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)' + ]( + accessControl.address, + { + mToken: mTokenMinBalance.address, + mTokenDataFeed: mTokenToUsdDataFeed.address, + }, + { + feeReceiver: feeReceiver.address, + tokensReceiver: tokensReceiver.address, + }, + { + instantFee: parseUnits('0.5', 2), + instantDailyLimit: parseUnits('500000'), + }, + mockedSanctionsList.address, + parseUnits('2', 2), + parseUnits('1.1'), + { + fiatAdditionalFee: parseUnits('0.1', 2), + fiatFlatFee: parseUnits('30'), + minFiatRedeemAmount: parseUnits('1000'), + }, + requestRedeemer.address, + redemptionVault.address, + ); + + const redemptionVaultWithUSTB = + await new RedemptionVaultWithUSTBTest__factory(owner).deploy(); + await redemptionVaultWithUSTB[ + 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)' + ]( + accessControl.address, + { + mToken: mTokenMinBalance.address, + mTokenDataFeed: mTokenToUsdDataFeed.address, + }, + { + feeReceiver: feeReceiver.address, + tokensReceiver: tokensReceiver.address, + }, + { + instantFee: parseUnits('0.5', 2), + instantDailyLimit: parseUnits('500000'), + }, + mockedSanctionsList.address, + parseUnits('2', 2), + parseUnits('1.1'), + { + fiatAdditionalFee: parseUnits('0.1', 2), + fiatFlatFee: parseUnits('30'), + minFiatRedeemAmount: parseUnits('1000'), + }, + requestRedeemer.address, + ustbRedemption.address, + ); + + const redemptionVaultWithSwapper = + await new RedemptionVaultWithSwapperTest__factory(owner).deploy(); + await redemptionVaultWithSwapper[ + 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,address)' + ]( + accessControl.address, + { + mToken: mTokenMinBalance.address, + mTokenDataFeed: mTokenToUsdDataFeed.address, + }, + { + feeReceiver: feeReceiver.address, + tokensReceiver: tokensReceiver.address, + }, + { + instantFee: parseUnits('0.5', 2), + instantDailyLimit: parseUnits('500000'), + }, + mockedSanctionsList.address, + parseUnits('2', 2), + parseUnits('1.1'), + { + fiatAdditionalFee: parseUnits('0.1', 2), + fiatFlatFee: parseUnits('30'), + minFiatRedeemAmount: parseUnits('1000'), + }, + requestRedeemer.address, + redemptionVault.address, + liquidityProvider.address, + ); + + await accessControl.grantRole( + mTokenMinBalanceRoles.burn, + redemptionVault.address, + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.burn, + redemptionVaultWithMorpho.address, + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.burn, + redemptionVaultWithAave.address, + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.burn, + redemptionVaultWithBUIDL.address, + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.burn, + redemptionVaultWithMToken.address, + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.burn, + redemptionVaultWithSwapper.address, + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.burn, + redemptionVaultWithUSTB.address, + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.minBalanceExempt, + feeReceiver.address, + ); + + const paymentToken = stableCoins.usdc; + for (const vault of [ + redemptionVault, + redemptionVaultWithAave, + redemptionVaultWithBUIDL, + redemptionVaultWithMToken, + redemptionVaultWithMorpho, + redemptionVaultWithSwapper, + redemptionVaultWithUSTB, + ]) { + await vault.addPaymentToken( + paymentToken.address, + dataFeed.address, + 0, + parseUnits('1000000000'), + true, + ); + } + + await redemptionVaultWithMorpho.setMorphoVault( + paymentToken.address, + morphoVaultMock.address, + ); + + await paymentToken.mint(redemptionVault.address, parseUnits('100', 8)); + await paymentToken.mint(morphoVaultMock.address, parseUnits('100', 8)); + await morphoVaultMock.mint( + redemptionVaultWithMorpho.address, + parseUnits('100', 8), + ); + for (const vault of [ + redemptionVaultWithAave, + redemptionVaultWithBUIDL, + redemptionVaultWithMToken, + redemptionVaultWithSwapper, + redemptionVaultWithUSTB, + ]) { + await paymentToken.mint(vault.address, parseUnits('100', 8)); + } + + return { + ...fx, + paymentToken, + redemptionVault, + redemptionVaultWithAave, + redemptionVaultWithBUIDL, + redemptionVaultWithMorpho, + redemptionVaultWithMToken, + redemptionVaultWithSwapper, + redemptionVaultWithUSTB, + }; +}; + +/** + * mTokenPermissionedMinBalanceTest fixture for permissioned + min-balance unit tests. + */ +export const mTokenPermissionedMinBalanceFixture = async ( + baseFixture?: Awaited>, +) => { + const fx = baseFixture ?? (await defaultDeploy()); + const { owner, accessControl } = fx; + + const mTokenPermissionedMinBalance = + await new MTokenPermissionedMinBalanceTest__factory(owner).deploy(); + await mTokenPermissionedMinBalance.initialize(accessControl.address); + + const mintRole = + await mTokenPermissionedMinBalance.M_TOKEN_TEST_MINT_OPERATOR_ROLE(); + const burnRole = + await mTokenPermissionedMinBalance.M_TOKEN_TEST_BURN_OPERATOR_ROLE(); + const pauseRole = + await mTokenPermissionedMinBalance.M_TOKEN_TEST_PAUSE_OPERATOR_ROLE(); + const greenlistedRole = + await mTokenPermissionedMinBalance.M_TOKEN_TEST_GREENLISTED_ROLE(); + const minBalanceExemptRole = + await mTokenPermissionedMinBalance.M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE(); + + await accessControl.grantRole(mintRole, owner.address); + await accessControl.grantRole(burnRole, owner.address); + await accessControl.grantRole(pauseRole, owner.address); + + return { + ...fx, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles: { + mint: mintRole, + burn: burnRole, + pause: pauseRole, + greenlisted: greenlistedRole, + minBalanceExempt: minBalanceExemptRole, + }, + }; +}; + export const acreAdapterFixture = async () => { const defaultFixture = await defaultDeploy(); diff --git a/test/common/mTBILL.helpers.ts b/test/common/mTBILL.helpers.ts index 8613a8b3..a3346d73 100644 --- a/test/common/mTBILL.helpers.ts +++ b/test/common/mTBILL.helpers.ts @@ -5,10 +5,21 @@ import { defaultAbiCoder, solidityKeccak256 } from 'ethers/lib/utils'; import { Account, OptionalCommonParams, getAccount } from './common.helpers'; -import { MTBILL, MToken, MTokenPermissioned } from '../../typechain-types'; +import { + MTBILL, + MToken, + MTokenMinBalance, + MTokenPermissioned, + MTokenPermissionedMinBalance, +} from '../../typechain-types'; type CommonParams = { - tokenContract: MToken | MTBILL | MTokenPermissioned; + tokenContract: + | MToken + | MTBILL + | MTokenPermissioned + | MTokenMinBalance + | MTokenPermissionedMinBalance; owner: SignerWithAddress; }; diff --git a/test/common/redemption-vault.helpers.ts b/test/common/redemption-vault.helpers.ts index 083d8aa1..42ae843b 100644 --- a/test/common/redemption-vault.helpers.ts +++ b/test/common/redemption-vault.helpers.ts @@ -16,8 +16,6 @@ import { ERC20, ERC20__factory, IERC20, - MTBILL, - MToken, RedemptionVault, RedemptionVaultWIthBUIDL, RedemptionVaultWithAave, @@ -28,7 +26,7 @@ import { } from '../../typechain-types'; type CommonParamsRedeem = { - mTBILL: MToken | MTBILL; + mTBILL: IERC20; } & Pick< Awaited>, 'owner' | 'mTokenToUsdDataFeed' @@ -137,6 +135,10 @@ export const redeemInstantTest = async ( amountIn, true, ); + const tokenDecimals = await tokenContract.decimals(); + const eventAmountOut = amountOut.mul( + BigNumber.from(10).pow(18 - tokenDecimals), + ); await expect(callFn()) .to.emit( @@ -149,14 +151,14 @@ export const redeemInstantTest = async ( ) .withArgs( ...[ - sender, + sender.address, tokenOut, withRecipient ? recipient : undefined, - amountTBillIn, + amountIn, fee, - amountOut, + eventAmountOut, ].filter((v) => v !== undefined), - ).to.not.reverted; + ); const balanceAfterUser = await mTBILL.balanceOf(sender.address); const balanceAfterReceiver = await mTBILL.balanceOf(tokensReceiver); @@ -276,14 +278,14 @@ export const redeemRequestTest = async ( ) .withArgs( ...[ - latestRequestIdBefore.add(1), - sender, + latestRequestIdBefore, + sender.address, tokenOut, withRecipient ? recipient : undefined, - amountTBillIn, + amountIn, fee, ].filter((v) => v !== undefined), - ).to.not.reverted; + ); const latestRequestIdAfter = await redemptionVault.currentRequestId(); const request = await redemptionVault.redeemRequests(latestRequestIdBefore); diff --git a/test/common/token.tests.ts b/test/common/token.tests.ts index 5c9df789..12951267 100644 --- a/test/common/token.tests.ts +++ b/test/common/token.tests.ts @@ -109,6 +109,10 @@ export const tokenContractsTests = (token: MTokenName) => { return factory.attach(proxy.address) as TContract; }; + const isPermissioned = !!mTokensMetadata[token]?.isPermissioned; + const isMinBalance = !!mTokensMetadata[token]?.isMinBalance; + const unitAmount = isMinBalance ? parseUnits('1') : 1; + const deployMTokenWithFixture = async () => { const fixture = await loadFixture(defaultDeploy); @@ -118,7 +122,7 @@ export const tokenContractsTests = (token: MTokenName) => { fixture.accessControl.address, )) as MTBILL; - if (mTokensMetadata[token]?.isPermissioned) { + if (isPermissioned) { const greenlistedRole = tokenRoles.greenlisted; for (const account of fixture.regularAccounts) { await fixture.accessControl @@ -347,6 +351,11 @@ export const tokenContractsTests = (token: MTokenName) => { expect(await contract[tokenRoleNames.burner]()).eq(tokenRoles.burner); expect(await contract[tokenRoleNames.minter]()).eq(tokenRoles.minter); expect(await contract[tokenRoleNames.pauser]()).eq(tokenRoles.pauser); + if (isMinBalance) { + expect(await contract[tokenRoleNames.minBalanceExempt]()).eq( + tokenRoles.minBalanceExempt, + ); + } expect(await contract[allRoleNames.defaultAdmin]()).eq( allRoles.common.defaultAdmin, @@ -533,7 +542,7 @@ export const tokenContractsTests = (token: MTokenName) => { { blacklistable: tokenContract, accessControl, owner }, blacklisted, ); - await mint({ tokenContract, owner }, blacklisted, 1, { + await mint({ tokenContract, owner }, blacklisted, unitAmount, { revertMessage: acErrors.WMAC_HAS_ROLE, }); }); @@ -545,14 +554,14 @@ export const tokenContractsTests = (token: MTokenName) => { const blacklisted = regularAccounts[0]; const to = regularAccounts[1]; - await mint({ tokenContract, owner }, blacklisted, 1); + await mint({ tokenContract, owner }, blacklisted, unitAmount); await blackList( { blacklistable: tokenContract, accessControl, owner }, blacklisted, ); await expect( - tokenContract.connect(blacklisted).transfer(to.address, 1), + tokenContract.connect(blacklisted).transfer(to.address, unitAmount), ).revertedWith(acErrors.WMAC_HAS_ROLE); }); @@ -563,14 +572,14 @@ export const tokenContractsTests = (token: MTokenName) => { const blacklisted = regularAccounts[0]; const from = regularAccounts[1]; - await mint({ tokenContract, owner }, from, 1); + await mint({ tokenContract, owner }, from, unitAmount); await blackList( { blacklistable: tokenContract, accessControl, owner }, blacklisted, ); await expect( - tokenContract.connect(from).transfer(blacklisted.address, 1), + tokenContract.connect(from).transfer(blacklisted.address, unitAmount), ).revertedWith(acErrors.WMAC_HAS_ROLE); }); @@ -581,18 +590,20 @@ export const tokenContractsTests = (token: MTokenName) => { const blacklisted = regularAccounts[0]; const to = regularAccounts[1]; - await mint({ tokenContract, owner }, blacklisted, 1); + await mint({ tokenContract, owner }, blacklisted, unitAmount); await blackList( { blacklistable: tokenContract, accessControl, owner }, blacklisted, ); - await tokenContract.connect(blacklisted).approve(to.address, 1); + await tokenContract + .connect(blacklisted) + .approve(to.address, unitAmount); await expect( tokenContract .connect(to) - .transferFrom(blacklisted.address, to.address, 1), + .transferFrom(blacklisted.address, to.address, unitAmount), ).revertedWith(acErrors.WMAC_HAS_ROLE); }); @@ -604,18 +615,18 @@ export const tokenContractsTests = (token: MTokenName) => { const from = regularAccounts[1]; const caller = regularAccounts[2]; - await mint({ tokenContract, owner }, from, 1); + await mint({ tokenContract, owner }, from, unitAmount); await blackList( { blacklistable: tokenContract, accessControl, owner }, blacklisted, ); - await tokenContract.connect(from).approve(caller.address, 1); + await tokenContract.connect(from).approve(caller.address, unitAmount); await expect( tokenContract .connect(caller) - .transferFrom(from.address, blacklisted.address, 1), + .transferFrom(from.address, blacklisted.address, unitAmount), ).revertedWith(acErrors.WMAC_HAS_ROLE); }); @@ -625,12 +636,12 @@ export const tokenContractsTests = (token: MTokenName) => { const blacklisted = regularAccounts[0]; - await mint({ tokenContract, owner }, blacklisted, 1); + await mint({ tokenContract, owner }, blacklisted, unitAmount); await blackList( { blacklistable: tokenContract, accessControl, owner }, blacklisted, ); - await burn({ tokenContract, owner }, blacklisted, 1, { + await burn({ tokenContract, owner }, blacklisted, unitAmount, { revertMessage: acErrors.WMAC_HAS_ROLE, }); }); @@ -641,7 +652,7 @@ export const tokenContractsTests = (token: MTokenName) => { const blacklisted = regularAccounts[0]; - await mint({ tokenContract, owner }, blacklisted, 1); + await mint({ tokenContract, owner }, blacklisted, unitAmount); await blackList( { blacklistable: tokenContract, accessControl, owner }, blacklisted, @@ -651,10 +662,12 @@ export const tokenContractsTests = (token: MTokenName) => { blacklisted.address, ); await expect( - tokenContract.connect(owner).burnGoverned(blacklisted.address, 1), + tokenContract + .connect(owner) + .burnGoverned(blacklisted.address, unitAmount), ).to.not.reverted; const balanceAfter = await tokenContract.balanceOf(blacklisted.address); - expect(balanceBefore.sub(balanceAfter)).eq(1); + expect(balanceBefore.sub(balanceAfter)).eq(unitAmount); }); it('should fail: burnGoverned(...) when caller lacks burner role', async () => { @@ -664,10 +677,12 @@ export const tokenContractsTests = (token: MTokenName) => { const unauthorized = regularAccounts[0]; const target = regularAccounts[1]; - await mint({ tokenContract, owner }, target, 1); + await mint({ tokenContract, owner }, target, unitAmount); await expect( - tokenContract.connect(unauthorized).burnGoverned(target.address, 1), + tokenContract + .connect(unauthorized) + .burnGoverned(target.address, unitAmount), ).revertedWith(acErrors.WMAC_HASNT_ROLE); }); @@ -679,18 +694,20 @@ export const tokenContractsTests = (token: MTokenName) => { const from = regularAccounts[1]; const to = regularAccounts[2]; - await mint({ tokenContract, owner }, from, 1); + await mint({ tokenContract, owner }, from, unitAmount); await blackList( { blacklistable: tokenContract, accessControl, owner }, blacklisted, ); - await tokenContract.connect(from).approve(blacklisted.address, 1); + await tokenContract + .connect(from) + .approve(blacklisted.address, unitAmount); await expect( tokenContract .connect(blacklisted) - .transferFrom(from.address, to.address, 1), + .transferFrom(from.address, to.address, unitAmount), ).not.reverted; }); @@ -701,14 +718,14 @@ export const tokenContractsTests = (token: MTokenName) => { const blacklisted = regularAccounts[0]; const to = regularAccounts[2]; - await mint({ tokenContract, owner }, blacklisted, 1); + await mint({ tokenContract, owner }, blacklisted, unitAmount); await blackList( { blacklistable: tokenContract, accessControl, owner }, blacklisted, ); await expect( - tokenContract.connect(blacklisted).transfer(to.address, 1), + tokenContract.connect(blacklisted).transfer(to.address, unitAmount), ).revertedWith(acErrors.WMAC_HAS_ROLE); await unBlackList( @@ -716,8 +733,9 @@ export const tokenContractsTests = (token: MTokenName) => { blacklisted, ); - await expect(tokenContract.connect(blacklisted).transfer(to.address, 1)) - .not.reverted; + await expect( + tokenContract.connect(blacklisted).transfer(to.address, unitAmount), + ).not.reverted; }); }); }); diff --git a/test/unit/RedemptionVault.test.ts b/test/unit/RedemptionVault.test.ts index 424610e4..ac3d20f7 100644 --- a/test/unit/RedemptionVault.test.ts +++ b/test/unit/RedemptionVault.test.ts @@ -22,7 +22,11 @@ import { setRoundDataGrowth, } from '../common/custom-feed-growth.helpers'; import { setRoundData } from '../common/data-feed.helpers'; -import { defaultDeploy, mTokenPermissionedFixture } from '../common/fixtures'; +import { + defaultDeploy, + mTokenMinBalanceRedemptionFixture, + mTokenPermissionedFixture, +} from '../common/fixtures'; import { addPaymentTokenTest, addWaivedFeeAccountTest, @@ -1468,6 +1472,137 @@ describe('RedemptionVault', function () { }); describe('redeemInstant()', () => { + it('with min-balance mToken - redeems the full balance after transferring the fee', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVault, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVault.address, amount); + + await redeemInstantTest( + { + redemptionVault, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq(0); + }); + + it('with min-balance mToken - allows a redemption that leaves exactly one token', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVault, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, parseUnits('2.1')); + await mTokenMinBalance + .connect(user) + .approve(redemptionVault.address, amount); + + await redeemInstantTest( + { + redemptionVault, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq( + parseUnits('1'), + ); + }); + + it('should fail: with min-balance mToken - redemption would leave dust', async () => { + const { + feeReceiver, + mTokenMinBalance, + paymentToken, + redemptionVault, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + const balance = parseUnits('2'); + + await mTokenMinBalance.mint(user.address, balance); + await mTokenMinBalance + .connect(user) + .approve(redemptionVault.address, amount); + + await expect( + redemptionVault + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(balance); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + + it('should fail: with min-balance mToken - fee receiver is not exempt', async () => { + const { + accessControl, + feeReceiver, + mTokenMinBalance, + mTokenMinBalanceRoles, + paymentToken, + redemptionVault, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await accessControl.revokeRole( + mTokenMinBalanceRoles.minBalanceExempt, + feeReceiver.address, + ); + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVault.address, amount); + + await expect( + redemptionVault + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(amount); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + it('should fail: when there is no token in vault', async () => { const { owner, @@ -1603,7 +1738,7 @@ describe('RedemptionVault', function () { stableCoins.dai, 100, { - revertMessage: 'ERC20: burn amount exceeds balance', + revertMessage: 'ERC20: transfer amount exceeds balance', }, ); }); @@ -2775,6 +2910,45 @@ describe('RedemptionVault', function () { }); describe('redeemRequest()', () => { + it('with min-balance mToken - requests the full balance after transferring the fee', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVault, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await redemptionVault.changeTokenFee( + paymentToken.address, + parseUnits('0.5', 2), + ); + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVault.address, amount); + + await redeemRequestTest( + { + redemptionVault, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq(0); + expect(await mTokenMinBalance.balanceOf(redemptionVault.address)).eq( + parseUnits('1.0945'), + ); + }); + it('should fail: when there is no token in vault', async () => { const { owner, diff --git a/test/unit/RedemptionVaultWithAave.test.ts b/test/unit/RedemptionVaultWithAave.test.ts index 72869bae..052e983a 100644 --- a/test/unit/RedemptionVaultWithAave.test.ts +++ b/test/unit/RedemptionVaultWithAave.test.ts @@ -16,7 +16,10 @@ import { pauseVaultFn, } from '../common/common.helpers'; import { setRoundData } from '../common/data-feed.helpers'; -import { defaultDeploy } from '../common/fixtures'; +import { + defaultDeploy, + mTokenMinBalanceRedemptionFixture, +} from '../common/fixtures'; import { addPaymentTokenTest, setInstantFeeTest, @@ -862,6 +865,137 @@ describe('RedemptionVaultWithAave', function () { }); describe('redeemInstant()', () => { + it('with min-balance mToken - redeems the full balance after transferring the fee', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithAave, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithAave.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithAave, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq(0); + }); + + it('with min-balance mToken - allows a redemption that leaves exactly one token', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithAave, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, parseUnits('2.1')); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithAave.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithAave, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq( + parseUnits('1'), + ); + }); + + it('should fail: with min-balance mToken - redemption would leave dust', async () => { + const { + feeReceiver, + mTokenMinBalance, + paymentToken, + redemptionVaultWithAave, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + const balance = parseUnits('2'); + + await mTokenMinBalance.mint(user.address, balance); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithAave.address, amount); + + await expect( + redemptionVaultWithAave + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(balance); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + + it('should fail: with min-balance mToken - fee receiver is not exempt', async () => { + const { + accessControl, + feeReceiver, + mTokenMinBalance, + mTokenMinBalanceRoles, + paymentToken, + redemptionVaultWithAave, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await accessControl.revokeRole( + mTokenMinBalanceRoles.minBalanceExempt, + feeReceiver.address, + ); + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithAave.address, amount); + + await expect( + redemptionVaultWithAave + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(amount); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + it('should fail: when there is no token in vault', async () => { const { owner, @@ -1022,7 +1156,7 @@ describe('RedemptionVaultWithAave', function () { stableCoins.usdc, 100, { - revertMessage: 'ERC20: burn amount exceeds balance', + revertMessage: 'ERC20: transfer amount exceeds balance', }, ); }); diff --git a/test/unit/RedemptionVaultWithBUIDL.test.ts b/test/unit/RedemptionVaultWithBUIDL.test.ts index 36f92de5..1f74ddd5 100644 --- a/test/unit/RedemptionVaultWithBUIDL.test.ts +++ b/test/unit/RedemptionVaultWithBUIDL.test.ts @@ -19,7 +19,10 @@ import { pauseVaultFn, } from '../common/common.helpers'; import { setRoundData } from '../common/data-feed.helpers'; -import { defaultDeploy } from '../common/fixtures'; +import { + defaultDeploy, + mTokenMinBalanceRedemptionFixture, +} from '../common/fixtures'; import { addPaymentTokenTest, addWaivedFeeAccountTest, @@ -1251,6 +1254,137 @@ describe('RedemptionVaultWithBUIDL', function () { }); describe('redeemInstant()', () => { + it('with min-balance mToken - redeems the full balance after transferring the fee', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithBUIDL, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithBUIDL.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithBUIDL, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq(0); + }); + + it('with min-balance mToken - allows a redemption that leaves exactly one token', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithBUIDL, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, parseUnits('2.1')); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithBUIDL.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithBUIDL, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq( + parseUnits('1'), + ); + }); + + it('should fail: with min-balance mToken - redemption would leave dust', async () => { + const { + feeReceiver, + mTokenMinBalance, + paymentToken, + redemptionVaultWithBUIDL, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + const balance = parseUnits('2'); + + await mTokenMinBalance.mint(user.address, balance); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithBUIDL.address, amount); + + await expect( + redemptionVaultWithBUIDL + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(balance); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + + it('should fail: with min-balance mToken - fee receiver is not exempt', async () => { + const { + accessControl, + feeReceiver, + mTokenMinBalance, + mTokenMinBalanceRoles, + paymentToken, + redemptionVaultWithBUIDL, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await accessControl.revokeRole( + mTokenMinBalanceRoles.minBalanceExempt, + feeReceiver.address, + ); + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithBUIDL.address, amount); + + await expect( + redemptionVaultWithBUIDL + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(amount); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + it('should fail: when there is no token in vault', async () => { const { owner, @@ -1411,7 +1545,7 @@ describe('RedemptionVaultWithBUIDL', function () { stableCoins.usdc, 100, { - revertMessage: 'ERC20: burn amount exceeds balance', + revertMessage: 'ERC20: transfer amount exceeds balance', }, ); }); diff --git a/test/unit/RedemptionVaultWithMToken.test.ts b/test/unit/RedemptionVaultWithMToken.test.ts index 0210ccb1..a68dc05e 100644 --- a/test/unit/RedemptionVaultWithMToken.test.ts +++ b/test/unit/RedemptionVaultWithMToken.test.ts @@ -16,7 +16,10 @@ import { pauseVaultFn, } from '../common/common.helpers'; import { setRoundData } from '../common/data-feed.helpers'; -import { defaultDeploy } from '../common/fixtures'; +import { + defaultDeploy, + mTokenMinBalanceRedemptionFixture, +} from '../common/fixtures'; import { addPaymentTokenTest, setInstantFeeTest, @@ -34,6 +37,7 @@ import { redeemInstantWithMTokenTest } from '../common/redemption-vault-mtoken.h import { approveRedeemRequestTest, redeemFiatRequestTest, + redeemInstantTest, redeemRequestTest, rejectRedeemRequestTest, safeApproveRedeemRequestTest, @@ -818,6 +822,137 @@ describe('RedemptionVaultWithMToken', function () { }); describe('redeemInstant()', () => { + it('with min-balance mToken - redeems the full balance after transferring the fee', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithMToken, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithMToken.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithMToken, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq(0); + }); + + it('with min-balance mToken - allows a redemption that leaves exactly one token', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithMToken, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, parseUnits('2.1')); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithMToken.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithMToken, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq( + parseUnits('1'), + ); + }); + + it('should fail: with min-balance mToken - redemption would leave dust', async () => { + const { + feeReceiver, + mTokenMinBalance, + paymentToken, + redemptionVaultWithMToken, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + const balance = parseUnits('2'); + + await mTokenMinBalance.mint(user.address, balance); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithMToken.address, amount); + + await expect( + redemptionVaultWithMToken + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(balance); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + + it('should fail: with min-balance mToken - fee receiver is not exempt', async () => { + const { + accessControl, + feeReceiver, + mTokenMinBalance, + mTokenMinBalanceRoles, + paymentToken, + redemptionVaultWithMToken, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await accessControl.revokeRole( + mTokenMinBalanceRoles.minBalanceExempt, + feeReceiver.address, + ); + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithMToken.address, amount); + + await expect( + redemptionVaultWithMToken + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(amount); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + it('should fail: when there is no token in vault', async () => { const { owner, @@ -996,7 +1131,7 @@ describe('RedemptionVaultWithMToken', function () { stableCoins.dai, 100, { - revertMessage: 'ERC20: burn amount exceeds balance', + revertMessage: 'ERC20: transfer amount exceeds balance', }, ); }); diff --git a/test/unit/RedemptionVaultWithMorpho.test.ts b/test/unit/RedemptionVaultWithMorpho.test.ts index 1517a731..d234fe5f 100644 --- a/test/unit/RedemptionVaultWithMorpho.test.ts +++ b/test/unit/RedemptionVaultWithMorpho.test.ts @@ -16,7 +16,10 @@ import { pauseVaultFn, } from '../common/common.helpers'; import { setRoundData } from '../common/data-feed.helpers'; -import { defaultDeploy } from '../common/fixtures'; +import { + defaultDeploy, + mTokenMinBalanceRedemptionFixture, +} from '../common/fixtures'; import { addPaymentTokenTest, setInstantFeeTest, @@ -948,6 +951,137 @@ describe('RedemptionVaultWithMorpho', function () { }); describe('redeemInstant()', () => { + it('with min-balance mToken - allows a redemption that leaves exactly one token', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithMorpho, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, parseUnits('2.1')); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithMorpho.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithMorpho, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq( + parseUnits('1'), + ); + }); + + it('should fail: with min-balance mToken - redemption would leave dust', async () => { + const { + feeReceiver, + mTokenMinBalance, + paymentToken, + redemptionVaultWithMorpho, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + const balance = parseUnits('2'); + + await mTokenMinBalance.mint(user.address, balance); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithMorpho.address, amount); + + await expect( + redemptionVaultWithMorpho + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(balance); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + + it('should fail: with min-balance mToken - fee receiver is not exempt', async () => { + const { + accessControl, + feeReceiver, + mTokenMinBalance, + mTokenMinBalanceRoles, + paymentToken, + redemptionVaultWithMorpho, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await accessControl.revokeRole( + mTokenMinBalanceRoles.minBalanceExempt, + feeReceiver.address, + ); + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithMorpho.address, amount); + + await expect( + redemptionVaultWithMorpho + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(amount); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + + it('with min-balance mToken - redeems the full balance after transferring the fee', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithMorpho, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithMorpho.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithMorpho, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq(0); + }); + it('should fail: when there is no token in vault', async () => { const { owner, @@ -1108,7 +1242,7 @@ describe('RedemptionVaultWithMorpho', function () { stableCoins.usdc, 100, { - revertMessage: 'ERC20: burn amount exceeds balance', + revertMessage: 'ERC20: transfer amount exceeds balance', }, ); }); diff --git a/test/unit/RedemptionVaultWithSwapper.test.ts b/test/unit/RedemptionVaultWithSwapper.test.ts index f284be69..eaa0f23e 100644 --- a/test/unit/RedemptionVaultWithSwapper.test.ts +++ b/test/unit/RedemptionVaultWithSwapper.test.ts @@ -12,7 +12,10 @@ import { pauseVaultFn, } from '../common/common.helpers'; import { setRoundData } from '../common/data-feed.helpers'; -import { defaultDeploy } from '../common/fixtures'; +import { + defaultDeploy, + mTokenMinBalanceRedemptionFixture, +} from '../common/fixtures'; import { addPaymentTokenTest, changeTokenAllowanceTest, @@ -26,6 +29,7 @@ import { setLiquidityProviderTest, setSwapperVaultTest, } from '../common/redemption-vault-swapper.helpers'; +import { redeemInstantTest } from '../common/redemption-vault.helpers'; import { sanctionUser } from '../common/with-sanctions-list.helpers'; describe('MBasisRedemptionVaultWithSwapper', () => { @@ -230,6 +234,137 @@ describe('MBasisRedemptionVaultWithSwapper', () => { }); }); describe('redeemInstant()', () => { + it('with min-balance mToken - redeems the full balance after transferring the fee', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithSwapper, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithSwapper.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithSwapper, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq(0); + }); + + it('with min-balance mToken - allows a redemption that leaves exactly one token', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithSwapper, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, parseUnits('2.1')); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithSwapper.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithSwapper, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq( + parseUnits('1'), + ); + }); + + it('should fail: with min-balance mToken - redemption would leave dust', async () => { + const { + feeReceiver, + mTokenMinBalance, + paymentToken, + redemptionVaultWithSwapper, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + const balance = parseUnits('2'); + + await mTokenMinBalance.mint(user.address, balance); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithSwapper.address, amount); + + await expect( + redemptionVaultWithSwapper + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(balance); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + + it('should fail: with min-balance mToken - fee receiver is not exempt', async () => { + const { + accessControl, + feeReceiver, + mTokenMinBalance, + mTokenMinBalanceRoles, + paymentToken, + redemptionVaultWithSwapper, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await accessControl.revokeRole( + mTokenMinBalanceRoles.minBalanceExempt, + feeReceiver.address, + ); + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithSwapper.address, amount); + + await expect( + redemptionVaultWithSwapper + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(amount); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + it('should fail: when there is no token in vault', async () => { const { owner, diff --git a/test/unit/RedemptionVaultWithUSTB.test.ts b/test/unit/RedemptionVaultWithUSTB.test.ts index 9ce5b13d..a7e2f9e8 100644 --- a/test/unit/RedemptionVaultWithUSTB.test.ts +++ b/test/unit/RedemptionVaultWithUSTB.test.ts @@ -17,7 +17,10 @@ import { pauseVaultFn, } from '../common/common.helpers'; import { setRoundData } from '../common/data-feed.helpers'; -import { defaultDeploy } from '../common/fixtures'; +import { + defaultDeploy, + mTokenMinBalanceRedemptionFixture, +} from '../common/fixtures'; import { addPaymentTokenTest, setInstantFeeTest, @@ -1284,6 +1287,137 @@ describe('RedemptionVaultWithUSTB', function () { }); describe('redeemInstant()', () => { + it('with min-balance mToken - redeems the full balance after transferring the fee', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithUSTB, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithUSTB.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithUSTB, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq(0); + }); + + it('with min-balance mToken - allows a redemption that leaves exactly one token', async () => { + const { + mTokenMinBalance, + mTokenToUsdDataFeed, + owner, + paymentToken, + redemptionVaultWithUSTB, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await mTokenMinBalance.mint(user.address, parseUnits('2.1')); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithUSTB.address, amount); + + await redeemInstantTest( + { + redemptionVault: redemptionVaultWithUSTB, + owner, + mTBILL: mTokenMinBalance, + mTokenToUsdDataFeed, + }, + paymentToken, + 1.1, + { from: user }, + ); + + expect(await mTokenMinBalance.balanceOf(user.address)).eq( + parseUnits('1'), + ); + }); + + it('should fail: with min-balance mToken - redemption would leave dust', async () => { + const { + feeReceiver, + mTokenMinBalance, + paymentToken, + redemptionVaultWithUSTB, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + const balance = parseUnits('2'); + + await mTokenMinBalance.mint(user.address, balance); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithUSTB.address, amount); + + await expect( + redemptionVaultWithUSTB + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(balance); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + + it('should fail: with min-balance mToken - fee receiver is not exempt', async () => { + const { + accessControl, + feeReceiver, + mTokenMinBalance, + mTokenMinBalanceRoles, + paymentToken, + redemptionVaultWithUSTB, + regularAccounts, + } = await mTokenMinBalanceRedemptionFixture(); + const user = regularAccounts[0]; + const amount = parseUnits('1.1'); + + await accessControl.revokeRole( + mTokenMinBalanceRoles.minBalanceExempt, + feeReceiver.address, + ); + await mTokenMinBalance.mint(user.address, amount); + await mTokenMinBalance + .connect(user) + .approve(redemptionVaultWithUSTB.address, amount); + + await expect( + redemptionVaultWithUSTB + .connect(user) + ['redeemInstant(address,uint256,uint256)']( + paymentToken.address, + amount, + 0, + ), + ).revertedWith('MTMB: min balance not met'); + expect(await mTokenMinBalance.balanceOf(user.address)).eq(amount); + expect(await mTokenMinBalance.balanceOf(feeReceiver.address)).eq(0); + }); + it('should fail: when there is no token in vault', async () => { const { owner, @@ -1444,7 +1578,7 @@ describe('RedemptionVaultWithUSTB', function () { stableCoins.usdc, 100, { - revertMessage: 'ERC20: burn amount exceeds balance', + revertMessage: 'ERC20: transfer amount exceeds balance', }, ); }); diff --git a/test/unit/mtoken.test.ts b/test/unit/mtoken.test.ts index 76807887..8c214f71 100644 --- a/test/unit/mtoken.test.ts +++ b/test/unit/mtoken.test.ts @@ -1,11 +1,22 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { parseUnits } from 'ethers/lib/utils'; +import { upgrades } from 'hardhat'; import { MTokenNameEnum } from '../../config'; import { getRolesForToken, getRolesNamesForToken } from '../../helpers/roles'; +import { + MTokenPermissionedMinBalanceTest, + MTokenPermissionedMinBalanceTest__factory, + MTokenPermissionedTest__factory, +} from '../../typechain-types'; import { acErrors, blackList } from '../common/ac.helpers'; -import { defaultDeploy, mTokenPermissionedFixture } from '../common/fixtures'; +import { + defaultDeploy, + mTokenMinBalanceFixture, + mTokenPermissionedFixture, + mTokenPermissionedMinBalanceFixture, +} from '../common/fixtures'; import { burn, mint } from '../common/mTBILL.helpers'; import { tokenContractsTests } from '../common/token.tests'; @@ -447,6 +458,1415 @@ describe('Token contracts', () => { }); }); }); + + describe('mTokenMinBalance (mTokenMinBalanceTest)', () => { + describe('transfer()', () => { + it('transfer when both parties hold above min balance after transfer', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const amount = parseUnits('0.1'); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('3'), + ); + + await expect( + mTokenMinBalance.connect(from).transfer(to.address, amount), + ).not.reverted; + expect(await mTokenMinBalance.balanceOf(from.address)).eq( + parseUnits('2.9'), + ); + expect(await mTokenMinBalance.balanceOf(to.address)).eq( + parseUnits('3.1'), + ); + }); + + it('should fail: transfer dust to empty recipient', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + + await expect( + mTokenMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith('MTMB: min balance not met'); + }); + + it('transfer dust to empty recipient when recipient is free from min balance', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenMinBalance, + mTokenMinBalanceRoles, + } = await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const amount = parseUnits('0.1'); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.minBalanceExempt, + to.address, + ); + + await expect( + mTokenMinBalance.connect(from).transfer(to.address, amount), + ).not.reverted; + expect(await mTokenMinBalance.balanceOf(to.address)).eq(amount); + }); + + it('should fail: transfer dust from waived sender to empty non-waived recipient', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenMinBalance, + mTokenMinBalanceRoles, + } = await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + + await accessControl.grantRole( + mTokenMinBalanceRoles.minBalanceExempt, + from.address, + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('0.3'), + ); + + await expect( + mTokenMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith('MTMB: min balance not met'); + }); + + it('transfer entire balance leaving sender at zero', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const amount = parseUnits('3'); + + await mint({ tokenContract: mTokenMinBalance, owner }, from, amount); + + await expect( + mTokenMinBalance.connect(from).transfer(to.address, amount), + ).not.reverted; + expect(await mTokenMinBalance.balanceOf(from.address)).eq(0); + expect(await mTokenMinBalance.balanceOf(to.address)).eq(amount); + }); + + it('should fail: transfer leaving sender below min balance', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('1'), + ); + + await expect( + mTokenMinBalance + .connect(from) + .transfer(to.address, parseUnits('2.5')), + ).revertedWith('MTMB: min balance not met'); + }); + + it('transfer leaving sender below min balance when sender is free from min balance', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenMinBalance, + mTokenMinBalanceRoles, + } = await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('1'), + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.minBalanceExempt, + from.address, + ); + + await expect( + mTokenMinBalance + .connect(from) + .transfer(to.address, parseUnits('2.5')), + ).not.reverted; + expect(await mTokenMinBalance.balanceOf(from.address)).eq( + parseUnits('0.5'), + ); + }); + + it('transfer leaving both parties at exactly min balance', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('2'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('1'), + ); + + await expect( + mTokenMinBalance.connect(from).transfer(to.address, parseUnits('1')), + ).not.reverted; + expect(await mTokenMinBalance.balanceOf(from.address)).eq( + parseUnits('1'), + ); + expect(await mTokenMinBalance.balanceOf(to.address)).eq( + parseUnits('2'), + ); + }); + + it('should fail: transfer when from is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { owner, accessControl, regularAccounts, mTokenMinBalance } = + await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('3'), + ); + await blackList( + { blacklistable: mTokenMinBalance, accessControl, owner }, + from, + ); + + await expect( + mTokenMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith(acErrors.WMAC_HAS_ROLE); + }); + + it('should fail: transfer when to is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { owner, accessControl, regularAccounts, mTokenMinBalance } = + await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('3'), + ); + await blackList( + { blacklistable: mTokenMinBalance, accessControl, owner }, + to, + ); + + await expect( + mTokenMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith(acErrors.WMAC_HAS_ROLE); + }); + + it('should fail: transfer when token is paused', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('3'), + ); + await mTokenMinBalance.connect(owner).pause(); + + await expect( + mTokenMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith('ERC20Pausable: token transfer while paused'); + }); + }); + + describe('transferFrom()', () => { + it('transferFrom when both parties hold above min balance after transfer', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const spender = regularAccounts[1]; + const to = regularAccounts[2]; + const amount = parseUnits('0.1'); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('3'), + ); + await mTokenMinBalance.connect(from).approve(spender.address, amount); + + await expect( + mTokenMinBalance + .connect(spender) + .transferFrom(from.address, to.address, amount), + ).not.reverted; + }); + + it('should fail: transferFrom dust to empty recipient', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const spender = regularAccounts[1]; + const to = regularAccounts[2]; + const amount = parseUnits('0.1'); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mTokenMinBalance.connect(from).approve(spender.address, amount); + + await expect( + mTokenMinBalance + .connect(spender) + .transferFrom(from.address, to.address, amount), + ).revertedWith('MTMB: min balance not met'); + }); + + it('should fail: transferFrom when from is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { owner, accessControl, regularAccounts, mTokenMinBalance } = + await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const from = regularAccounts[0]; + const spender = regularAccounts[1]; + const to = regularAccounts[2]; + const amount = parseUnits('0.1'); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('3'), + ); + await mTokenMinBalance.connect(from).approve(spender.address, amount); + await blackList( + { blacklistable: mTokenMinBalance, accessControl, owner }, + from, + ); + + await expect( + mTokenMinBalance + .connect(spender) + .transferFrom(from.address, to.address, amount), + ).revertedWith(acErrors.WMAC_HAS_ROLE); + }); + + it('should fail: transferFrom when to is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { owner, accessControl, regularAccounts, mTokenMinBalance } = + await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const from = regularAccounts[0]; + const spender = regularAccounts[1]; + const to = regularAccounts[2]; + const amount = parseUnits('0.1'); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('3'), + ); + await mTokenMinBalance.connect(from).approve(spender.address, amount); + await blackList( + { blacklistable: mTokenMinBalance, accessControl, owner }, + to, + ); + + await expect( + mTokenMinBalance + .connect(spender) + .transferFrom(from.address, to.address, amount), + ).revertedWith(acErrors.WMAC_HAS_ROLE); + }); + }); + + describe('mint()', () => { + it('mint at least 1 token to empty recipient', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + regularAccounts[0], + parseUnits('1'), + ); + }); + + it('should fail: mint less than 1 token to empty recipient', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + regularAccounts[0], + parseUnits('0.5'), + { revertMessage: 'MTMB: min balance not met' }, + ); + }); + + it('mint less than 1 token to empty recipient when free from min balance', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenMinBalance, + mTokenMinBalanceRoles, + } = await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const to = regularAccounts[0]; + await accessControl.grantRole( + mTokenMinBalanceRoles.minBalanceExempt, + to.address, + ); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('0.5'), + ); + }); + + it('mint dust to recipient that already holds min balance', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const to = regularAccounts[0]; + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('1'), + ); + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('0.1'), + ); + }); + + it('should fail: mint when recipient is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { owner, accessControl, regularAccounts, mTokenMinBalance } = + await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const to = regularAccounts[0]; + await blackList( + { blacklistable: mTokenMinBalance, accessControl, owner }, + to, + ); + + await mint( + { tokenContract: mTokenMinBalance, owner }, + to, + parseUnits('1'), + { revertMessage: acErrors.WMAC_HAS_ROLE }, + ); + }); + }); + + describe('burn()', () => { + it('burn leaving holder at zero', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const holder = regularAccounts[0]; + await mint( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('1'), + ); + await burn( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('1'), + ); + }); + + it('burn leaving holder at or above min balance', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const holder = regularAccounts[0]; + await mint( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('3'), + ); + await burn( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('1'), + ); + expect(await mTokenMinBalance.balanceOf(holder.address)).eq( + parseUnits('2'), + ); + }); + + it('should fail: burn leaving holder below min balance', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const holder = regularAccounts[0]; + await mint( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('3'), + ); + + await burn( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('2.5'), + { revertMessage: 'MTMB: min balance not met' }, + ); + }); + + it('burn leaving holder below min balance when free from min balance', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenMinBalance, + mTokenMinBalanceRoles, + } = await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const holder = regularAccounts[0]; + await mint( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('3'), + ); + await accessControl.grantRole( + mTokenMinBalanceRoles.minBalanceExempt, + holder.address, + ); + + await burn( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('2.5'), + ); + expect(await mTokenMinBalance.balanceOf(holder.address)).eq( + parseUnits('0.5'), + ); + }); + + it('should fail: burn when holder is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { owner, accessControl, regularAccounts, mTokenMinBalance } = + await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const holder = regularAccounts[0]; + await mint( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('1'), + ); + await blackList( + { blacklistable: mTokenMinBalance, accessControl, owner }, + holder, + ); + + await burn( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('1'), + { revertMessage: acErrors.WMAC_HAS_ROLE }, + ); + }); + + it('burnGoverned when holder is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { owner, accessControl, regularAccounts, mTokenMinBalance } = + await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture)); + + const holder = regularAccounts[0]; + const amount = parseUnits('1'); + await mint({ tokenContract: mTokenMinBalance, owner }, holder, amount); + await blackList( + { blacklistable: mTokenMinBalance, accessControl, owner }, + holder, + ); + + await expect( + mTokenMinBalance.connect(owner).burnGoverned(holder.address, amount), + ).not.reverted; + expect(await mTokenMinBalance.balanceOf(holder.address)).eq(0); + }); + + it('should fail: burnGoverned leaving holder below min balance', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenMinBalance } = await loadFixture( + mTokenMinBalanceFixture.bind(this, baseFixture), + ); + + const holder = regularAccounts[0]; + await mint( + { tokenContract: mTokenMinBalance, owner }, + holder, + parseUnits('3'), + ); + + await expect( + mTokenMinBalance + .connect(owner) + .burnGoverned(holder.address, parseUnits('2.5')), + ).revertedWith('MTMB: min balance not met'); + }); + }); + }); + + describe('mTokenPermissionedMinBalance (mTokenPermissionedMinBalanceTest)', () => { + describe('transfer()', () => { + it('should fail: transfer when sender is not greenlisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('3'), + ); + await accessControl.revokeRole(greenlisted, from.address); + + await expect( + mTokenPermissionedMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith(acErrors.WMAC_HASNT_ROLE); + }); + + it('should fail: transfer when recipient is not greenlisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + + await accessControl.grantRole(greenlisted, from.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + + await expect( + mTokenPermissionedMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith(acErrors.WMAC_HASNT_ROLE); + }); + + it('should fail: transfer dust to empty recipient when both greenlisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + + await expect( + mTokenPermissionedMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith('MTMB: min balance not met'); + }); + + it('transfer dust to empty recipient when recipient is free from min balance', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const { greenlisted, minBalanceExempt } = + mTokenPermissionedMinBalanceRoles; + const amount = parseUnits('0.1'); + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + await accessControl.grantRole(minBalanceExempt, to.address); + + await expect( + mTokenPermissionedMinBalance + .connect(from) + .transfer(to.address, amount), + ).not.reverted; + expect(await mTokenPermissionedMinBalance.balanceOf(to.address)).eq( + amount, + ); + }); + + it('should fail: transfer dust from waived sender to empty non-waived recipient', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const { greenlisted, minBalanceExempt } = + mTokenPermissionedMinBalanceRoles; + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await accessControl.grantRole(minBalanceExempt, from.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('0.3'), + ); + + await expect( + mTokenPermissionedMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith('MTMB: min balance not met'); + }); + + it('transfer when both greenlisted and above min balance', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + const amount = parseUnits('0.1'); + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('3'), + ); + + await expect( + mTokenPermissionedMinBalance + .connect(from) + .transfer(to.address, amount), + ).not.reverted; + }); + + it('should fail: transfer when from is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('3'), + ); + await blackList( + { + blacklistable: mTokenPermissionedMinBalance, + accessControl, + owner, + }, + from, + ); + + await expect( + mTokenPermissionedMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith(acErrors.WMAC_HAS_ROLE); + }); + + it('should fail: transfer when to is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('3'), + ); + await blackList( + { + blacklistable: mTokenPermissionedMinBalance, + accessControl, + owner, + }, + to, + ); + + await expect( + mTokenPermissionedMinBalance + .connect(from) + .transfer(to.address, parseUnits('0.1')), + ).revertedWith(acErrors.WMAC_HAS_ROLE); + }); + }); + + describe('mint()', () => { + it('should fail: mint when receiver is not greenlisted', async () => { + const baseFixture = await defaultDeploy(); + const { owner, regularAccounts, mTokenPermissionedMinBalance } = + await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + regularAccounts[0], + parseUnits('1'), + { revertMessage: acErrors.WMAC_HASNT_ROLE }, + ); + }); + + it('should fail: mint less than 1 token to empty greenlisted recipient', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const to = regularAccounts[0]; + await accessControl.grantRole( + mTokenPermissionedMinBalanceRoles.greenlisted, + to.address, + ); + + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('0.5'), + { revertMessage: 'MTMB: min balance not met' }, + ); + }); + + it('mint when receiver is greenlisted and amount meets min balance', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const to = regularAccounts[0]; + await accessControl.grantRole( + mTokenPermissionedMinBalanceRoles.greenlisted, + to.address, + ); + + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('1'), + ); + }); + + it('should fail: mint when recipient is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const to = regularAccounts[0]; + await accessControl.grantRole( + mTokenPermissionedMinBalanceRoles.greenlisted, + to.address, + ); + await blackList( + { + blacklistable: mTokenPermissionedMinBalance, + accessControl, + owner, + }, + to, + ); + + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('1'), + { revertMessage: acErrors.WMAC_HAS_ROLE }, + ); + }); + }); + + describe('burn()', () => { + it('burn without greenlist on holder', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const holder = regularAccounts[0]; + await accessControl.grantRole( + mTokenPermissionedMinBalanceRoles.greenlisted, + holder.address, + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + holder, + parseUnits('1'), + ); + await accessControl.revokeRole( + mTokenPermissionedMinBalanceRoles.greenlisted, + holder.address, + ); + + await burn( + { tokenContract: mTokenPermissionedMinBalance, owner }, + holder, + parseUnits('1'), + ); + }); + + it('should fail: burn leaving holder below min balance', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const holder = regularAccounts[0]; + await accessControl.grantRole( + mTokenPermissionedMinBalanceRoles.greenlisted, + holder.address, + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + holder, + parseUnits('3'), + ); + + await burn( + { tokenContract: mTokenPermissionedMinBalance, owner }, + holder, + parseUnits('2.5'), + { revertMessage: 'MTMB: min balance not met' }, + ); + }); + + it('should fail: burn when holder is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const holder = regularAccounts[0]; + await accessControl.grantRole( + mTokenPermissionedMinBalanceRoles.greenlisted, + holder.address, + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + holder, + parseUnits('1'), + ); + await blackList( + { + blacklistable: mTokenPermissionedMinBalance, + accessControl, + owner, + }, + holder, + ); + + await burn( + { tokenContract: mTokenPermissionedMinBalance, owner }, + holder, + parseUnits('1'), + { revertMessage: acErrors.WMAC_HAS_ROLE }, + ); + }); + + it('burnGoverned when holder is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const holder = regularAccounts[0]; + const amount = parseUnits('1'); + await accessControl.grantRole( + mTokenPermissionedMinBalanceRoles.greenlisted, + holder.address, + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + holder, + amount, + ); + await blackList( + { + blacklistable: mTokenPermissionedMinBalance, + accessControl, + owner, + }, + holder, + ); + + await expect( + mTokenPermissionedMinBalance + .connect(owner) + .burnGoverned(holder.address, amount), + ).not.reverted; + expect(await mTokenPermissionedMinBalance.balanceOf(holder.address)).eq( + 0, + ); + }); + }); + + describe('transferFrom()', () => { + it('should fail: transferFrom when from is not greenlisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const spender = regularAccounts[1]; + const to = regularAccounts[2]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + const amount = parseUnits('0.1'); + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('3'), + ); + await mTokenPermissionedMinBalance + .connect(from) + .approve(spender.address, amount); + await accessControl.revokeRole(greenlisted, from.address); + + await expect( + mTokenPermissionedMinBalance + .connect(spender) + .transferFrom(from.address, to.address, amount), + ).revertedWith(acErrors.WMAC_HASNT_ROLE); + }); + + it('should fail: transferFrom dust to empty recipient when both greenlisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const spender = regularAccounts[1]; + const to = regularAccounts[2]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + const amount = parseUnits('0.1'); + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + await mTokenPermissionedMinBalance + .connect(from) + .approve(spender.address, amount); + + await expect( + mTokenPermissionedMinBalance + .connect(spender) + .transferFrom(from.address, to.address, amount), + ).revertedWith('MTMB: min balance not met'); + }); + + it('transferFrom when both greenlisted and above min balance', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const spender = regularAccounts[1]; + const to = regularAccounts[2]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + const amount = parseUnits('0.1'); + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('3'), + ); + await mTokenPermissionedMinBalance + .connect(from) + .approve(spender.address, amount); + + await expect( + mTokenPermissionedMinBalance + .connect(spender) + .transferFrom(from.address, to.address, amount), + ).not.reverted; + }); + + it('should fail: transferFrom when from is blacklisted', async () => { + const baseFixture = await defaultDeploy(); + const { + owner, + accessControl, + regularAccounts, + mTokenPermissionedMinBalance, + mTokenPermissionedMinBalanceRoles, + } = await loadFixture( + mTokenPermissionedMinBalanceFixture.bind(this, baseFixture), + ); + + const from = regularAccounts[0]; + const spender = regularAccounts[1]; + const to = regularAccounts[2]; + const { greenlisted } = mTokenPermissionedMinBalanceRoles; + const amount = parseUnits('0.1'); + + await accessControl.grantRole(greenlisted, from.address); + await accessControl.grantRole(greenlisted, to.address); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + from, + parseUnits('3'), + ); + await mint( + { tokenContract: mTokenPermissionedMinBalance, owner }, + to, + parseUnits('3'), + ); + await mTokenPermissionedMinBalance + .connect(from) + .approve(spender.address, amount); + await blackList( + { + blacklistable: mTokenPermissionedMinBalance, + accessControl, + owner, + }, + from, + ); + + await expect( + mTokenPermissionedMinBalance + .connect(spender) + .transferFrom(from.address, to.address, amount), + ).revertedWith(acErrors.WMAC_HAS_ROLE); + }); + }); + }); }); describe('Shared greenlist role (mGLO -> mGLOBAL)', () => { @@ -475,3 +1895,123 @@ describe('Shared greenlist role (mGLO -> mGLOBAL)', () => { ); }); }); + +describe('mToken upgrades', () => { + describe('PermissionedMinBalance -> Permissioned', () => { + const permissionedMinBalanceProxyFixture = async () => { + const { owner, accessControl, regularAccounts } = await defaultDeploy(); + + const token = (await upgrades.deployProxy( + new MTokenPermissionedMinBalanceTest__factory(owner), + [accessControl.address], + { initializer: 'initialize', kind: 'transparent' }, + )) as MTokenPermissionedMinBalanceTest; + + const mintRole = await token.M_TOKEN_TEST_MINT_OPERATOR_ROLE(); + const burnRole = await token.M_TOKEN_TEST_BURN_OPERATOR_ROLE(); + const pauseRole = await token.M_TOKEN_TEST_PAUSE_OPERATOR_ROLE(); + const greenlisted = await token.M_TOKEN_TEST_GREENLISTED_ROLE(); + const minBalanceExempt = + await token.M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE(); + + await accessControl.grantRole(mintRole, owner.address); + await accessControl.grantRole(burnRole, owner.address); + await accessControl.grantRole(pauseRole, owner.address); + + return { + owner, + accessControl, + regularAccounts, + token, + roles: { mintRole, burnRole, pauseRole, greenlisted, minBalanceExempt }, + }; + }; + + it('preserves balances and keeps greenlist after dropping min-balance checks', async () => { + const { owner, accessControl, regularAccounts, token, roles } = + await loadFixture(permissionedMinBalanceProxyFixture); + + const from = regularAccounts[0]; + const to = regularAccounts[1]; + const dust = parseUnits('0.1'); + + await accessControl.grantRole(roles.greenlisted, from.address); + await accessControl.grantRole(roles.greenlisted, to.address); + + await mint({ tokenContract: token, owner }, from, parseUnits('3')); + + const balanceBefore = await token.balanceOf(from.address); + const totalSupplyBefore = await token.totalSupply(); + const nameBefore = await token.name(); + const symbolBefore = await token.symbol(); + + await expect(token.connect(from).transfer(to.address, dust)).revertedWith( + 'MTMB: min balance not met', + ); + + await upgrades.upgradeProxy( + token.address, + new MTokenPermissionedTest__factory(owner), + { + unsafeSkipStorageCheck: true, + }, + ); + + const upgraded = MTokenPermissionedTest__factory.connect( + token.address, + owner, + ); + + expect(await upgraded.balanceOf(from.address)).eq(balanceBefore); + expect(await upgraded.totalSupply()).eq(totalSupplyBefore); + expect(await upgraded.name()).eq(nameBefore); + expect(await upgraded.symbol()).eq(symbolBefore); + + await expect(upgraded.connect(from).transfer(to.address, dust)).not + .reverted; + expect(await upgraded.balanceOf(to.address)).eq(dust); + + await accessControl.revokeRole(roles.greenlisted, from.address); + await expect( + upgraded.connect(from).transfer(to.address, dust), + ).revertedWith(acErrors.WMAC_HASNT_ROLE); + }); + + it('preserves greenlist mint rules after upgrade', async () => { + const { owner, accessControl, regularAccounts, token, roles } = + await loadFixture(permissionedMinBalanceProxyFixture); + + const recipient = regularAccounts[0]; + + await mint({ tokenContract: token, owner }, recipient, parseUnits('1'), { + revertMessage: acErrors.WMAC_HASNT_ROLE, + }); + + await upgrades.upgradeProxy( + token.address, + new MTokenPermissionedTest__factory(owner), + { unsafeSkipStorageCheck: true }, + ); + + const upgraded = MTokenPermissionedTest__factory.connect( + token.address, + owner, + ); + + await mint( + { tokenContract: upgraded, owner }, + recipient, + parseUnits('1'), + { revertMessage: acErrors.WMAC_HASNT_ROLE }, + ); + + await accessControl.grantRole(roles.greenlisted, recipient.address); + await mint( + { tokenContract: upgraded, owner }, + recipient, + parseUnits('0.5'), + ); + expect(await upgraded.balanceOf(recipient.address)).eq(parseUnits('0.5')); + }); + }); +});